diff --git a/angular.json b/angular.json index 742f216..640c269 100644 --- a/angular.json +++ b/angular.json @@ -31,7 +31,12 @@ "quill-delta", "buffer", "localforage", - "moment" + "moment", + "bech32", + "bn.js", + "qrcode", + "dayjs", + "dayjs/plugin/relativeTime" ], "assets": [ { diff --git a/package-lock.json b/package-lock.json index 748a576..d56e248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "angor-hub", - "version": "0.0.7", + "version": "0.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "angor-hub", - "version": "0.0.7", + "version": "0.0.8", "dependencies": { "@angular-builders/custom-webpack": "^18.0.0", "@angular/animations": "18.2.6", @@ -91,7 +91,7 @@ "karma-jasmine-html-reporter": "2.1.0", "lodash": "4.17.21", "postcss": "8.4.47", - "prettier": "3.3.3", + "prettier": "^3.3.3", "prettier-plugin-organize-imports": "4.1.0", "prettier-plugin-tailwindcss": "0.6.8", "tailwindcss": "3.4.13", diff --git a/package.json b/package.json index cc42593..89d96e3 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "test": "ng test", "deploy": "ng deploy", "version": "node -p \"require('./package.json').version\"", - "changelog": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md" + "changelog": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md", + "format": "prettier --write \"src/**/*.{ts,html,css,scss,json,js}\"" }, "dependencies": { "@angular-builders/custom-webpack": "^18.0.0", @@ -98,7 +99,7 @@ "karma-jasmine-html-reporter": "2.1.0", "lodash": "4.17.21", "postcss": "8.4.47", - "prettier": "3.3.3", + "prettier": "^3.3.3", "prettier-plugin-organize-imports": "4.1.0", "prettier-plugin-tailwindcss": "0.6.8", "tailwindcss": "3.4.13", diff --git a/public/data/emoji.json b/public/data/emoji.json deleted file mode 100644 index 6aa7136..0000000 --- a/public/data/emoji.json +++ /dev/null @@ -1,7612 +0,0 @@ -{ - "Smileys & Emotion": [ - { - "emoji": "๐Ÿ˜€", - "name": "grinning face" - }, - { - "emoji": "๐Ÿ˜ƒ", - "name": "grinning face with big eyes" - }, - { - "emoji": "๐Ÿ˜„", - "name": "grinning face with smiling eyes" - }, - { - "emoji": "๐Ÿ˜", - "name": "beaming face with smiling eyes" - }, - { - "emoji": "๐Ÿ˜†", - "name": "grinning squinting face" - }, - { - "emoji": "๐Ÿ˜…", - "name": "grinning face with sweat" - }, - { - "emoji": "๐Ÿคฃ", - "name": "rolling on the floor laughing" - }, - { - "emoji": "๐Ÿ˜‚", - "name": "face with tears of joy" - }, - { - "emoji": "๐Ÿ™‚", - "name": "slightly smiling face" - }, - { - "emoji": "๐Ÿ™ƒ", - "name": "upside-down face" - }, - { - "emoji": "๐Ÿซ ", - "name": "melting face" - }, - { - "emoji": "๐Ÿ˜‰", - "name": "winking face" - }, - { - "emoji": "๐Ÿ˜Š", - "name": "smiling face with smiling eyes" - }, - { - "emoji": "๐Ÿ˜‡", - "name": "smiling face with halo" - }, - { - "emoji": "๐Ÿฅฐ", - "name": "smiling face with hearts" - }, - { - "emoji": "๐Ÿ˜", - "name": "smiling face with heart-eyes" - }, - { - "emoji": "๐Ÿคฉ", - "name": "star-struck" - }, - { - "emoji": "๐Ÿ˜˜", - "name": "face blowing a kiss" - }, - { - "emoji": "๐Ÿ˜—", - "name": "kissing face" - }, - { - "emoji": "โ˜บ๏ธ", - "name": "smiling face" - }, - { - "emoji": "๐Ÿ˜š", - "name": "kissing face with closed eyes" - }, - { - "emoji": "๐Ÿ˜™", - "name": "kissing face with smiling eyes" - }, - { - "emoji": "๐Ÿฅฒ", - "name": "smiling face with tear" - }, - { - "emoji": "๐Ÿ˜‹", - "name": "face savoring food" - }, - { - "emoji": "๐Ÿ˜›", - "name": "face with tongue" - }, - { - "emoji": "๐Ÿ˜œ", - "name": "winking face with tongue" - }, - { - "emoji": "๐Ÿคช", - "name": "zany face" - }, - { - "emoji": "๐Ÿ˜", - "name": "squinting face with tongue" - }, - { - "emoji": "๐Ÿค‘", - "name": "money-mouth face" - }, - { - "emoji": "๐Ÿค—", - "name": "smiling face with open hands" - }, - { - "emoji": "๐Ÿคญ", - "name": "face with hand over mouth" - }, - { - "emoji": "๐Ÿซข", - "name": "face with open eyes and hand over mouth" - }, - { - "emoji": "๐Ÿซฃ", - "name": "face with peeking eye" - }, - { - "emoji": "๐Ÿคซ", - "name": "shushing face" - }, - { - "emoji": "๐Ÿค”", - "name": "thinking face" - }, - { - "emoji": "๐Ÿซก", - "name": "saluting face" - }, - { - "emoji": "๐Ÿค", - "name": "zipper-mouth face" - }, - { - "emoji": "๐Ÿคจ", - "name": "face with raised eyebrow" - }, - { - "emoji": "๐Ÿ˜", - "name": "neutral face" - }, - { - "emoji": "๐Ÿ˜‘", - "name": "expressionless face" - }, - { - "emoji": "๐Ÿ˜ถ", - "name": "face without mouth" - }, - { - "emoji": "๐Ÿซฅ", - "name": "dotted line face" - }, - { - "emoji": "๐Ÿ˜ถโ€๐ŸŒซ๏ธ", - "name": "face in clouds" - }, - { - "emoji": "๐Ÿ˜", - "name": "smirking face" - }, - { - "emoji": "๐Ÿ˜’", - "name": "unamused face" - }, - { - "emoji": "๐Ÿ™„", - "name": "face with rolling eyes" - }, - { - "emoji": "๐Ÿ˜ฌ", - "name": "grimacing face" - }, - { - "emoji": "๐Ÿ˜ฎโ€๐Ÿ’จ", - "name": "face exhaling" - }, - { - "emoji": "๐Ÿคฅ", - "name": "lying face" - }, - { - "emoji": "๐Ÿซจ", - "name": "shaking face" - }, - { - "emoji": "๐Ÿ™‚โ€โ†”๏ธ", - "name": "head shaking horizontally" - }, - { - "emoji": "๐Ÿ™‚โ€โ†•๏ธ", - "name": "head shaking vertically" - }, - { - "emoji": "๐Ÿ˜Œ", - "name": "relieved face" - }, - { - "emoji": "๐Ÿ˜”", - "name": "pensive face" - }, - { - "emoji": "๐Ÿ˜ช", - "name": "sleepy face" - }, - { - "emoji": "๐Ÿคค", - "name": "drooling face" - }, - { - "emoji": "๐Ÿ˜ด", - "name": "sleeping face" - }, - { - "emoji": "๐Ÿ˜ท", - "name": "face with medical mask" - }, - { - "emoji": "๐Ÿค’", - "name": "face with thermometer" - }, - { - "emoji": "๐Ÿค•", - "name": "face with head-bandage" - }, - { - "emoji": "๐Ÿคข", - "name": "nauseated face" - }, - { - "emoji": "๐Ÿคฎ", - "name": "face vomiting" - }, - { - "emoji": "๐Ÿคง", - "name": "sneezing face" - }, - { - "emoji": "๐Ÿฅต", - "name": "hot face" - }, - { - "emoji": "๐Ÿฅถ", - "name": "cold face" - }, - { - "emoji": "๐Ÿฅด", - "name": "woozy face" - }, - { - "emoji": "๐Ÿ˜ต", - "name": "face with crossed-out eyes" - }, - { - "emoji": "๐Ÿ˜ตโ€๐Ÿ’ซ", - "name": "face with spiral eyes" - }, - { - "emoji": "๐Ÿคฏ", - "name": "exploding head" - }, - { - "emoji": "๐Ÿค ", - "name": "cowboy hat face" - }, - { - "emoji": "๐Ÿฅณ", - "name": "partying face" - }, - { - "emoji": "๐Ÿฅธ", - "name": "disguised face" - }, - { - "emoji": "๐Ÿ˜Ž", - "name": "smiling face with sunglasses" - }, - { - "emoji": "๐Ÿค“", - "name": "nerd face" - }, - { - "emoji": "๐Ÿง", - "name": "face with monocle" - }, - { - "emoji": "๐Ÿ˜•", - "name": "confused face" - }, - { - "emoji": "๐Ÿซค", - "name": "face with diagonal mouth" - }, - { - "emoji": "๐Ÿ˜Ÿ", - "name": "worried face" - }, - { - "emoji": "๐Ÿ™", - "name": "slightly frowning face" - }, - { - "emoji": "โ˜น๏ธ", - "name": "frowning face" - }, - { - "emoji": "๐Ÿ˜ฎ", - "name": "face with open mouth" - }, - { - "emoji": "๐Ÿ˜ฏ", - "name": "hushed face" - }, - { - "emoji": "๐Ÿ˜ฒ", - "name": "astonished face" - }, - { - "emoji": "๐Ÿ˜ณ", - "name": "flushed face" - }, - { - "emoji": "๐Ÿฅบ", - "name": "pleading face" - }, - { - "emoji": "๐Ÿฅน", - "name": "face holding back tears" - }, - { - "emoji": "๐Ÿ˜ฆ", - "name": "frowning face with open mouth" - }, - { - "emoji": "๐Ÿ˜ง", - "name": "anguished face" - }, - { - "emoji": "๐Ÿ˜จ", - "name": "fearful face" - }, - { - "emoji": "๐Ÿ˜ฐ", - "name": "anxious face with sweat" - }, - { - "emoji": "๐Ÿ˜ฅ", - "name": "sad but relieved face" - }, - { - "emoji": "๐Ÿ˜ข", - "name": "crying face" - }, - { - "emoji": "๐Ÿ˜ญ", - "name": "loudly crying face" - }, - { - "emoji": "๐Ÿ˜ฑ", - "name": "face screaming in fear" - }, - { - "emoji": "๐Ÿ˜–", - "name": "confounded face" - }, - { - "emoji": "๐Ÿ˜ฃ", - "name": "persevering face" - }, - { - "emoji": "๐Ÿ˜ž", - "name": "disappointed face" - }, - { - "emoji": "๐Ÿ˜“", - "name": "downcast face with sweat" - }, - { - "emoji": "๐Ÿ˜ฉ", - "name": "weary face" - }, - { - "emoji": "๐Ÿ˜ซ", - "name": "tired face" - }, - { - "emoji": "๐Ÿฅฑ", - "name": "yawning face" - }, - { - "emoji": "๐Ÿ˜ค", - "name": "face with steam from nose" - }, - { - "emoji": "๐Ÿ˜ก", - "name": "enraged face" - }, - { - "emoji": "๐Ÿ˜ ", - "name": "angry face" - }, - { - "emoji": "๐Ÿคฌ", - "name": "face with symbols on mouth" - }, - { - "emoji": "๐Ÿ˜ˆ", - "name": "smiling face with horns" - }, - { - "emoji": "๐Ÿ‘ฟ", - "name": "angry face with horns" - }, - { - "emoji": "๐Ÿ’€", - "name": "skull" - }, - { - "emoji": "โ˜ ๏ธ", - "name": "skull and crossbones" - }, - { - "emoji": "๐Ÿ’ฉ", - "name": "pile of poo" - }, - { - "emoji": "๐Ÿคก", - "name": "clown face" - }, - { - "emoji": "๐Ÿ‘น", - "name": "ogre" - }, - { - "emoji": "๐Ÿ‘บ", - "name": "goblin" - }, - { - "emoji": "๐Ÿ‘ป", - "name": "ghost" - }, - { - "emoji": "๐Ÿ‘ฝ", - "name": "alien" - }, - { - "emoji": "๐Ÿ‘พ", - "name": "alien monster" - }, - { - "emoji": "๐Ÿค–", - "name": "robot" - }, - { - "emoji": "๐Ÿ˜บ", - "name": "grinning cat" - }, - { - "emoji": "๐Ÿ˜ธ", - "name": "grinning cat with smiling eyes" - }, - { - "emoji": "๐Ÿ˜น", - "name": "cat with tears of joy" - }, - { - "emoji": "๐Ÿ˜ป", - "name": "smiling cat with heart-eyes" - }, - { - "emoji": "๐Ÿ˜ผ", - "name": "cat with wry smile" - }, - { - "emoji": "๐Ÿ˜ฝ", - "name": "kissing cat" - }, - { - "emoji": "๐Ÿ™€", - "name": "weary cat" - }, - { - "emoji": "๐Ÿ˜ฟ", - "name": "crying cat" - }, - { - "emoji": "๐Ÿ˜พ", - "name": "pouting cat" - }, - { - "emoji": "๐Ÿ™ˆ", - "name": "see-no-evil monkey" - }, - { - "emoji": "๐Ÿ™‰", - "name": "hear-no-evil monkey" - }, - { - "emoji": "๐Ÿ™Š", - "name": "speak-no-evil monkey" - }, - { - "emoji": "๐Ÿ’Œ", - "name": "love letter" - }, - { - "emoji": "๐Ÿ’˜", - "name": "heart with arrow" - }, - { - "emoji": "๐Ÿ’", - "name": "heart with ribbon" - }, - { - "emoji": "๐Ÿ’–", - "name": "sparkling heart" - }, - { - "emoji": "๐Ÿ’—", - "name": "growing heart" - }, - { - "emoji": "๐Ÿ’“", - "name": "beating heart" - }, - { - "emoji": "๐Ÿ’ž", - "name": "revolving hearts" - }, - { - "emoji": "๐Ÿ’•", - "name": "two hearts" - }, - { - "emoji": "๐Ÿ’Ÿ", - "name": "heart decoration" - }, - { - "emoji": "โฃ๏ธ", - "name": "heart exclamation" - }, - { - "emoji": "๐Ÿ’”", - "name": "broken heart" - }, - { - "emoji": "โค๏ธโ€๐Ÿ”ฅ", - "name": "heart on fire" - }, - { - "emoji": "โค๏ธโ€๐Ÿฉน", - "name": "mending heart" - }, - { - "emoji": "โค๏ธ", - "name": "red heart" - }, - { - "emoji": "๐Ÿฉท", - "name": "pink heart" - }, - { - "emoji": "๐Ÿงก", - "name": "orange heart" - }, - { - "emoji": "๐Ÿ’›", - "name": "yellow heart" - }, - { - "emoji": "๐Ÿ’š", - "name": "green heart" - }, - { - "emoji": "๐Ÿ’™", - "name": "blue heart" - }, - { - "emoji": "๐Ÿฉต", - "name": "light blue heart" - }, - { - "emoji": "๐Ÿ’œ", - "name": "purple heart" - }, - { - "emoji": "๐ŸคŽ", - "name": "brown heart" - }, - { - "emoji": "๐Ÿ–ค", - "name": "black heart" - }, - { - "emoji": "๐Ÿฉถ", - "name": "grey heart" - }, - { - "emoji": "๐Ÿค", - "name": "white heart" - }, - { - "emoji": "๐Ÿ’‹", - "name": "kiss mark" - }, - { - "emoji": "๐Ÿ’ฏ", - "name": "hundred points" - }, - { - "emoji": "๐Ÿ’ข", - "name": "anger symbol" - }, - { - "emoji": "๐Ÿ’ฅ", - "name": "collision" - }, - { - "emoji": "๐Ÿ’ซ", - "name": "dizzy" - }, - { - "emoji": "๐Ÿ’ฆ", - "name": "sweat droplets" - }, - { - "emoji": "๐Ÿ’จ", - "name": "dashing away" - }, - { - "emoji": "๐Ÿ•ณ๏ธ", - "name": "hole" - }, - { - "emoji": "๐Ÿ’ฌ", - "name": "speech balloon" - }, - { - "emoji": "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", - "name": "eye in speech bubble" - }, - { - "emoji": "๐Ÿ—จ๏ธ", - "name": "left speech bubble" - }, - { - "emoji": "๐Ÿ—ฏ๏ธ", - "name": "right anger bubble" - }, - { - "emoji": "๐Ÿ’ญ", - "name": "thought balloon" - }, - { - "emoji": "๐Ÿ’ค", - "name": "ZZZ" - } - ], - "People & Body": [ - { - "emoji": "๐Ÿ‘‹", - "name": "waving hand" - }, - { - "emoji": "๐Ÿคš", - "name": "raised back of hand" - }, - { - "emoji": "๐Ÿ–๏ธ", - "name": "hand with fingers splayed" - }, - { - "emoji": "โœ‹", - "name": "raised hand" - }, - { - "emoji": "๐Ÿ––", - "name": "vulcan salute" - }, - { - "emoji": "๐Ÿซฑ", - "name": "rightwards hand" - }, - { - "emoji": "๐Ÿซฒ", - "name": "leftwards hand" - }, - { - "emoji": "๐Ÿซณ", - "name": "palm down hand" - }, - { - "emoji": "๐Ÿซด", - "name": "palm up hand" - }, - { - "emoji": "๐Ÿซท", - "name": "leftwards pushing hand" - }, - { - "emoji": "๐Ÿซธ", - "name": "rightwards pushing hand" - }, - { - "emoji": "๐Ÿ‘Œ", - "name": "OK hand" - }, - { - "emoji": "๐ŸคŒ", - "name": "pinched fingers" - }, - { - "emoji": "๐Ÿค", - "name": "pinching hand" - }, - { - "emoji": "โœŒ๏ธ", - "name": "victory hand" - }, - { - "emoji": "๐Ÿคž", - "name": "crossed fingers" - }, - { - "emoji": "๐Ÿซฐ", - "name": "hand with index finger and thumb crossed" - }, - { - "emoji": "๐ŸคŸ", - "name": "love-you gesture" - }, - { - "emoji": "๐Ÿค˜", - "name": "sign of the horns" - }, - { - "emoji": "๐Ÿค™", - "name": "call me hand" - }, - { - "emoji": "๐Ÿ‘ˆ", - "name": "backhand index pointing left" - }, - { - "emoji": "๐Ÿ‘‰", - "name": "backhand index pointing right" - }, - { - "emoji": "๐Ÿ‘†", - "name": "backhand index pointing up" - }, - { - "emoji": "๐Ÿ–•", - "name": "middle finger" - }, - { - "emoji": "๐Ÿ‘‡", - "name": "backhand index pointing down" - }, - { - "emoji": "โ˜๏ธ", - "name": "index pointing up" - }, - { - "emoji": "๐Ÿซต", - "name": "index pointing at the viewer" - }, - { - "emoji": "๐Ÿ‘", - "name": "thumbs up" - }, - { - "emoji": "๐Ÿ‘Ž", - "name": "thumbs down" - }, - { - "emoji": "โœŠ", - "name": "raised fist" - }, - { - "emoji": "๐Ÿ‘Š", - "name": "oncoming fist" - }, - { - "emoji": "๐Ÿค›", - "name": "left-facing fist" - }, - { - "emoji": "๐Ÿคœ", - "name": "right-facing fist" - }, - { - "emoji": "๐Ÿ‘", - "name": "clapping hands" - }, - { - "emoji": "๐Ÿ™Œ", - "name": "raising hands" - }, - { - "emoji": "๐Ÿซถ", - "name": "heart hands" - }, - { - "emoji": "๐Ÿ‘", - "name": "open hands" - }, - { - "emoji": "๐Ÿคฒ", - "name": "palms up together" - }, - { - "emoji": "๐Ÿค", - "name": "handshake" - }, - { - "emoji": "๐Ÿ™", - "name": "folded hands" - }, - { - "emoji": "โœ๏ธ", - "name": "writing hand" - }, - { - "emoji": "๐Ÿ’…", - "name": "nail polish" - }, - { - "emoji": "๐Ÿคณ", - "name": "selfie" - }, - { - "emoji": "๐Ÿ’ช", - "name": "flexed biceps" - }, - { - "emoji": "๐Ÿฆพ", - "name": "mechanical arm" - }, - { - "emoji": "๐Ÿฆฟ", - "name": "mechanical leg" - }, - { - "emoji": "๐Ÿฆต", - "name": "leg" - }, - { - "emoji": "๐Ÿฆถ", - "name": "foot" - }, - { - "emoji": "๐Ÿ‘‚", - "name": "ear" - }, - { - "emoji": "๐Ÿฆป", - "name": "ear with hearing aid" - }, - { - "emoji": "๐Ÿ‘ƒ", - "name": "nose" - }, - { - "emoji": "๐Ÿง ", - "name": "brain" - }, - { - "emoji": "๐Ÿซ€", - "name": "anatomical heart" - }, - { - "emoji": "๐Ÿซ", - "name": "lungs" - }, - { - "emoji": "๐Ÿฆท", - "name": "tooth" - }, - { - "emoji": "๐Ÿฆด", - "name": "bone" - }, - { - "emoji": "๐Ÿ‘€", - "name": "eyes" - }, - { - "emoji": "๐Ÿ‘๏ธ", - "name": "eye" - }, - { - "emoji": "๐Ÿ‘…", - "name": "tongue" - }, - { - "emoji": "๐Ÿ‘„", - "name": "mouth" - }, - { - "emoji": "๐Ÿซฆ", - "name": "biting lip" - }, - { - "emoji": "๐Ÿ‘ถ", - "name": "baby" - }, - { - "emoji": "๐Ÿง’", - "name": "child" - }, - { - "emoji": "๐Ÿ‘ฆ", - "name": "boy" - }, - { - "emoji": "๐Ÿ‘ง", - "name": "girl" - }, - { - "emoji": "๐Ÿง‘", - "name": "person" - }, - { - "emoji": "๐Ÿ‘ฑ", - "name": "person blond hair" - }, - { - "emoji": "๐Ÿ‘จ", - "name": "man" - }, - { - "emoji": "๐Ÿง”", - "name": "person beard" - }, - { - "emoji": "๐Ÿง”โ€โ™‚๏ธ", - "name": "man beard" - }, - { - "emoji": "๐Ÿง”โ€โ™€๏ธ", - "name": "woman beard" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฐ", - "name": "man red hair" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฑ", - "name": "man curly hair" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆณ", - "name": "man white hair" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฒ", - "name": "man bald" - }, - { - "emoji": "๐Ÿ‘ฉ", - "name": "woman" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฐ", - "name": "woman red hair" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฐ", - "name": "person red hair" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฑ", - "name": "woman curly hair" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฑ", - "name": "person curly hair" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆณ", - "name": "woman white hair" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆณ", - "name": "person white hair" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฒ", - "name": "woman bald" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฒ", - "name": "person bald" - }, - { - "emoji": "๐Ÿ‘ฑโ€โ™€๏ธ", - "name": "woman blond hair" - }, - { - "emoji": "๐Ÿ‘ฑโ€โ™‚๏ธ", - "name": "man blond hair" - }, - { - "emoji": "๐Ÿง“", - "name": "older person" - }, - { - "emoji": "๐Ÿ‘ด", - "name": "old man" - }, - { - "emoji": "๐Ÿ‘ต", - "name": "old woman" - }, - { - "emoji": "๐Ÿ™", - "name": "person frowning" - }, - { - "emoji": "๐Ÿ™โ€โ™‚๏ธ", - "name": "man frowning" - }, - { - "emoji": "๐Ÿ™โ€โ™€๏ธ", - "name": "woman frowning" - }, - { - "emoji": "๐Ÿ™Ž", - "name": "person pouting" - }, - { - "emoji": "๐Ÿ™Žโ€โ™‚๏ธ", - "name": "man pouting" - }, - { - "emoji": "๐Ÿ™Žโ€โ™€๏ธ", - "name": "woman pouting" - }, - { - "emoji": "๐Ÿ™…", - "name": "person gesturing NO" - }, - { - "emoji": "๐Ÿ™…โ€โ™‚๏ธ", - "name": "man gesturing NO" - }, - { - "emoji": "๐Ÿ™…โ€โ™€๏ธ", - "name": "woman gesturing NO" - }, - { - "emoji": "๐Ÿ™†", - "name": "person gesturing OK" - }, - { - "emoji": "๐Ÿ™†โ€โ™‚๏ธ", - "name": "man gesturing OK" - }, - { - "emoji": "๐Ÿ™†โ€โ™€๏ธ", - "name": "woman gesturing OK" - }, - { - "emoji": "๐Ÿ’", - "name": "person tipping hand" - }, - { - "emoji": "๐Ÿ’โ€โ™‚๏ธ", - "name": "man tipping hand" - }, - { - "emoji": "๐Ÿ’โ€โ™€๏ธ", - "name": "woman tipping hand" - }, - { - "emoji": "๐Ÿ™‹", - "name": "person raising hand" - }, - { - "emoji": "๐Ÿ™‹โ€โ™‚๏ธ", - "name": "man raising hand" - }, - { - "emoji": "๐Ÿ™‹โ€โ™€๏ธ", - "name": "woman raising hand" - }, - { - "emoji": "๐Ÿง", - "name": "deaf person" - }, - { - "emoji": "๐Ÿงโ€โ™‚๏ธ", - "name": "deaf man" - }, - { - "emoji": "๐Ÿงโ€โ™€๏ธ", - "name": "deaf woman" - }, - { - "emoji": "๐Ÿ™‡", - "name": "person bowing" - }, - { - "emoji": "๐Ÿ™‡โ€โ™‚๏ธ", - "name": "man bowing" - }, - { - "emoji": "๐Ÿ™‡โ€โ™€๏ธ", - "name": "woman bowing" - }, - { - "emoji": "๐Ÿคฆ", - "name": "person facepalming" - }, - { - "emoji": "๐Ÿคฆโ€โ™‚๏ธ", - "name": "man facepalming" - }, - { - "emoji": "๐Ÿคฆโ€โ™€๏ธ", - "name": "woman facepalming" - }, - { - "emoji": "๐Ÿคท", - "name": "person shrugging" - }, - { - "emoji": "๐Ÿคทโ€โ™‚๏ธ", - "name": "man shrugging" - }, - { - "emoji": "๐Ÿคทโ€โ™€๏ธ", - "name": "woman shrugging" - }, - { - "emoji": "๐Ÿง‘โ€โš•๏ธ", - "name": "health worker" - }, - { - "emoji": "๐Ÿ‘จโ€โš•๏ธ", - "name": "man health worker" - }, - { - "emoji": "๐Ÿ‘ฉโ€โš•๏ธ", - "name": "woman health worker" - }, - { - "emoji": "๐Ÿง‘โ€๐ŸŽ“", - "name": "student" - }, - { - "emoji": "๐Ÿ‘จโ€๐ŸŽ“", - "name": "man student" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐ŸŽ“", - "name": "woman student" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿซ", - "name": "teacher" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿซ", - "name": "man teacher" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿซ", - "name": "woman teacher" - }, - { - "emoji": "๐Ÿง‘โ€โš–๏ธ", - "name": "judge" - }, - { - "emoji": "๐Ÿ‘จโ€โš–๏ธ", - "name": "man judge" - }, - { - "emoji": "๐Ÿ‘ฉโ€โš–๏ธ", - "name": "woman judge" - }, - { - "emoji": "๐Ÿง‘โ€๐ŸŒพ", - "name": "farmer" - }, - { - "emoji": "๐Ÿ‘จโ€๐ŸŒพ", - "name": "man farmer" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐ŸŒพ", - "name": "woman farmer" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿณ", - "name": "cook" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿณ", - "name": "man cook" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿณ", - "name": "woman cook" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿ”ง", - "name": "mechanic" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ”ง", - "name": "man mechanic" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ”ง", - "name": "woman mechanic" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿญ", - "name": "factory worker" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿญ", - "name": "man factory worker" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿญ", - "name": "woman factory worker" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿ’ผ", - "name": "office worker" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ’ผ", - "name": "man office worker" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ’ผ", - "name": "woman office worker" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿ”ฌ", - "name": "scientist" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ”ฌ", - "name": "man scientist" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ”ฌ", - "name": "woman scientist" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿ’ป", - "name": "technologist" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ’ป", - "name": "man technologist" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ’ป", - "name": "woman technologist" - }, - { - "emoji": "๐Ÿง‘โ€๐ŸŽค", - "name": "singer" - }, - { - "emoji": "๐Ÿ‘จโ€๐ŸŽค", - "name": "man singer" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐ŸŽค", - "name": "woman singer" - }, - { - "emoji": "๐Ÿง‘โ€๐ŸŽจ", - "name": "artist" - }, - { - "emoji": "๐Ÿ‘จโ€๐ŸŽจ", - "name": "man artist" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐ŸŽจ", - "name": "woman artist" - }, - { - "emoji": "๐Ÿง‘โ€โœˆ๏ธ", - "name": "pilot" - }, - { - "emoji": "๐Ÿ‘จโ€โœˆ๏ธ", - "name": "man pilot" - }, - { - "emoji": "๐Ÿ‘ฉโ€โœˆ๏ธ", - "name": "woman pilot" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿš€", - "name": "astronaut" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿš€", - "name": "man astronaut" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿš€", - "name": "woman astronaut" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿš’", - "name": "firefighter" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿš’", - "name": "man firefighter" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿš’", - "name": "woman firefighter" - }, - { - "emoji": "๐Ÿ‘ฎ", - "name": "police officer" - }, - { - "emoji": "๐Ÿ‘ฎโ€โ™‚๏ธ", - "name": "man police officer" - }, - { - "emoji": "๐Ÿ‘ฎโ€โ™€๏ธ", - "name": "woman police officer" - }, - { - "emoji": "๐Ÿ•ต๏ธ", - "name": "detective" - }, - { - "emoji": "๐Ÿ•ต๏ธโ€โ™‚๏ธ", - "name": "man detective" - }, - { - "emoji": "๐Ÿ•ต๏ธโ€โ™€๏ธ", - "name": "woman detective" - }, - { - "emoji": "๐Ÿ’‚", - "name": "guard" - }, - { - "emoji": "๐Ÿ’‚โ€โ™‚๏ธ", - "name": "man guard" - }, - { - "emoji": "๐Ÿ’‚โ€โ™€๏ธ", - "name": "woman guard" - }, - { - "emoji": "๐Ÿฅท", - "name": "ninja" - }, - { - "emoji": "๐Ÿ‘ท", - "name": "construction worker" - }, - { - "emoji": "๐Ÿ‘ทโ€โ™‚๏ธ", - "name": "man construction worker" - }, - { - "emoji": "๐Ÿ‘ทโ€โ™€๏ธ", - "name": "woman construction worker" - }, - { - "emoji": "๐Ÿซ…", - "name": "person with crown" - }, - { - "emoji": "๐Ÿคด", - "name": "prince" - }, - { - "emoji": "๐Ÿ‘ธ", - "name": "princess" - }, - { - "emoji": "๐Ÿ‘ณ", - "name": "person wearing turban" - }, - { - "emoji": "๐Ÿ‘ณโ€โ™‚๏ธ", - "name": "man wearing turban" - }, - { - "emoji": "๐Ÿ‘ณโ€โ™€๏ธ", - "name": "woman wearing turban" - }, - { - "emoji": "๐Ÿ‘ฒ", - "name": "person with skullcap" - }, - { - "emoji": "๐Ÿง•", - "name": "woman with headscarf" - }, - { - "emoji": "๐Ÿคต", - "name": "person in tuxedo" - }, - { - "emoji": "๐Ÿคตโ€โ™‚๏ธ", - "name": "man in tuxedo" - }, - { - "emoji": "๐Ÿคตโ€โ™€๏ธ", - "name": "woman in tuxedo" - }, - { - "emoji": "๐Ÿ‘ฐ", - "name": "person with veil" - }, - { - "emoji": "๐Ÿ‘ฐโ€โ™‚๏ธ", - "name": "man with veil" - }, - { - "emoji": "๐Ÿ‘ฐโ€โ™€๏ธ", - "name": "woman with veil" - }, - { - "emoji": "๐Ÿคฐ", - "name": "pregnant woman" - }, - { - "emoji": "๐Ÿซƒ", - "name": "pregnant man" - }, - { - "emoji": "๐Ÿซ„", - "name": "pregnant person" - }, - { - "emoji": "๐Ÿคฑ", - "name": "breast-feeding" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿผ", - "name": "woman feeding baby" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿผ", - "name": "man feeding baby" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿผ", - "name": "person feeding baby" - }, - { - "emoji": "๐Ÿ‘ผ", - "name": "baby angel" - }, - { - "emoji": "๐ŸŽ…", - "name": "Santa Claus" - }, - { - "emoji": "๐Ÿคถ", - "name": "Mrs. Claus" - }, - { - "emoji": "๐Ÿง‘โ€๐ŸŽ„", - "name": "mx claus" - }, - { - "emoji": "๐Ÿฆธ", - "name": "superhero" - }, - { - "emoji": "๐Ÿฆธโ€โ™‚๏ธ", - "name": "man superhero" - }, - { - "emoji": "๐Ÿฆธโ€โ™€๏ธ", - "name": "woman superhero" - }, - { - "emoji": "๐Ÿฆน", - "name": "supervillain" - }, - { - "emoji": "๐Ÿฆนโ€โ™‚๏ธ", - "name": "man supervillain" - }, - { - "emoji": "๐Ÿฆนโ€โ™€๏ธ", - "name": "woman supervillain" - }, - { - "emoji": "๐Ÿง™", - "name": "mage" - }, - { - "emoji": "๐Ÿง™โ€โ™‚๏ธ", - "name": "man mage" - }, - { - "emoji": "๐Ÿง™โ€โ™€๏ธ", - "name": "woman mage" - }, - { - "emoji": "๐Ÿงš", - "name": "fairy" - }, - { - "emoji": "๐Ÿงšโ€โ™‚๏ธ", - "name": "man fairy" - }, - { - "emoji": "๐Ÿงšโ€โ™€๏ธ", - "name": "woman fairy" - }, - { - "emoji": "๐Ÿง›", - "name": "vampire" - }, - { - "emoji": "๐Ÿง›โ€โ™‚๏ธ", - "name": "man vampire" - }, - { - "emoji": "๐Ÿง›โ€โ™€๏ธ", - "name": "woman vampire" - }, - { - "emoji": "๐Ÿงœ", - "name": "merperson" - }, - { - "emoji": "๐Ÿงœโ€โ™‚๏ธ", - "name": "merman" - }, - { - "emoji": "๐Ÿงœโ€โ™€๏ธ", - "name": "mermaid" - }, - { - "emoji": "๐Ÿง", - "name": "elf" - }, - { - "emoji": "๐Ÿงโ€โ™‚๏ธ", - "name": "man elf" - }, - { - "emoji": "๐Ÿงโ€โ™€๏ธ", - "name": "woman elf" - }, - { - "emoji": "๐Ÿงž", - "name": "genie" - }, - { - "emoji": "๐Ÿงžโ€โ™‚๏ธ", - "name": "man genie" - }, - { - "emoji": "๐Ÿงžโ€โ™€๏ธ", - "name": "woman genie" - }, - { - "emoji": "๐ŸงŸ", - "name": "zombie" - }, - { - "emoji": "๐ŸงŸโ€โ™‚๏ธ", - "name": "man zombie" - }, - { - "emoji": "๐ŸงŸโ€โ™€๏ธ", - "name": "woman zombie" - }, - { - "emoji": "๐ŸงŒ", - "name": "troll" - }, - { - "emoji": "๐Ÿ’†", - "name": "person getting massage" - }, - { - "emoji": "๐Ÿ’†โ€โ™‚๏ธ", - "name": "man getting massage" - }, - { - "emoji": "๐Ÿ’†โ€โ™€๏ธ", - "name": "woman getting massage" - }, - { - "emoji": "๐Ÿ’‡", - "name": "person getting haircut" - }, - { - "emoji": "๐Ÿ’‡โ€โ™‚๏ธ", - "name": "man getting haircut" - }, - { - "emoji": "๐Ÿ’‡โ€โ™€๏ธ", - "name": "woman getting haircut" - }, - { - "emoji": "๐Ÿšถ", - "name": "person walking" - }, - { - "emoji": "๐Ÿšถโ€โ™‚๏ธ", - "name": "man walking" - }, - { - "emoji": "๐Ÿšถโ€โ™€๏ธ", - "name": "woman walking" - }, - { - "emoji": "๐Ÿšถโ€โžก๏ธ", - "name": "person walking facing right" - }, - { - "emoji": "๐Ÿšถโ€โ™€๏ธโ€โžก๏ธ", - "name": "woman walking facing right" - }, - { - "emoji": "๐Ÿšถโ€โ™‚๏ธโ€โžก๏ธ", - "name": "man walking facing right" - }, - { - "emoji": "๐Ÿง", - "name": "person standing" - }, - { - "emoji": "๐Ÿงโ€โ™‚๏ธ", - "name": "man standing" - }, - { - "emoji": "๐Ÿงโ€โ™€๏ธ", - "name": "woman standing" - }, - { - "emoji": "๐ŸงŽ", - "name": "person kneeling" - }, - { - "emoji": "๐ŸงŽโ€โ™‚๏ธ", - "name": "man kneeling" - }, - { - "emoji": "๐ŸงŽโ€โ™€๏ธ", - "name": "woman kneeling" - }, - { - "emoji": "๐ŸงŽโ€โžก๏ธ", - "name": "person kneeling facing right" - }, - { - "emoji": "๐ŸงŽโ€โ™€๏ธโ€โžก๏ธ", - "name": "woman kneeling facing right" - }, - { - "emoji": "๐ŸงŽโ€โ™‚๏ธโ€โžก๏ธ", - "name": "man kneeling facing right" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฏ", - "name": "person with white cane" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฏโ€โžก๏ธ", - "name": "person with white cane facing right" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฏ", - "name": "man with white cane" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฏโ€โžก๏ธ", - "name": "man with white cane facing right" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฏ", - "name": "woman with white cane" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก๏ธ", - "name": "woman with white cane facing right" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆผ", - "name": "person in motorized wheelchair" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆผโ€โžก๏ธ", - "name": "person in motorized wheelchair facing right" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆผ", - "name": "man in motorized wheelchair" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆผโ€โžก๏ธ", - "name": "man in motorized wheelchair facing right" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆผ", - "name": "woman in motorized wheelchair" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก๏ธ", - "name": "woman in motorized wheelchair facing right" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฝ", - "name": "person in manual wheelchair" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿฆฝโ€โžก๏ธ", - "name": "person in manual wheelchair facing right" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฝ", - "name": "man in manual wheelchair" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿฆฝโ€โžก๏ธ", - "name": "man in manual wheelchair facing right" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฝ", - "name": "woman in manual wheelchair" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก๏ธ", - "name": "woman in manual wheelchair facing right" - }, - { - "emoji": "๐Ÿƒ", - "name": "person running" - }, - { - "emoji": "๐Ÿƒโ€โ™‚๏ธ", - "name": "man running" - }, - { - "emoji": "๐Ÿƒโ€โ™€๏ธ", - "name": "woman running" - }, - { - "emoji": "๐Ÿƒโ€โžก๏ธ", - "name": "person running facing right" - }, - { - "emoji": "๐Ÿƒโ€โ™€๏ธโ€โžก๏ธ", - "name": "woman running facing right" - }, - { - "emoji": "๐Ÿƒโ€โ™‚๏ธโ€โžก๏ธ", - "name": "man running facing right" - }, - { - "emoji": "๐Ÿ’ƒ", - "name": "woman dancing" - }, - { - "emoji": "๐Ÿ•บ", - "name": "man dancing" - }, - { - "emoji": "๐Ÿ•ด๏ธ", - "name": "person in suit levitating" - }, - { - "emoji": "๐Ÿ‘ฏ", - "name": "people with bunny ears" - }, - { - "emoji": "๐Ÿ‘ฏโ€โ™‚๏ธ", - "name": "men with bunny ears" - }, - { - "emoji": "๐Ÿ‘ฏโ€โ™€๏ธ", - "name": "women with bunny ears" - }, - { - "emoji": "๐Ÿง–", - "name": "person in steamy room" - }, - { - "emoji": "๐Ÿง–โ€โ™‚๏ธ", - "name": "man in steamy room" - }, - { - "emoji": "๐Ÿง–โ€โ™€๏ธ", - "name": "woman in steamy room" - }, - { - "emoji": "๐Ÿง—", - "name": "person climbing" - }, - { - "emoji": "๐Ÿง—โ€โ™‚๏ธ", - "name": "man climbing" - }, - { - "emoji": "๐Ÿง—โ€โ™€๏ธ", - "name": "woman climbing" - }, - { - "emoji": "๐Ÿคบ", - "name": "person fencing" - }, - { - "emoji": "๐Ÿ‡", - "name": "horse racing" - }, - { - "emoji": "โ›ท๏ธ", - "name": "skier" - }, - { - "emoji": "๐Ÿ‚", - "name": "snowboarder" - }, - { - "emoji": "๐ŸŒ๏ธ", - "name": "person golfing" - }, - { - "emoji": "๐ŸŒ๏ธโ€โ™‚๏ธ", - "name": "man golfing" - }, - { - "emoji": "๐ŸŒ๏ธโ€โ™€๏ธ", - "name": "woman golfing" - }, - { - "emoji": "๐Ÿ„", - "name": "person surfing" - }, - { - "emoji": "๐Ÿ„โ€โ™‚๏ธ", - "name": "man surfing" - }, - { - "emoji": "๐Ÿ„โ€โ™€๏ธ", - "name": "woman surfing" - }, - { - "emoji": "๐Ÿšฃ", - "name": "person rowing boat" - }, - { - "emoji": "๐Ÿšฃโ€โ™‚๏ธ", - "name": "man rowing boat" - }, - { - "emoji": "๐Ÿšฃโ€โ™€๏ธ", - "name": "woman rowing boat" - }, - { - "emoji": "๐ŸŠ", - "name": "person swimming" - }, - { - "emoji": "๐ŸŠโ€โ™‚๏ธ", - "name": "man swimming" - }, - { - "emoji": "๐ŸŠโ€โ™€๏ธ", - "name": "woman swimming" - }, - { - "emoji": "โ›น๏ธ", - "name": "person bouncing ball" - }, - { - "emoji": "โ›น๏ธโ€โ™‚๏ธ", - "name": "man bouncing ball" - }, - { - "emoji": "โ›น๏ธโ€โ™€๏ธ", - "name": "woman bouncing ball" - }, - { - "emoji": "๐Ÿ‹๏ธ", - "name": "person lifting weights" - }, - { - "emoji": "๐Ÿ‹๏ธโ€โ™‚๏ธ", - "name": "man lifting weights" - }, - { - "emoji": "๐Ÿ‹๏ธโ€โ™€๏ธ", - "name": "woman lifting weights" - }, - { - "emoji": "๐Ÿšด", - "name": "person biking" - }, - { - "emoji": "๐Ÿšดโ€โ™‚๏ธ", - "name": "man biking" - }, - { - "emoji": "๐Ÿšดโ€โ™€๏ธ", - "name": "woman biking" - }, - { - "emoji": "๐Ÿšต", - "name": "person mountain biking" - }, - { - "emoji": "๐Ÿšตโ€โ™‚๏ธ", - "name": "man mountain biking" - }, - { - "emoji": "๐Ÿšตโ€โ™€๏ธ", - "name": "woman mountain biking" - }, - { - "emoji": "๐Ÿคธ", - "name": "person cartwheeling" - }, - { - "emoji": "๐Ÿคธโ€โ™‚๏ธ", - "name": "man cartwheeling" - }, - { - "emoji": "๐Ÿคธโ€โ™€๏ธ", - "name": "woman cartwheeling" - }, - { - "emoji": "๐Ÿคผ", - "name": "people wrestling" - }, - { - "emoji": "๐Ÿคผโ€โ™‚๏ธ", - "name": "men wrestling" - }, - { - "emoji": "๐Ÿคผโ€โ™€๏ธ", - "name": "women wrestling" - }, - { - "emoji": "๐Ÿคฝ", - "name": "person playing water polo" - }, - { - "emoji": "๐Ÿคฝโ€โ™‚๏ธ", - "name": "man playing water polo" - }, - { - "emoji": "๐Ÿคฝโ€โ™€๏ธ", - "name": "woman playing water polo" - }, - { - "emoji": "๐Ÿคพ", - "name": "person playing handball" - }, - { - "emoji": "๐Ÿคพโ€โ™‚๏ธ", - "name": "man playing handball" - }, - { - "emoji": "๐Ÿคพโ€โ™€๏ธ", - "name": "woman playing handball" - }, - { - "emoji": "๐Ÿคน", - "name": "person juggling" - }, - { - "emoji": "๐Ÿคนโ€โ™‚๏ธ", - "name": "man juggling" - }, - { - "emoji": "๐Ÿคนโ€โ™€๏ธ", - "name": "woman juggling" - }, - { - "emoji": "๐Ÿง˜", - "name": "person in lotus position" - }, - { - "emoji": "๐Ÿง˜โ€โ™‚๏ธ", - "name": "man in lotus position" - }, - { - "emoji": "๐Ÿง˜โ€โ™€๏ธ", - "name": "woman in lotus position" - }, - { - "emoji": "๐Ÿ›€", - "name": "person taking bath" - }, - { - "emoji": "๐Ÿ›Œ", - "name": "person in bed" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘", - "name": "people holding hands" - }, - { - "emoji": "๐Ÿ‘ญ", - "name": "women holding hands" - }, - { - "emoji": "๐Ÿ‘ซ", - "name": "woman and man holding hands" - }, - { - "emoji": "๐Ÿ‘ฌ", - "name": "men holding hands" - }, - { - "emoji": "๐Ÿ’", - "name": "kiss" - }, - { - "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", - "name": "kiss woman, man" - }, - { - "emoji": "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", - "name": "kiss man, man" - }, - { - "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ", - "name": "kiss woman, woman" - }, - { - "emoji": "๐Ÿ’‘", - "name": "couple with heart" - }, - { - "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ", - "name": "couple with heart woman, man" - }, - { - "emoji": "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ", - "name": "couple with heart man, man" - }, - { - "emoji": "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ", - "name": "couple with heart woman, woman" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", - "name": "family man, woman, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", - "name": "family man, woman, girl" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "name": "family man, woman, girl, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "name": "family man, woman, boy, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "name": "family man, woman, girl, girl" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ", - "name": "family man, man, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง", - "name": "family man, man, girl" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "name": "family man, man, girl, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "name": "family man, man, boy, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "name": "family man, man, girl, girl" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", - "name": "family woman, woman, boy" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", - "name": "family woman, woman, girl" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "name": "family woman, woman, girl, boy" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "name": "family woman, woman, boy, boy" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "name": "family woman, woman, girl, girl" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฆ", - "name": "family man, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "name": "family man, boy, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘ง", - "name": "family man, girl" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "name": "family man, girl, boy" - }, - { - "emoji": "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "name": "family man, girl, girl" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฆ", - "name": "family woman, boy" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", - "name": "family woman, boy, boy" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘ง", - "name": "family woman, girl" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", - "name": "family woman, girl, boy" - }, - { - "emoji": "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", - "name": "family woman, girl, girl" - }, - { - "emoji": "๐Ÿ—ฃ๏ธ", - "name": "speaking head" - }, - { - "emoji": "๐Ÿ‘ค", - "name": "bust in silhouette" - }, - { - "emoji": "๐Ÿ‘ฅ", - "name": "busts in silhouette" - }, - { - "emoji": "๐Ÿซ‚", - "name": "people hugging" - }, - { - "emoji": "๐Ÿ‘ช", - "name": "family" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’", - "name": "family adult, adult, child" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’", - "name": "family adult, adult, child, child" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿง’", - "name": "family adult, child" - }, - { - "emoji": "๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’", - "name": "family adult, child, child" - }, - { - "emoji": "๐Ÿ‘ฃ", - "name": "footprints" - } - ], - "Animals & Nature": [ - { - "emoji": "๐Ÿต", - "name": "monkey face" - }, - { - "emoji": "๐Ÿ’", - "name": "monkey" - }, - { - "emoji": "๐Ÿฆ", - "name": "gorilla" - }, - { - "emoji": "๐Ÿฆง", - "name": "orangutan" - }, - { - "emoji": "๐Ÿถ", - "name": "dog face" - }, - { - "emoji": "๐Ÿ•", - "name": "dog" - }, - { - "emoji": "๐Ÿฆฎ", - "name": "guide dog" - }, - { - "emoji": "๐Ÿ•โ€๐Ÿฆบ", - "name": "service dog" - }, - { - "emoji": "๐Ÿฉ", - "name": "poodle" - }, - { - "emoji": "๐Ÿบ", - "name": "wolf" - }, - { - "emoji": "๐ŸฆŠ", - "name": "fox" - }, - { - "emoji": "๐Ÿฆ", - "name": "raccoon" - }, - { - "emoji": "๐Ÿฑ", - "name": "cat face" - }, - { - "emoji": "๐Ÿˆ", - "name": "cat" - }, - { - "emoji": "๐Ÿˆโ€โฌ›", - "name": "black cat" - }, - { - "emoji": "๐Ÿฆ", - "name": "lion" - }, - { - "emoji": "๐Ÿฏ", - "name": "tiger face" - }, - { - "emoji": "๐Ÿ…", - "name": "tiger" - }, - { - "emoji": "๐Ÿ†", - "name": "leopard" - }, - { - "emoji": "๐Ÿด", - "name": "horse face" - }, - { - "emoji": "๐ŸซŽ", - "name": "moose" - }, - { - "emoji": "๐Ÿซ", - "name": "donkey" - }, - { - "emoji": "๐ŸŽ", - "name": "horse" - }, - { - "emoji": "๐Ÿฆ„", - "name": "unicorn" - }, - { - "emoji": "๐Ÿฆ“", - "name": "zebra" - }, - { - "emoji": "๐ŸฆŒ", - "name": "deer" - }, - { - "emoji": "๐Ÿฆฌ", - "name": "bison" - }, - { - "emoji": "๐Ÿฎ", - "name": "cow face" - }, - { - "emoji": "๐Ÿ‚", - "name": "ox" - }, - { - "emoji": "๐Ÿƒ", - "name": "water buffalo" - }, - { - "emoji": "๐Ÿ„", - "name": "cow" - }, - { - "emoji": "๐Ÿท", - "name": "pig face" - }, - { - "emoji": "๐Ÿ–", - "name": "pig" - }, - { - "emoji": "๐Ÿ—", - "name": "boar" - }, - { - "emoji": "๐Ÿฝ", - "name": "pig nose" - }, - { - "emoji": "๐Ÿ", - "name": "ram" - }, - { - "emoji": "๐Ÿ‘", - "name": "ewe" - }, - { - "emoji": "๐Ÿ", - "name": "goat" - }, - { - "emoji": "๐Ÿช", - "name": "camel" - }, - { - "emoji": "๐Ÿซ", - "name": "two-hump camel" - }, - { - "emoji": "๐Ÿฆ™", - "name": "llama" - }, - { - "emoji": "๐Ÿฆ’", - "name": "giraffe" - }, - { - "emoji": "๐Ÿ˜", - "name": "elephant" - }, - { - "emoji": "๐Ÿฆฃ", - "name": "mammoth" - }, - { - "emoji": "๐Ÿฆ", - "name": "rhinoceros" - }, - { - "emoji": "๐Ÿฆ›", - "name": "hippopotamus" - }, - { - "emoji": "๐Ÿญ", - "name": "mouse face" - }, - { - "emoji": "๐Ÿ", - "name": "mouse" - }, - { - "emoji": "๐Ÿ€", - "name": "rat" - }, - { - "emoji": "๐Ÿน", - "name": "hamster" - }, - { - "emoji": "๐Ÿฐ", - "name": "rabbit face" - }, - { - "emoji": "๐Ÿ‡", - "name": "rabbit" - }, - { - "emoji": "๐Ÿฟ๏ธ", - "name": "chipmunk" - }, - { - "emoji": "๐Ÿฆซ", - "name": "beaver" - }, - { - "emoji": "๐Ÿฆ”", - "name": "hedgehog" - }, - { - "emoji": "๐Ÿฆ‡", - "name": "bat" - }, - { - "emoji": "๐Ÿป", - "name": "bear" - }, - { - "emoji": "๐Ÿปโ€โ„๏ธ", - "name": "polar bear" - }, - { - "emoji": "๐Ÿจ", - "name": "koala" - }, - { - "emoji": "๐Ÿผ", - "name": "panda" - }, - { - "emoji": "๐Ÿฆฅ", - "name": "sloth" - }, - { - "emoji": "๐Ÿฆฆ", - "name": "otter" - }, - { - "emoji": "๐Ÿฆจ", - "name": "skunk" - }, - { - "emoji": "๐Ÿฆ˜", - "name": "kangaroo" - }, - { - "emoji": "๐Ÿฆก", - "name": "badger" - }, - { - "emoji": "๐Ÿพ", - "name": "paw prints" - }, - { - "emoji": "๐Ÿฆƒ", - "name": "turkey" - }, - { - "emoji": "๐Ÿ”", - "name": "chicken" - }, - { - "emoji": "๐Ÿ“", - "name": "rooster" - }, - { - "emoji": "๐Ÿฃ", - "name": "hatching chick" - }, - { - "emoji": "๐Ÿค", - "name": "baby chick" - }, - { - "emoji": "๐Ÿฅ", - "name": "front-facing baby chick" - }, - { - "emoji": "๐Ÿฆ", - "name": "bird" - }, - { - "emoji": "๐Ÿง", - "name": "penguin" - }, - { - "emoji": "๐Ÿ•Š๏ธ", - "name": "dove" - }, - { - "emoji": "๐Ÿฆ…", - "name": "eagle" - }, - { - "emoji": "๐Ÿฆ†", - "name": "duck" - }, - { - "emoji": "๐Ÿฆข", - "name": "swan" - }, - { - "emoji": "๐Ÿฆ‰", - "name": "owl" - }, - { - "emoji": "๐Ÿฆค", - "name": "dodo" - }, - { - "emoji": "๐Ÿชถ", - "name": "feather" - }, - { - "emoji": "๐Ÿฆฉ", - "name": "flamingo" - }, - { - "emoji": "๐Ÿฆš", - "name": "peacock" - }, - { - "emoji": "๐Ÿฆœ", - "name": "parrot" - }, - { - "emoji": "๐Ÿชฝ", - "name": "wing" - }, - { - "emoji": "๐Ÿฆโ€โฌ›", - "name": "black bird" - }, - { - "emoji": "๐Ÿชฟ", - "name": "goose" - }, - { - "emoji": "๐Ÿฆโ€๐Ÿ”ฅ", - "name": "phoenix" - }, - { - "emoji": "๐Ÿธ", - "name": "frog" - }, - { - "emoji": "๐ŸŠ", - "name": "crocodile" - }, - { - "emoji": "๐Ÿข", - "name": "turtle" - }, - { - "emoji": "๐ŸฆŽ", - "name": "lizard" - }, - { - "emoji": "๐Ÿ", - "name": "snake" - }, - { - "emoji": "๐Ÿฒ", - "name": "dragon face" - }, - { - "emoji": "๐Ÿ‰", - "name": "dragon" - }, - { - "emoji": "๐Ÿฆ•", - "name": "sauropod" - }, - { - "emoji": "๐Ÿฆ–", - "name": "T-Rex" - }, - { - "emoji": "๐Ÿณ", - "name": "spouting whale" - }, - { - "emoji": "๐Ÿ‹", - "name": "whale" - }, - { - "emoji": "๐Ÿฌ", - "name": "dolphin" - }, - { - "emoji": "๐Ÿฆญ", - "name": "seal" - }, - { - "emoji": "๐ŸŸ", - "name": "fish" - }, - { - "emoji": "๐Ÿ ", - "name": "tropical fish" - }, - { - "emoji": "๐Ÿก", - "name": "blowfish" - }, - { - "emoji": "๐Ÿฆˆ", - "name": "shark" - }, - { - "emoji": "๐Ÿ™", - "name": "octopus" - }, - { - "emoji": "๐Ÿš", - "name": "spiral shell" - }, - { - "emoji": "๐Ÿชธ", - "name": "coral" - }, - { - "emoji": "๐Ÿชผ", - "name": "jellyfish" - }, - { - "emoji": "๐ŸŒ", - "name": "snail" - }, - { - "emoji": "๐Ÿฆ‹", - "name": "butterfly" - }, - { - "emoji": "๐Ÿ›", - "name": "bug" - }, - { - "emoji": "๐Ÿœ", - "name": "ant" - }, - { - "emoji": "๐Ÿ", - "name": "honeybee" - }, - { - "emoji": "๐Ÿชฒ", - "name": "beetle" - }, - { - "emoji": "๐Ÿž", - "name": "lady beetle" - }, - { - "emoji": "๐Ÿฆ—", - "name": "cricket" - }, - { - "emoji": "๐Ÿชณ", - "name": "cockroach" - }, - { - "emoji": "๐Ÿ•ท๏ธ", - "name": "spider" - }, - { - "emoji": "๐Ÿ•ธ๏ธ", - "name": "spider web" - }, - { - "emoji": "๐Ÿฆ‚", - "name": "scorpion" - }, - { - "emoji": "๐ŸฆŸ", - "name": "mosquito" - }, - { - "emoji": "๐Ÿชฐ", - "name": "fly" - }, - { - "emoji": "๐Ÿชฑ", - "name": "worm" - }, - { - "emoji": "๐Ÿฆ ", - "name": "microbe" - }, - { - "emoji": "๐Ÿ’", - "name": "bouquet" - }, - { - "emoji": "๐ŸŒธ", - "name": "cherry blossom" - }, - { - "emoji": "๐Ÿ’ฎ", - "name": "white flower" - }, - { - "emoji": "๐Ÿชท", - "name": "lotus" - }, - { - "emoji": "๐Ÿต๏ธ", - "name": "rosette" - }, - { - "emoji": "๐ŸŒน", - "name": "rose" - }, - { - "emoji": "๐Ÿฅ€", - "name": "wilted flower" - }, - { - "emoji": "๐ŸŒบ", - "name": "hibiscus" - }, - { - "emoji": "๐ŸŒป", - "name": "sunflower" - }, - { - "emoji": "๐ŸŒผ", - "name": "blossom" - }, - { - "emoji": "๐ŸŒท", - "name": "tulip" - }, - { - "emoji": "๐Ÿชป", - "name": "hyacinth" - }, - { - "emoji": "๐ŸŒฑ", - "name": "seedling" - }, - { - "emoji": "๐Ÿชด", - "name": "potted plant" - }, - { - "emoji": "๐ŸŒฒ", - "name": "evergreen tree" - }, - { - "emoji": "๐ŸŒณ", - "name": "deciduous tree" - }, - { - "emoji": "๐ŸŒด", - "name": "palm tree" - }, - { - "emoji": "๐ŸŒต", - "name": "cactus" - }, - { - "emoji": "๐ŸŒพ", - "name": "sheaf of rice" - }, - { - "emoji": "๐ŸŒฟ", - "name": "herb" - }, - { - "emoji": "โ˜˜๏ธ", - "name": "shamrock" - }, - { - "emoji": "๐Ÿ€", - "name": "four leaf clover" - }, - { - "emoji": "๐Ÿ", - "name": "maple leaf" - }, - { - "emoji": "๐Ÿ‚", - "name": "fallen leaf" - }, - { - "emoji": "๐Ÿƒ", - "name": "leaf fluttering in wind" - }, - { - "emoji": "๐Ÿชน", - "name": "empty nest" - }, - { - "emoji": "๐Ÿชบ", - "name": "nest with eggs" - }, - { - "emoji": "๐Ÿ„", - "name": "mushroom" - } - ], - "Food & Drink": [ - { - "emoji": "๐Ÿ‡", - "name": "grapes" - }, - { - "emoji": "๐Ÿˆ", - "name": "melon" - }, - { - "emoji": "๐Ÿ‰", - "name": "watermelon" - }, - { - "emoji": "๐ŸŠ", - "name": "tangerine" - }, - { - "emoji": "๐Ÿ‹", - "name": "lemon" - }, - { - "emoji": "๐Ÿ‹โ€๐ŸŸฉ", - "name": "lime" - }, - { - "emoji": "๐ŸŒ", - "name": "banana" - }, - { - "emoji": "๐Ÿ", - "name": "pineapple" - }, - { - "emoji": "๐Ÿฅญ", - "name": "mango" - }, - { - "emoji": "๐ŸŽ", - "name": "red apple" - }, - { - "emoji": "๐Ÿ", - "name": "green apple" - }, - { - "emoji": "๐Ÿ", - "name": "pear" - }, - { - "emoji": "๐Ÿ‘", - "name": "peach" - }, - { - "emoji": "๐Ÿ’", - "name": "cherries" - }, - { - "emoji": "๐Ÿ“", - "name": "strawberry" - }, - { - "emoji": "๐Ÿซ", - "name": "blueberries" - }, - { - "emoji": "๐Ÿฅ", - "name": "kiwi fruit" - }, - { - "emoji": "๐Ÿ…", - "name": "tomato" - }, - { - "emoji": "๐Ÿซ’", - "name": "olive" - }, - { - "emoji": "๐Ÿฅฅ", - "name": "coconut" - }, - { - "emoji": "๐Ÿฅ‘", - "name": "avocado" - }, - { - "emoji": "๐Ÿ†", - "name": "eggplant" - }, - { - "emoji": "๐Ÿฅ”", - "name": "potato" - }, - { - "emoji": "๐Ÿฅ•", - "name": "carrot" - }, - { - "emoji": "๐ŸŒฝ", - "name": "ear of corn" - }, - { - "emoji": "๐ŸŒถ๏ธ", - "name": "hot pepper" - }, - { - "emoji": "๐Ÿซ‘", - "name": "bell pepper" - }, - { - "emoji": "๐Ÿฅ’", - "name": "cucumber" - }, - { - "emoji": "๐Ÿฅฌ", - "name": "leafy green" - }, - { - "emoji": "๐Ÿฅฆ", - "name": "broccoli" - }, - { - "emoji": "๐Ÿง„", - "name": "garlic" - }, - { - "emoji": "๐Ÿง…", - "name": "onion" - }, - { - "emoji": "๐Ÿฅœ", - "name": "peanuts" - }, - { - "emoji": "๐Ÿซ˜", - "name": "beans" - }, - { - "emoji": "๐ŸŒฐ", - "name": "chestnut" - }, - { - "emoji": "๐Ÿซš", - "name": "ginger root" - }, - { - "emoji": "๐Ÿซ›", - "name": "pea pod" - }, - { - "emoji": "๐Ÿ„โ€๐ŸŸซ", - "name": "brown mushroom" - }, - { - "emoji": "๐Ÿž", - "name": "bread" - }, - { - "emoji": "๐Ÿฅ", - "name": "croissant" - }, - { - "emoji": "๐Ÿฅ–", - "name": "baguette bread" - }, - { - "emoji": "๐Ÿซ“", - "name": "flatbread" - }, - { - "emoji": "๐Ÿฅจ", - "name": "pretzel" - }, - { - "emoji": "๐Ÿฅฏ", - "name": "bagel" - }, - { - "emoji": "๐Ÿฅž", - "name": "pancakes" - }, - { - "emoji": "๐Ÿง‡", - "name": "waffle" - }, - { - "emoji": "๐Ÿง€", - "name": "cheese wedge" - }, - { - "emoji": "๐Ÿ–", - "name": "meat on bone" - }, - { - "emoji": "๐Ÿ—", - "name": "poultry leg" - }, - { - "emoji": "๐Ÿฅฉ", - "name": "cut of meat" - }, - { - "emoji": "๐Ÿฅ“", - "name": "bacon" - }, - { - "emoji": "๐Ÿ”", - "name": "hamburger" - }, - { - "emoji": "๐ŸŸ", - "name": "french fries" - }, - { - "emoji": "๐Ÿ•", - "name": "pizza" - }, - { - "emoji": "๐ŸŒญ", - "name": "hot dog" - }, - { - "emoji": "๐Ÿฅช", - "name": "sandwich" - }, - { - "emoji": "๐ŸŒฎ", - "name": "taco" - }, - { - "emoji": "๐ŸŒฏ", - "name": "burrito" - }, - { - "emoji": "๐Ÿซ”", - "name": "tamale" - }, - { - "emoji": "๐Ÿฅ™", - "name": "stuffed flatbread" - }, - { - "emoji": "๐Ÿง†", - "name": "falafel" - }, - { - "emoji": "๐Ÿฅš", - "name": "egg" - }, - { - "emoji": "๐Ÿณ", - "name": "cooking" - }, - { - "emoji": "๐Ÿฅ˜", - "name": "shallow pan of food" - }, - { - "emoji": "๐Ÿฒ", - "name": "pot of food" - }, - { - "emoji": "๐Ÿซ•", - "name": "fondue" - }, - { - "emoji": "๐Ÿฅฃ", - "name": "bowl with spoon" - }, - { - "emoji": "๐Ÿฅ—", - "name": "green salad" - }, - { - "emoji": "๐Ÿฟ", - "name": "popcorn" - }, - { - "emoji": "๐Ÿงˆ", - "name": "butter" - }, - { - "emoji": "๐Ÿง‚", - "name": "salt" - }, - { - "emoji": "๐Ÿฅซ", - "name": "canned food" - }, - { - "emoji": "๐Ÿฑ", - "name": "bento box" - }, - { - "emoji": "๐Ÿ˜", - "name": "rice cracker" - }, - { - "emoji": "๐Ÿ™", - "name": "rice ball" - }, - { - "emoji": "๐Ÿš", - "name": "cooked rice" - }, - { - "emoji": "๐Ÿ›", - "name": "curry rice" - }, - { - "emoji": "๐Ÿœ", - "name": "steaming bowl" - }, - { - "emoji": "๐Ÿ", - "name": "spaghetti" - }, - { - "emoji": "๐Ÿ ", - "name": "roasted sweet potato" - }, - { - "emoji": "๐Ÿข", - "name": "oden" - }, - { - "emoji": "๐Ÿฃ", - "name": "sushi" - }, - { - "emoji": "๐Ÿค", - "name": "fried shrimp" - }, - { - "emoji": "๐Ÿฅ", - "name": "fish cake with swirl" - }, - { - "emoji": "๐Ÿฅฎ", - "name": "moon cake" - }, - { - "emoji": "๐Ÿก", - "name": "dango" - }, - { - "emoji": "๐ŸฅŸ", - "name": "dumpling" - }, - { - "emoji": "๐Ÿฅ ", - "name": "fortune cookie" - }, - { - "emoji": "๐Ÿฅก", - "name": "takeout box" - }, - { - "emoji": "๐Ÿฆ€", - "name": "crab" - }, - { - "emoji": "๐Ÿฆž", - "name": "lobster" - }, - { - "emoji": "๐Ÿฆ", - "name": "shrimp" - }, - { - "emoji": "๐Ÿฆ‘", - "name": "squid" - }, - { - "emoji": "๐Ÿฆช", - "name": "oyster" - }, - { - "emoji": "๐Ÿฆ", - "name": "soft ice cream" - }, - { - "emoji": "๐Ÿง", - "name": "shaved ice" - }, - { - "emoji": "๐Ÿจ", - "name": "ice cream" - }, - { - "emoji": "๐Ÿฉ", - "name": "doughnut" - }, - { - "emoji": "๐Ÿช", - "name": "cookie" - }, - { - "emoji": "๐ŸŽ‚", - "name": "birthday cake" - }, - { - "emoji": "๐Ÿฐ", - "name": "shortcake" - }, - { - "emoji": "๐Ÿง", - "name": "cupcake" - }, - { - "emoji": "๐Ÿฅง", - "name": "pie" - }, - { - "emoji": "๐Ÿซ", - "name": "chocolate bar" - }, - { - "emoji": "๐Ÿฌ", - "name": "candy" - }, - { - "emoji": "๐Ÿญ", - "name": "lollipop" - }, - { - "emoji": "๐Ÿฎ", - "name": "custard" - }, - { - "emoji": "๐Ÿฏ", - "name": "honey pot" - }, - { - "emoji": "๐Ÿผ", - "name": "baby bottle" - }, - { - "emoji": "๐Ÿฅ›", - "name": "glass of milk" - }, - { - "emoji": "โ˜•", - "name": "hot beverage" - }, - { - "emoji": "๐Ÿซ–", - "name": "teapot" - }, - { - "emoji": "๐Ÿต", - "name": "teacup without handle" - }, - { - "emoji": "๐Ÿถ", - "name": "sake" - }, - { - "emoji": "๐Ÿพ", - "name": "bottle with popping cork" - }, - { - "emoji": "๐Ÿท", - "name": "wine glass" - }, - { - "emoji": "๐Ÿธ", - "name": "cocktail glass" - }, - { - "emoji": "๐Ÿน", - "name": "tropical drink" - }, - { - "emoji": "๐Ÿบ", - "name": "beer mug" - }, - { - "emoji": "๐Ÿป", - "name": "clinking beer mugs" - }, - { - "emoji": "๐Ÿฅ‚", - "name": "clinking glasses" - }, - { - "emoji": "๐Ÿฅƒ", - "name": "tumbler glass" - }, - { - "emoji": "๐Ÿซ—", - "name": "pouring liquid" - }, - { - "emoji": "๐Ÿฅค", - "name": "cup with straw" - }, - { - "emoji": "๐Ÿง‹", - "name": "bubble tea" - }, - { - "emoji": "๐Ÿงƒ", - "name": "beverage box" - }, - { - "emoji": "๐Ÿง‰", - "name": "mate" - }, - { - "emoji": "๐ŸงŠ", - "name": "ice" - }, - { - "emoji": "๐Ÿฅข", - "name": "chopsticks" - }, - { - "emoji": "๐Ÿฝ๏ธ", - "name": "fork and knife with plate" - }, - { - "emoji": "๐Ÿด", - "name": "fork and knife" - }, - { - "emoji": "๐Ÿฅ„", - "name": "spoon" - }, - { - "emoji": "๐Ÿ”ช", - "name": "kitchen knife" - }, - { - "emoji": "๐Ÿซ™", - "name": "jar" - }, - { - "emoji": "๐Ÿบ", - "name": "amphora" - } - ], - "Travel & Places": [ - { - "emoji": "๐ŸŒ", - "name": "globe showing Europe-Africa" - }, - { - "emoji": "๐ŸŒŽ", - "name": "globe showing Americas" - }, - { - "emoji": "๐ŸŒ", - "name": "globe showing Asia-Australia" - }, - { - "emoji": "๐ŸŒ", - "name": "globe with meridians" - }, - { - "emoji": "๐Ÿ—บ๏ธ", - "name": "world map" - }, - { - "emoji": "๐Ÿ—พ", - "name": "map of Japan" - }, - { - "emoji": "๐Ÿงญ", - "name": "compass" - }, - { - "emoji": "๐Ÿ”๏ธ", - "name": "snow-capped mountain" - }, - { - "emoji": "โ›ฐ๏ธ", - "name": "mountain" - }, - { - "emoji": "๐ŸŒ‹", - "name": "volcano" - }, - { - "emoji": "๐Ÿ—ป", - "name": "mount fuji" - }, - { - "emoji": "๐Ÿ•๏ธ", - "name": "camping" - }, - { - "emoji": "๐Ÿ–๏ธ", - "name": "beach with umbrella" - }, - { - "emoji": "๐Ÿœ๏ธ", - "name": "desert" - }, - { - "emoji": "๐Ÿ๏ธ", - "name": "desert island" - }, - { - "emoji": "๐Ÿž๏ธ", - "name": "national park" - }, - { - "emoji": "๐ŸŸ๏ธ", - "name": "stadium" - }, - { - "emoji": "๐Ÿ›๏ธ", - "name": "classical building" - }, - { - "emoji": "๐Ÿ—๏ธ", - "name": "building construction" - }, - { - "emoji": "๐Ÿงฑ", - "name": "brick" - }, - { - "emoji": "๐Ÿชจ", - "name": "rock" - }, - { - "emoji": "๐Ÿชต", - "name": "wood" - }, - { - "emoji": "๐Ÿ›–", - "name": "hut" - }, - { - "emoji": "๐Ÿ˜๏ธ", - "name": "houses" - }, - { - "emoji": "๐Ÿš๏ธ", - "name": "derelict house" - }, - { - "emoji": "๐Ÿ ", - "name": "house" - }, - { - "emoji": "๐Ÿก", - "name": "house with garden" - }, - { - "emoji": "๐Ÿข", - "name": "office building" - }, - { - "emoji": "๐Ÿฃ", - "name": "Japanese post office" - }, - { - "emoji": "๐Ÿค", - "name": "post office" - }, - { - "emoji": "๐Ÿฅ", - "name": "hospital" - }, - { - "emoji": "๐Ÿฆ", - "name": "bank" - }, - { - "emoji": "๐Ÿจ", - "name": "hotel" - }, - { - "emoji": "๐Ÿฉ", - "name": "love hotel" - }, - { - "emoji": "๐Ÿช", - "name": "convenience store" - }, - { - "emoji": "๐Ÿซ", - "name": "school" - }, - { - "emoji": "๐Ÿฌ", - "name": "department store" - }, - { - "emoji": "๐Ÿญ", - "name": "factory" - }, - { - "emoji": "๐Ÿฏ", - "name": "Japanese castle" - }, - { - "emoji": "๐Ÿฐ", - "name": "castle" - }, - { - "emoji": "๐Ÿ’’", - "name": "wedding" - }, - { - "emoji": "๐Ÿ—ผ", - "name": "Tokyo tower" - }, - { - "emoji": "๐Ÿ—ฝ", - "name": "Statue of Liberty" - }, - { - "emoji": "โ›ช", - "name": "church" - }, - { - "emoji": "๐Ÿ•Œ", - "name": "mosque" - }, - { - "emoji": "๐Ÿ›•", - "name": "hindu temple" - }, - { - "emoji": "๐Ÿ•", - "name": "synagogue" - }, - { - "emoji": "โ›ฉ๏ธ", - "name": "shinto shrine" - }, - { - "emoji": "๐Ÿ•‹", - "name": "kaaba" - }, - { - "emoji": "โ›ฒ", - "name": "fountain" - }, - { - "emoji": "โ›บ", - "name": "tent" - }, - { - "emoji": "๐ŸŒ", - "name": "foggy" - }, - { - "emoji": "๐ŸŒƒ", - "name": "night with stars" - }, - { - "emoji": "๐Ÿ™๏ธ", - "name": "cityscape" - }, - { - "emoji": "๐ŸŒ„", - "name": "sunrise over mountains" - }, - { - "emoji": "๐ŸŒ…", - "name": "sunrise" - }, - { - "emoji": "๐ŸŒ†", - "name": "cityscape at dusk" - }, - { - "emoji": "๐ŸŒ‡", - "name": "sunset" - }, - { - "emoji": "๐ŸŒ‰", - "name": "bridge at night" - }, - { - "emoji": "โ™จ๏ธ", - "name": "hot springs" - }, - { - "emoji": "๐ŸŽ ", - "name": "carousel horse" - }, - { - "emoji": "๐Ÿ›", - "name": "playground slide" - }, - { - "emoji": "๐ŸŽก", - "name": "ferris wheel" - }, - { - "emoji": "๐ŸŽข", - "name": "roller coaster" - }, - { - "emoji": "๐Ÿ’ˆ", - "name": "barber pole" - }, - { - "emoji": "๐ŸŽช", - "name": "circus tent" - }, - { - "emoji": "๐Ÿš‚", - "name": "locomotive" - }, - { - "emoji": "๐Ÿšƒ", - "name": "railway car" - }, - { - "emoji": "๐Ÿš„", - "name": "high-speed train" - }, - { - "emoji": "๐Ÿš…", - "name": "bullet train" - }, - { - "emoji": "๐Ÿš†", - "name": "train" - }, - { - "emoji": "๐Ÿš‡", - "name": "metro" - }, - { - "emoji": "๐Ÿšˆ", - "name": "light rail" - }, - { - "emoji": "๐Ÿš‰", - "name": "station" - }, - { - "emoji": "๐ŸšŠ", - "name": "tram" - }, - { - "emoji": "๐Ÿš", - "name": "monorail" - }, - { - "emoji": "๐Ÿšž", - "name": "mountain railway" - }, - { - "emoji": "๐Ÿš‹", - "name": "tram car" - }, - { - "emoji": "๐ŸšŒ", - "name": "bus" - }, - { - "emoji": "๐Ÿš", - "name": "oncoming bus" - }, - { - "emoji": "๐ŸšŽ", - "name": "trolleybus" - }, - { - "emoji": "๐Ÿš", - "name": "minibus" - }, - { - "emoji": "๐Ÿš‘", - "name": "ambulance" - }, - { - "emoji": "๐Ÿš’", - "name": "fire engine" - }, - { - "emoji": "๐Ÿš“", - "name": "police car" - }, - { - "emoji": "๐Ÿš”", - "name": "oncoming police car" - }, - { - "emoji": "๐Ÿš•", - "name": "taxi" - }, - { - "emoji": "๐Ÿš–", - "name": "oncoming taxi" - }, - { - "emoji": "๐Ÿš—", - "name": "automobile" - }, - { - "emoji": "๐Ÿš˜", - "name": "oncoming automobile" - }, - { - "emoji": "๐Ÿš™", - "name": "sport utility vehicle" - }, - { - "emoji": "๐Ÿ›ป", - "name": "pickup truck" - }, - { - "emoji": "๐Ÿšš", - "name": "delivery truck" - }, - { - "emoji": "๐Ÿš›", - "name": "articulated lorry" - }, - { - "emoji": "๐Ÿšœ", - "name": "tractor" - }, - { - "emoji": "๐ŸŽ๏ธ", - "name": "racing car" - }, - { - "emoji": "๐Ÿ๏ธ", - "name": "motorcycle" - }, - { - "emoji": "๐Ÿ›ต", - "name": "motor scooter" - }, - { - "emoji": "๐Ÿฆฝ", - "name": "manual wheelchair" - }, - { - "emoji": "๐Ÿฆผ", - "name": "motorized wheelchair" - }, - { - "emoji": "๐Ÿ›บ", - "name": "auto rickshaw" - }, - { - "emoji": "๐Ÿšฒ", - "name": "bicycle" - }, - { - "emoji": "๐Ÿ›ด", - "name": "kick scooter" - }, - { - "emoji": "๐Ÿ›น", - "name": "skateboard" - }, - { - "emoji": "๐Ÿ›ผ", - "name": "roller skate" - }, - { - "emoji": "๐Ÿš", - "name": "bus stop" - }, - { - "emoji": "๐Ÿ›ฃ๏ธ", - "name": "motorway" - }, - { - "emoji": "๐Ÿ›ค๏ธ", - "name": "railway track" - }, - { - "emoji": "๐Ÿ›ข๏ธ", - "name": "oil drum" - }, - { - "emoji": "โ›ฝ", - "name": "fuel pump" - }, - { - "emoji": "๐Ÿ›ž", - "name": "wheel" - }, - { - "emoji": "๐Ÿšจ", - "name": "police car light" - }, - { - "emoji": "๐Ÿšฅ", - "name": "horizontal traffic light" - }, - { - "emoji": "๐Ÿšฆ", - "name": "vertical traffic light" - }, - { - "emoji": "๐Ÿ›‘", - "name": "stop sign" - }, - { - "emoji": "๐Ÿšง", - "name": "construction" - }, - { - "emoji": "โš“", - "name": "anchor" - }, - { - "emoji": "๐Ÿ›Ÿ", - "name": "ring buoy" - }, - { - "emoji": "โ›ต", - "name": "sailboat" - }, - { - "emoji": "๐Ÿ›ถ", - "name": "canoe" - }, - { - "emoji": "๐Ÿšค", - "name": "speedboat" - }, - { - "emoji": "๐Ÿ›ณ๏ธ", - "name": "passenger ship" - }, - { - "emoji": "โ›ด๏ธ", - "name": "ferry" - }, - { - "emoji": "๐Ÿ›ฅ๏ธ", - "name": "motor boat" - }, - { - "emoji": "๐Ÿšข", - "name": "ship" - }, - { - "emoji": "โœˆ๏ธ", - "name": "airplane" - }, - { - "emoji": "๐Ÿ›ฉ๏ธ", - "name": "small airplane" - }, - { - "emoji": "๐Ÿ›ซ", - "name": "airplane departure" - }, - { - "emoji": "๐Ÿ›ฌ", - "name": "airplane arrival" - }, - { - "emoji": "๐Ÿช‚", - "name": "parachute" - }, - { - "emoji": "๐Ÿ’บ", - "name": "seat" - }, - { - "emoji": "๐Ÿš", - "name": "helicopter" - }, - { - "emoji": "๐ŸšŸ", - "name": "suspension railway" - }, - { - "emoji": "๐Ÿš ", - "name": "mountain cableway" - }, - { - "emoji": "๐Ÿšก", - "name": "aerial tramway" - }, - { - "emoji": "๐Ÿ›ฐ๏ธ", - "name": "satellite" - }, - { - "emoji": "๐Ÿš€", - "name": "rocket" - }, - { - "emoji": "๐Ÿ›ธ", - "name": "flying saucer" - }, - { - "emoji": "๐Ÿ›Ž๏ธ", - "name": "bellhop bell" - }, - { - "emoji": "๐Ÿงณ", - "name": "luggage" - }, - { - "emoji": "โŒ›", - "name": "hourglass done" - }, - { - "emoji": "โณ", - "name": "hourglass not done" - }, - { - "emoji": "โŒš", - "name": "watch" - }, - { - "emoji": "โฐ", - "name": "alarm clock" - }, - { - "emoji": "โฑ๏ธ", - "name": "stopwatch" - }, - { - "emoji": "โฒ๏ธ", - "name": "timer clock" - }, - { - "emoji": "๐Ÿ•ฐ๏ธ", - "name": "mantelpiece clock" - }, - { - "emoji": "๐Ÿ•›", - "name": "twelve oโ€™clock" - }, - { - "emoji": "๐Ÿ•ง", - "name": "twelve-thirty" - }, - { - "emoji": "๐Ÿ•", - "name": "one oโ€™clock" - }, - { - "emoji": "๐Ÿ•œ", - "name": "one-thirty" - }, - { - "emoji": "๐Ÿ•‘", - "name": "two oโ€™clock" - }, - { - "emoji": "๐Ÿ•", - "name": "two-thirty" - }, - { - "emoji": "๐Ÿ•’", - "name": "three oโ€™clock" - }, - { - "emoji": "๐Ÿ•ž", - "name": "three-thirty" - }, - { - "emoji": "๐Ÿ•“", - "name": "four oโ€™clock" - }, - { - "emoji": "๐Ÿ•Ÿ", - "name": "four-thirty" - }, - { - "emoji": "๐Ÿ•”", - "name": "five oโ€™clock" - }, - { - "emoji": "๐Ÿ• ", - "name": "five-thirty" - }, - { - "emoji": "๐Ÿ••", - "name": "six oโ€™clock" - }, - { - "emoji": "๐Ÿ•ก", - "name": "six-thirty" - }, - { - "emoji": "๐Ÿ•–", - "name": "seven oโ€™clock" - }, - { - "emoji": "๐Ÿ•ข", - "name": "seven-thirty" - }, - { - "emoji": "๐Ÿ•—", - "name": "eight oโ€™clock" - }, - { - "emoji": "๐Ÿ•ฃ", - "name": "eight-thirty" - }, - { - "emoji": "๐Ÿ•˜", - "name": "nine oโ€™clock" - }, - { - "emoji": "๐Ÿ•ค", - "name": "nine-thirty" - }, - { - "emoji": "๐Ÿ•™", - "name": "ten oโ€™clock" - }, - { - "emoji": "๐Ÿ•ฅ", - "name": "ten-thirty" - }, - { - "emoji": "๐Ÿ•š", - "name": "eleven oโ€™clock" - }, - { - "emoji": "๐Ÿ•ฆ", - "name": "eleven-thirty" - }, - { - "emoji": "๐ŸŒ‘", - "name": "new moon" - }, - { - "emoji": "๐ŸŒ’", - "name": "waxing crescent moon" - }, - { - "emoji": "๐ŸŒ“", - "name": "first quarter moon" - }, - { - "emoji": "๐ŸŒ”", - "name": "waxing gibbous moon" - }, - { - "emoji": "๐ŸŒ•", - "name": "full moon" - }, - { - "emoji": "๐ŸŒ–", - "name": "waning gibbous moon" - }, - { - "emoji": "๐ŸŒ—", - "name": "last quarter moon" - }, - { - "emoji": "๐ŸŒ˜", - "name": "waning crescent moon" - }, - { - "emoji": "๐ŸŒ™", - "name": "crescent moon" - }, - { - "emoji": "๐ŸŒš", - "name": "new moon face" - }, - { - "emoji": "๐ŸŒ›", - "name": "first quarter moon face" - }, - { - "emoji": "๐ŸŒœ", - "name": "last quarter moon face" - }, - { - "emoji": "๐ŸŒก๏ธ", - "name": "thermometer" - }, - { - "emoji": "โ˜€๏ธ", - "name": "sun" - }, - { - "emoji": "๐ŸŒ", - "name": "full moon face" - }, - { - "emoji": "๐ŸŒž", - "name": "sun with face" - }, - { - "emoji": "๐Ÿช", - "name": "ringed planet" - }, - { - "emoji": "โญ", - "name": "star" - }, - { - "emoji": "๐ŸŒŸ", - "name": "glowing star" - }, - { - "emoji": "๐ŸŒ ", - "name": "shooting star" - }, - { - "emoji": "๐ŸŒŒ", - "name": "milky way" - }, - { - "emoji": "โ˜๏ธ", - "name": "cloud" - }, - { - "emoji": "โ›…", - "name": "sun behind cloud" - }, - { - "emoji": "โ›ˆ๏ธ", - "name": "cloud with lightning and rain" - }, - { - "emoji": "๐ŸŒค๏ธ", - "name": "sun behind small cloud" - }, - { - "emoji": "๐ŸŒฅ๏ธ", - "name": "sun behind large cloud" - }, - { - "emoji": "๐ŸŒฆ๏ธ", - "name": "sun behind rain cloud" - }, - { - "emoji": "๐ŸŒง๏ธ", - "name": "cloud with rain" - }, - { - "emoji": "๐ŸŒจ๏ธ", - "name": "cloud with snow" - }, - { - "emoji": "๐ŸŒฉ๏ธ", - "name": "cloud with lightning" - }, - { - "emoji": "๐ŸŒช๏ธ", - "name": "tornado" - }, - { - "emoji": "๐ŸŒซ๏ธ", - "name": "fog" - }, - { - "emoji": "๐ŸŒฌ๏ธ", - "name": "wind face" - }, - { - "emoji": "๐ŸŒ€", - "name": "cyclone" - }, - { - "emoji": "๐ŸŒˆ", - "name": "rainbow" - }, - { - "emoji": "๐ŸŒ‚", - "name": "closed umbrella" - }, - { - "emoji": "โ˜‚๏ธ", - "name": "umbrella" - }, - { - "emoji": "โ˜”", - "name": "umbrella with rain drops" - }, - { - "emoji": "โ›ฑ๏ธ", - "name": "umbrella on ground" - }, - { - "emoji": "โšก", - "name": "high voltage" - }, - { - "emoji": "โ„๏ธ", - "name": "snowflake" - }, - { - "emoji": "โ˜ƒ๏ธ", - "name": "snowman" - }, - { - "emoji": "โ›„", - "name": "snowman without snow" - }, - { - "emoji": "โ˜„๏ธ", - "name": "comet" - }, - { - "emoji": "๐Ÿ”ฅ", - "name": "fire" - }, - { - "emoji": "๐Ÿ’ง", - "name": "droplet" - }, - { - "emoji": "๐ŸŒŠ", - "name": "water wave" - } - ], - "Activities": [ - { - "emoji": "๐ŸŽƒ", - "name": "jack-o-lantern" - }, - { - "emoji": "๐ŸŽ„", - "name": "Christmas tree" - }, - { - "emoji": "๐ŸŽ†", - "name": "fireworks" - }, - { - "emoji": "๐ŸŽ‡", - "name": "sparkler" - }, - { - "emoji": "๐Ÿงจ", - "name": "firecracker" - }, - { - "emoji": "โœจ", - "name": "sparkles" - }, - { - "emoji": "๐ŸŽˆ", - "name": "balloon" - }, - { - "emoji": "๐ŸŽ‰", - "name": "party popper" - }, - { - "emoji": "๐ŸŽŠ", - "name": "confetti ball" - }, - { - "emoji": "๐ŸŽ‹", - "name": "tanabata tree" - }, - { - "emoji": "๐ŸŽ", - "name": "pine decoration" - }, - { - "emoji": "๐ŸŽŽ", - "name": "Japanese dolls" - }, - { - "emoji": "๐ŸŽ", - "name": "carp streamer" - }, - { - "emoji": "๐ŸŽ", - "name": "wind chime" - }, - { - "emoji": "๐ŸŽ‘", - "name": "moon viewing ceremony" - }, - { - "emoji": "๐Ÿงง", - "name": "red envelope" - }, - { - "emoji": "๐ŸŽ€", - "name": "ribbon" - }, - { - "emoji": "๐ŸŽ", - "name": "wrapped gift" - }, - { - "emoji": "๐ŸŽ—๏ธ", - "name": "reminder ribbon" - }, - { - "emoji": "๐ŸŽŸ๏ธ", - "name": "admission tickets" - }, - { - "emoji": "๐ŸŽซ", - "name": "ticket" - }, - { - "emoji": "๐ŸŽ–๏ธ", - "name": "military medal" - }, - { - "emoji": "๐Ÿ†", - "name": "trophy" - }, - { - "emoji": "๐Ÿ…", - "name": "sports medal" - }, - { - "emoji": "๐Ÿฅ‡", - "name": "1st place medal" - }, - { - "emoji": "๐Ÿฅˆ", - "name": "2nd place medal" - }, - { - "emoji": "๐Ÿฅ‰", - "name": "3rd place medal" - }, - { - "emoji": "โšฝ", - "name": "soccer ball" - }, - { - "emoji": "โšพ", - "name": "baseball" - }, - { - "emoji": "๐ŸฅŽ", - "name": "softball" - }, - { - "emoji": "๐Ÿ€", - "name": "basketball" - }, - { - "emoji": "๐Ÿ", - "name": "volleyball" - }, - { - "emoji": "๐Ÿˆ", - "name": "american football" - }, - { - "emoji": "๐Ÿ‰", - "name": "rugby football" - }, - { - "emoji": "๐ŸŽพ", - "name": "tennis" - }, - { - "emoji": "๐Ÿฅ", - "name": "flying disc" - }, - { - "emoji": "๐ŸŽณ", - "name": "bowling" - }, - { - "emoji": "๐Ÿ", - "name": "cricket game" - }, - { - "emoji": "๐Ÿ‘", - "name": "field hockey" - }, - { - "emoji": "๐Ÿ’", - "name": "ice hockey" - }, - { - "emoji": "๐Ÿฅ", - "name": "lacrosse" - }, - { - "emoji": "๐Ÿ“", - "name": "ping pong" - }, - { - "emoji": "๐Ÿธ", - "name": "badminton" - }, - { - "emoji": "๐ŸฅŠ", - "name": "boxing glove" - }, - { - "emoji": "๐Ÿฅ‹", - "name": "martial arts uniform" - }, - { - "emoji": "๐Ÿฅ…", - "name": "goal net" - }, - { - "emoji": "โ›ณ", - "name": "flag in hole" - }, - { - "emoji": "โ›ธ๏ธ", - "name": "ice skate" - }, - { - "emoji": "๐ŸŽฃ", - "name": "fishing pole" - }, - { - "emoji": "๐Ÿคฟ", - "name": "diving mask" - }, - { - "emoji": "๐ŸŽฝ", - "name": "running shirt" - }, - { - "emoji": "๐ŸŽฟ", - "name": "skis" - }, - { - "emoji": "๐Ÿ›ท", - "name": "sled" - }, - { - "emoji": "๐ŸฅŒ", - "name": "curling stone" - }, - { - "emoji": "๐ŸŽฏ", - "name": "bullseye" - }, - { - "emoji": "๐Ÿช€", - "name": "yo-yo" - }, - { - "emoji": "๐Ÿช", - "name": "kite" - }, - { - "emoji": "๐Ÿ”ซ", - "name": "water pistol" - }, - { - "emoji": "๐ŸŽฑ", - "name": "pool 8 ball" - }, - { - "emoji": "๐Ÿ”ฎ", - "name": "crystal ball" - }, - { - "emoji": "๐Ÿช„", - "name": "magic wand" - }, - { - "emoji": "๐ŸŽฎ", - "name": "video game" - }, - { - "emoji": "๐Ÿ•น๏ธ", - "name": "joystick" - }, - { - "emoji": "๐ŸŽฐ", - "name": "slot machine" - }, - { - "emoji": "๐ŸŽฒ", - "name": "game die" - }, - { - "emoji": "๐Ÿงฉ", - "name": "puzzle piece" - }, - { - "emoji": "๐Ÿงธ", - "name": "teddy bear" - }, - { - "emoji": "๐Ÿช…", - "name": "piรฑata" - }, - { - "emoji": "๐Ÿชฉ", - "name": "mirror ball" - }, - { - "emoji": "๐Ÿช†", - "name": "nesting dolls" - }, - { - "emoji": "โ™ ๏ธ", - "name": "spade suit" - }, - { - "emoji": "โ™ฅ๏ธ", - "name": "heart suit" - }, - { - "emoji": "โ™ฆ๏ธ", - "name": "diamond suit" - }, - { - "emoji": "โ™ฃ๏ธ", - "name": "club suit" - }, - { - "emoji": "โ™Ÿ๏ธ", - "name": "chess pawn" - }, - { - "emoji": "๐Ÿƒ", - "name": "joker" - }, - { - "emoji": "๐Ÿ€„", - "name": "mahjong red dragon" - }, - { - "emoji": "๐ŸŽด", - "name": "flower playing cards" - }, - { - "emoji": "๐ŸŽญ", - "name": "performing arts" - }, - { - "emoji": "๐Ÿ–ผ๏ธ", - "name": "framed picture" - }, - { - "emoji": "๐ŸŽจ", - "name": "artist palette" - }, - { - "emoji": "๐Ÿงต", - "name": "thread" - }, - { - "emoji": "๐Ÿชก", - "name": "sewing needle" - }, - { - "emoji": "๐Ÿงถ", - "name": "yarn" - }, - { - "emoji": "๐Ÿชข", - "name": "knot" - } - ], - "Objects": [ - { - "emoji": "๐Ÿ‘“", - "name": "glasses" - }, - { - "emoji": "๐Ÿ•ถ๏ธ", - "name": "sunglasses" - }, - { - "emoji": "๐Ÿฅฝ", - "name": "goggles" - }, - { - "emoji": "๐Ÿฅผ", - "name": "lab coat" - }, - { - "emoji": "๐Ÿฆบ", - "name": "safety vest" - }, - { - "emoji": "๐Ÿ‘”", - "name": "necktie" - }, - { - "emoji": "๐Ÿ‘•", - "name": "t-shirt" - }, - { - "emoji": "๐Ÿ‘–", - "name": "jeans" - }, - { - "emoji": "๐Ÿงฃ", - "name": "scarf" - }, - { - "emoji": "๐Ÿงค", - "name": "gloves" - }, - { - "emoji": "๐Ÿงฅ", - "name": "coat" - }, - { - "emoji": "๐Ÿงฆ", - "name": "socks" - }, - { - "emoji": "๐Ÿ‘—", - "name": "dress" - }, - { - "emoji": "๐Ÿ‘˜", - "name": "kimono" - }, - { - "emoji": "๐Ÿฅป", - "name": "sari" - }, - { - "emoji": "๐Ÿฉฑ", - "name": "one-piece swimsuit" - }, - { - "emoji": "๐Ÿฉฒ", - "name": "briefs" - }, - { - "emoji": "๐Ÿฉณ", - "name": "shorts" - }, - { - "emoji": "๐Ÿ‘™", - "name": "bikini" - }, - { - "emoji": "๐Ÿ‘š", - "name": "womanโ€™s clothes" - }, - { - "emoji": "๐Ÿชญ", - "name": "folding hand fan" - }, - { - "emoji": "๐Ÿ‘›", - "name": "purse" - }, - { - "emoji": "๐Ÿ‘œ", - "name": "handbag" - }, - { - "emoji": "๐Ÿ‘", - "name": "clutch bag" - }, - { - "emoji": "๐Ÿ›๏ธ", - "name": "shopping bags" - }, - { - "emoji": "๐ŸŽ’", - "name": "backpack" - }, - { - "emoji": "๐Ÿฉด", - "name": "thong sandal" - }, - { - "emoji": "๐Ÿ‘ž", - "name": "manโ€™s shoe" - }, - { - "emoji": "๐Ÿ‘Ÿ", - "name": "running shoe" - }, - { - "emoji": "๐Ÿฅพ", - "name": "hiking boot" - }, - { - "emoji": "๐Ÿฅฟ", - "name": "flat shoe" - }, - { - "emoji": "๐Ÿ‘ ", - "name": "high-heeled shoe" - }, - { - "emoji": "๐Ÿ‘ก", - "name": "womanโ€™s sandal" - }, - { - "emoji": "๐Ÿฉฐ", - "name": "ballet shoes" - }, - { - "emoji": "๐Ÿ‘ข", - "name": "womanโ€™s boot" - }, - { - "emoji": "๐Ÿชฎ", - "name": "hair pick" - }, - { - "emoji": "๐Ÿ‘‘", - "name": "crown" - }, - { - "emoji": "๐Ÿ‘’", - "name": "womanโ€™s hat" - }, - { - "emoji": "๐ŸŽฉ", - "name": "top hat" - }, - { - "emoji": "๐ŸŽ“", - "name": "graduation cap" - }, - { - "emoji": "๐Ÿงข", - "name": "billed cap" - }, - { - "emoji": "๐Ÿช–", - "name": "military helmet" - }, - { - "emoji": "โ›‘๏ธ", - "name": "rescue workerโ€™s helmet" - }, - { - "emoji": "๐Ÿ“ฟ", - "name": "prayer beads" - }, - { - "emoji": "๐Ÿ’„", - "name": "lipstick" - }, - { - "emoji": "๐Ÿ’", - "name": "ring" - }, - { - "emoji": "๐Ÿ’Ž", - "name": "gem stone" - }, - { - "emoji": "๐Ÿ”‡", - "name": "muted speaker" - }, - { - "emoji": "๐Ÿ”ˆ", - "name": "speaker low volume" - }, - { - "emoji": "๐Ÿ”‰", - "name": "speaker medium volume" - }, - { - "emoji": "๐Ÿ”Š", - "name": "speaker high volume" - }, - { - "emoji": "๐Ÿ“ข", - "name": "loudspeaker" - }, - { - "emoji": "๐Ÿ“ฃ", - "name": "megaphone" - }, - { - "emoji": "๐Ÿ“ฏ", - "name": "postal horn" - }, - { - "emoji": "๐Ÿ””", - "name": "bell" - }, - { - "emoji": "๐Ÿ”•", - "name": "bell with slash" - }, - { - "emoji": "๐ŸŽผ", - "name": "musical score" - }, - { - "emoji": "๐ŸŽต", - "name": "musical note" - }, - { - "emoji": "๐ŸŽถ", - "name": "musical notes" - }, - { - "emoji": "๐ŸŽ™๏ธ", - "name": "studio microphone" - }, - { - "emoji": "๐ŸŽš๏ธ", - "name": "level slider" - }, - { - "emoji": "๐ŸŽ›๏ธ", - "name": "control knobs" - }, - { - "emoji": "๐ŸŽค", - "name": "microphone" - }, - { - "emoji": "๐ŸŽง", - "name": "headphone" - }, - { - "emoji": "๐Ÿ“ป", - "name": "radio" - }, - { - "emoji": "๐ŸŽท", - "name": "saxophone" - }, - { - "emoji": "๐Ÿช—", - "name": "accordion" - }, - { - "emoji": "๐ŸŽธ", - "name": "guitar" - }, - { - "emoji": "๐ŸŽน", - "name": "musical keyboard" - }, - { - "emoji": "๐ŸŽบ", - "name": "trumpet" - }, - { - "emoji": "๐ŸŽป", - "name": "violin" - }, - { - "emoji": "๐Ÿช•", - "name": "banjo" - }, - { - "emoji": "๐Ÿฅ", - "name": "drum" - }, - { - "emoji": "๐Ÿช˜", - "name": "long drum" - }, - { - "emoji": "๐Ÿช‡", - "name": "maracas" - }, - { - "emoji": "๐Ÿชˆ", - "name": "flute" - }, - { - "emoji": "๐Ÿ“ฑ", - "name": "mobile phone" - }, - { - "emoji": "๐Ÿ“ฒ", - "name": "mobile phone with arrow" - }, - { - "emoji": "โ˜Ž๏ธ", - "name": "telephone" - }, - { - "emoji": "๐Ÿ“ž", - "name": "telephone receiver" - }, - { - "emoji": "๐Ÿ“Ÿ", - "name": "pager" - }, - { - "emoji": "๐Ÿ“ ", - "name": "fax machine" - }, - { - "emoji": "๐Ÿ”‹", - "name": "battery" - }, - { - "emoji": "๐Ÿชซ", - "name": "low battery" - }, - { - "emoji": "๐Ÿ”Œ", - "name": "electric plug" - }, - { - "emoji": "๐Ÿ’ป", - "name": "laptop" - }, - { - "emoji": "๐Ÿ–ฅ๏ธ", - "name": "desktop computer" - }, - { - "emoji": "๐Ÿ–จ๏ธ", - "name": "printer" - }, - { - "emoji": "โŒจ๏ธ", - "name": "keyboard" - }, - { - "emoji": "๐Ÿ–ฑ๏ธ", - "name": "computer mouse" - }, - { - "emoji": "๐Ÿ–ฒ๏ธ", - "name": "trackball" - }, - { - "emoji": "๐Ÿ’ฝ", - "name": "computer disk" - }, - { - "emoji": "๐Ÿ’พ", - "name": "floppy disk" - }, - { - "emoji": "๐Ÿ’ฟ", - "name": "optical disk" - }, - { - "emoji": "๐Ÿ“€", - "name": "dvd" - }, - { - "emoji": "๐Ÿงฎ", - "name": "abacus" - }, - { - "emoji": "๐ŸŽฅ", - "name": "movie camera" - }, - { - "emoji": "๐ŸŽž๏ธ", - "name": "film frames" - }, - { - "emoji": "๐Ÿ“ฝ๏ธ", - "name": "film projector" - }, - { - "emoji": "๐ŸŽฌ", - "name": "clapper board" - }, - { - "emoji": "๐Ÿ“บ", - "name": "television" - }, - { - "emoji": "๐Ÿ“ท", - "name": "camera" - }, - { - "emoji": "๐Ÿ“ธ", - "name": "camera with flash" - }, - { - "emoji": "๐Ÿ“น", - "name": "video camera" - }, - { - "emoji": "๐Ÿ“ผ", - "name": "videocassette" - }, - { - "emoji": "๐Ÿ”", - "name": "magnifying glass tilted left" - }, - { - "emoji": "๐Ÿ”Ž", - "name": "magnifying glass tilted right" - }, - { - "emoji": "๐Ÿ•ฏ๏ธ", - "name": "candle" - }, - { - "emoji": "๐Ÿ’ก", - "name": "light bulb" - }, - { - "emoji": "๐Ÿ”ฆ", - "name": "flashlight" - }, - { - "emoji": "๐Ÿฎ", - "name": "red paper lantern" - }, - { - "emoji": "๐Ÿช”", - "name": "diya lamp" - }, - { - "emoji": "๐Ÿ“”", - "name": "notebook with decorative cover" - }, - { - "emoji": "๐Ÿ“•", - "name": "closed book" - }, - { - "emoji": "๐Ÿ“–", - "name": "open book" - }, - { - "emoji": "๐Ÿ“—", - "name": "green book" - }, - { - "emoji": "๐Ÿ“˜", - "name": "blue book" - }, - { - "emoji": "๐Ÿ“™", - "name": "orange book" - }, - { - "emoji": "๐Ÿ“š", - "name": "books" - }, - { - "emoji": "๐Ÿ““", - "name": "notebook" - }, - { - "emoji": "๐Ÿ“’", - "name": "ledger" - }, - { - "emoji": "๐Ÿ“ƒ", - "name": "page with curl" - }, - { - "emoji": "๐Ÿ“œ", - "name": "scroll" - }, - { - "emoji": "๐Ÿ“„", - "name": "page facing up" - }, - { - "emoji": "๐Ÿ“ฐ", - "name": "newspaper" - }, - { - "emoji": "๐Ÿ—ž๏ธ", - "name": "rolled-up newspaper" - }, - { - "emoji": "๐Ÿ“‘", - "name": "bookmark tabs" - }, - { - "emoji": "๐Ÿ”–", - "name": "bookmark" - }, - { - "emoji": "๐Ÿท๏ธ", - "name": "label" - }, - { - "emoji": "๐Ÿ’ฐ", - "name": "money bag" - }, - { - "emoji": "๐Ÿช™", - "name": "coin" - }, - { - "emoji": "๐Ÿ’ด", - "name": "yen banknote" - }, - { - "emoji": "๐Ÿ’ต", - "name": "dollar banknote" - }, - { - "emoji": "๐Ÿ’ถ", - "name": "euro banknote" - }, - { - "emoji": "๐Ÿ’ท", - "name": "pound banknote" - }, - { - "emoji": "๐Ÿ’ธ", - "name": "money with wings" - }, - { - "emoji": "๐Ÿ’ณ", - "name": "credit card" - }, - { - "emoji": "๐Ÿงพ", - "name": "receipt" - }, - { - "emoji": "๐Ÿ’น", - "name": "chart increasing with yen" - }, - { - "emoji": "โœ‰๏ธ", - "name": "envelope" - }, - { - "emoji": "๐Ÿ“ง", - "name": "e-mail" - }, - { - "emoji": "๐Ÿ“จ", - "name": "incoming envelope" - }, - { - "emoji": "๐Ÿ“ฉ", - "name": "envelope with arrow" - }, - { - "emoji": "๐Ÿ“ค", - "name": "outbox tray" - }, - { - "emoji": "๐Ÿ“ฅ", - "name": "inbox tray" - }, - { - "emoji": "๐Ÿ“ฆ", - "name": "package" - }, - { - "emoji": "๐Ÿ“ซ", - "name": "closed mailbox with raised flag" - }, - { - "emoji": "๐Ÿ“ช", - "name": "closed mailbox with lowered flag" - }, - { - "emoji": "๐Ÿ“ฌ", - "name": "open mailbox with raised flag" - }, - { - "emoji": "๐Ÿ“ญ", - "name": "open mailbox with lowered flag" - }, - { - "emoji": "๐Ÿ“ฎ", - "name": "postbox" - }, - { - "emoji": "๐Ÿ—ณ๏ธ", - "name": "ballot box with ballot" - }, - { - "emoji": "โœ๏ธ", - "name": "pencil" - }, - { - "emoji": "โœ’๏ธ", - "name": "black nib" - }, - { - "emoji": "๐Ÿ–‹๏ธ", - "name": "fountain pen" - }, - { - "emoji": "๐Ÿ–Š๏ธ", - "name": "pen" - }, - { - "emoji": "๐Ÿ–Œ๏ธ", - "name": "paintbrush" - }, - { - "emoji": "๐Ÿ–๏ธ", - "name": "crayon" - }, - { - "emoji": "๐Ÿ“", - "name": "memo" - }, - { - "emoji": "๐Ÿ’ผ", - "name": "briefcase" - }, - { - "emoji": "๐Ÿ“", - "name": "file folder" - }, - { - "emoji": "๐Ÿ“‚", - "name": "open file folder" - }, - { - "emoji": "๐Ÿ—‚๏ธ", - "name": "card index dividers" - }, - { - "emoji": "๐Ÿ“…", - "name": "calendar" - }, - { - "emoji": "๐Ÿ“†", - "name": "tear-off calendar" - }, - { - "emoji": "๐Ÿ—’๏ธ", - "name": "spiral notepad" - }, - { - "emoji": "๐Ÿ—“๏ธ", - "name": "spiral calendar" - }, - { - "emoji": "๐Ÿ“‡", - "name": "card index" - }, - { - "emoji": "๐Ÿ“ˆ", - "name": "chart increasing" - }, - { - "emoji": "๐Ÿ“‰", - "name": "chart decreasing" - }, - { - "emoji": "๐Ÿ“Š", - "name": "bar chart" - }, - { - "emoji": "๐Ÿ“‹", - "name": "clipboard" - }, - { - "emoji": "๐Ÿ“Œ", - "name": "pushpin" - }, - { - "emoji": "๐Ÿ“", - "name": "round pushpin" - }, - { - "emoji": "๐Ÿ“Ž", - "name": "paperclip" - }, - { - "emoji": "๐Ÿ–‡๏ธ", - "name": "linked paperclips" - }, - { - "emoji": "๐Ÿ“", - "name": "straight ruler" - }, - { - "emoji": "๐Ÿ“", - "name": "triangular ruler" - }, - { - "emoji": "โœ‚๏ธ", - "name": "scissors" - }, - { - "emoji": "๐Ÿ—ƒ๏ธ", - "name": "card file box" - }, - { - "emoji": "๐Ÿ—„๏ธ", - "name": "file cabinet" - }, - { - "emoji": "๐Ÿ—‘๏ธ", - "name": "wastebasket" - }, - { - "emoji": "๐Ÿ”’", - "name": "locked" - }, - { - "emoji": "๐Ÿ”“", - "name": "unlocked" - }, - { - "emoji": "๐Ÿ”", - "name": "locked with pen" - }, - { - "emoji": "๐Ÿ”", - "name": "locked with key" - }, - { - "emoji": "๐Ÿ”‘", - "name": "key" - }, - { - "emoji": "๐Ÿ—๏ธ", - "name": "old key" - }, - { - "emoji": "๐Ÿ”จ", - "name": "hammer" - }, - { - "emoji": "๐Ÿช“", - "name": "axe" - }, - { - "emoji": "โ›๏ธ", - "name": "pick" - }, - { - "emoji": "โš’๏ธ", - "name": "hammer and pick" - }, - { - "emoji": "๐Ÿ› ๏ธ", - "name": "hammer and wrench" - }, - { - "emoji": "๐Ÿ—ก๏ธ", - "name": "dagger" - }, - { - "emoji": "โš”๏ธ", - "name": "crossed swords" - }, - { - "emoji": "๐Ÿ’ฃ", - "name": "bomb" - }, - { - "emoji": "๐Ÿชƒ", - "name": "boomerang" - }, - { - "emoji": "๐Ÿน", - "name": "bow and arrow" - }, - { - "emoji": "๐Ÿ›ก๏ธ", - "name": "shield" - }, - { - "emoji": "๐Ÿชš", - "name": "carpentry saw" - }, - { - "emoji": "๐Ÿ”ง", - "name": "wrench" - }, - { - "emoji": "๐Ÿช›", - "name": "screwdriver" - }, - { - "emoji": "๐Ÿ”ฉ", - "name": "nut and bolt" - }, - { - "emoji": "โš™๏ธ", - "name": "gear" - }, - { - "emoji": "๐Ÿ—œ๏ธ", - "name": "clamp" - }, - { - "emoji": "โš–๏ธ", - "name": "balance scale" - }, - { - "emoji": "๐Ÿฆฏ", - "name": "white cane" - }, - { - "emoji": "๐Ÿ”—", - "name": "link" - }, - { - "emoji": "โ›“๏ธโ€๐Ÿ’ฅ", - "name": "broken chain" - }, - { - "emoji": "โ›“๏ธ", - "name": "chains" - }, - { - "emoji": "๐Ÿช", - "name": "hook" - }, - { - "emoji": "๐Ÿงฐ", - "name": "toolbox" - }, - { - "emoji": "๐Ÿงฒ", - "name": "magnet" - }, - { - "emoji": "๐Ÿชœ", - "name": "ladder" - }, - { - "emoji": "โš—๏ธ", - "name": "alembic" - }, - { - "emoji": "๐Ÿงช", - "name": "test tube" - }, - { - "emoji": "๐Ÿงซ", - "name": "petri dish" - }, - { - "emoji": "๐Ÿงฌ", - "name": "dna" - }, - { - "emoji": "๐Ÿ”ฌ", - "name": "microscope" - }, - { - "emoji": "๐Ÿ”ญ", - "name": "telescope" - }, - { - "emoji": "๐Ÿ“ก", - "name": "satellite antenna" - }, - { - "emoji": "๐Ÿ’‰", - "name": "syringe" - }, - { - "emoji": "๐Ÿฉธ", - "name": "drop of blood" - }, - { - "emoji": "๐Ÿ’Š", - "name": "pill" - }, - { - "emoji": "๐Ÿฉน", - "name": "adhesive bandage" - }, - { - "emoji": "๐Ÿฉผ", - "name": "crutch" - }, - { - "emoji": "๐Ÿฉบ", - "name": "stethoscope" - }, - { - "emoji": "๐Ÿฉป", - "name": "x-ray" - }, - { - "emoji": "๐Ÿšช", - "name": "door" - }, - { - "emoji": "๐Ÿ›—", - "name": "elevator" - }, - { - "emoji": "๐Ÿชž", - "name": "mirror" - }, - { - "emoji": "๐ŸชŸ", - "name": "window" - }, - { - "emoji": "๐Ÿ›๏ธ", - "name": "bed" - }, - { - "emoji": "๐Ÿ›‹๏ธ", - "name": "couch and lamp" - }, - { - "emoji": "๐Ÿช‘", - "name": "chair" - }, - { - "emoji": "๐Ÿšฝ", - "name": "toilet" - }, - { - "emoji": "๐Ÿช ", - "name": "plunger" - }, - { - "emoji": "๐Ÿšฟ", - "name": "shower" - }, - { - "emoji": "๐Ÿ›", - "name": "bathtub" - }, - { - "emoji": "๐Ÿชค", - "name": "mouse trap" - }, - { - "emoji": "๐Ÿช’", - "name": "razor" - }, - { - "emoji": "๐Ÿงด", - "name": "lotion bottle" - }, - { - "emoji": "๐Ÿงท", - "name": "safety pin" - }, - { - "emoji": "๐Ÿงน", - "name": "broom" - }, - { - "emoji": "๐Ÿงบ", - "name": "basket" - }, - { - "emoji": "๐Ÿงป", - "name": "roll of paper" - }, - { - "emoji": "๐Ÿชฃ", - "name": "bucket" - }, - { - "emoji": "๐Ÿงผ", - "name": "soap" - }, - { - "emoji": "๐Ÿซง", - "name": "bubbles" - }, - { - "emoji": "๐Ÿชฅ", - "name": "toothbrush" - }, - { - "emoji": "๐Ÿงฝ", - "name": "sponge" - }, - { - "emoji": "๐Ÿงฏ", - "name": "fire extinguisher" - }, - { - "emoji": "๐Ÿ›’", - "name": "shopping cart" - }, - { - "emoji": "๐Ÿšฌ", - "name": "cigarette" - }, - { - "emoji": "โšฐ๏ธ", - "name": "coffin" - }, - { - "emoji": "๐Ÿชฆ", - "name": "headstone" - }, - { - "emoji": "โšฑ๏ธ", - "name": "funeral urn" - }, - { - "emoji": "๐Ÿงฟ", - "name": "nazar amulet" - }, - { - "emoji": "๐Ÿชฌ", - "name": "hamsa" - }, - { - "emoji": "๐Ÿ—ฟ", - "name": "moai" - }, - { - "emoji": "๐Ÿชง", - "name": "placard" - }, - { - "emoji": "๐Ÿชช", - "name": "identification card" - } - ], - "Symbols": [ - { - "emoji": "๐Ÿง", - "name": "ATM sign" - }, - { - "emoji": "๐Ÿšฎ", - "name": "litter in bin sign" - }, - { - "emoji": "๐Ÿšฐ", - "name": "potable water" - }, - { - "emoji": "โ™ฟ", - "name": "wheelchair symbol" - }, - { - "emoji": "๐Ÿšน", - "name": "menโ€™s room" - }, - { - "emoji": "๐Ÿšบ", - "name": "womenโ€™s room" - }, - { - "emoji": "๐Ÿšป", - "name": "restroom" - }, - { - "emoji": "๐Ÿšผ", - "name": "baby symbol" - }, - { - "emoji": "๐Ÿšพ", - "name": "water closet" - }, - { - "emoji": "๐Ÿ›‚", - "name": "passport control" - }, - { - "emoji": "๐Ÿ›ƒ", - "name": "customs" - }, - { - "emoji": "๐Ÿ›„", - "name": "baggage claim" - }, - { - "emoji": "๐Ÿ›…", - "name": "left luggage" - }, - { - "emoji": "โš ๏ธ", - "name": "warning" - }, - { - "emoji": "๐Ÿšธ", - "name": "children crossing" - }, - { - "emoji": "โ›”", - "name": "no entry" - }, - { - "emoji": "๐Ÿšซ", - "name": "prohibited" - }, - { - "emoji": "๐Ÿšณ", - "name": "no bicycles" - }, - { - "emoji": "๐Ÿšญ", - "name": "no smoking" - }, - { - "emoji": "๐Ÿšฏ", - "name": "no littering" - }, - { - "emoji": "๐Ÿšฑ", - "name": "non-potable water" - }, - { - "emoji": "๐Ÿšท", - "name": "no pedestrians" - }, - { - "emoji": "๐Ÿ“ต", - "name": "no mobile phones" - }, - { - "emoji": "๐Ÿ”ž", - "name": "no one under eighteen" - }, - { - "emoji": "โ˜ข๏ธ", - "name": "radioactive" - }, - { - "emoji": "โ˜ฃ๏ธ", - "name": "biohazard" - }, - { - "emoji": "โฌ†๏ธ", - "name": "up arrow" - }, - { - "emoji": "โ†—๏ธ", - "name": "up-right arrow" - }, - { - "emoji": "โžก๏ธ", - "name": "right arrow" - }, - { - "emoji": "โ†˜๏ธ", - "name": "down-right arrow" - }, - { - "emoji": "โฌ‡๏ธ", - "name": "down arrow" - }, - { - "emoji": "โ†™๏ธ", - "name": "down-left arrow" - }, - { - "emoji": "โฌ…๏ธ", - "name": "left arrow" - }, - { - "emoji": "โ†–๏ธ", - "name": "up-left arrow" - }, - { - "emoji": "โ†•๏ธ", - "name": "up-down arrow" - }, - { - "emoji": "โ†”๏ธ", - "name": "left-right arrow" - }, - { - "emoji": "โ†ฉ๏ธ", - "name": "right arrow curving left" - }, - { - "emoji": "โ†ช๏ธ", - "name": "left arrow curving right" - }, - { - "emoji": "โคด๏ธ", - "name": "right arrow curving up" - }, - { - "emoji": "โคต๏ธ", - "name": "right arrow curving down" - }, - { - "emoji": "๐Ÿ”ƒ", - "name": "clockwise vertical arrows" - }, - { - "emoji": "๐Ÿ”„", - "name": "counterclockwise arrows button" - }, - { - "emoji": "๐Ÿ”™", - "name": "BACK arrow" - }, - { - "emoji": "๐Ÿ”š", - "name": "END arrow" - }, - { - "emoji": "๐Ÿ”›", - "name": "ON! arrow" - }, - { - "emoji": "๐Ÿ”œ", - "name": "SOON arrow" - }, - { - "emoji": "๐Ÿ”", - "name": "TOP arrow" - }, - { - "emoji": "๐Ÿ›", - "name": "place of worship" - }, - { - "emoji": "โš›๏ธ", - "name": "atom symbol" - }, - { - "emoji": "๐Ÿ•‰๏ธ", - "name": "om" - }, - { - "emoji": "โœก๏ธ", - "name": "star of David" - }, - { - "emoji": "โ˜ธ๏ธ", - "name": "wheel of dharma" - }, - { - "emoji": "โ˜ฏ๏ธ", - "name": "yin yang" - }, - { - "emoji": "โœ๏ธ", - "name": "latin cross" - }, - { - "emoji": "โ˜ฆ๏ธ", - "name": "orthodox cross" - }, - { - "emoji": "โ˜ช๏ธ", - "name": "star and crescent" - }, - { - "emoji": "โ˜ฎ๏ธ", - "name": "peace symbol" - }, - { - "emoji": "๐Ÿ•Ž", - "name": "menorah" - }, - { - "emoji": "๐Ÿ”ฏ", - "name": "dotted six-pointed star" - }, - { - "emoji": "๐Ÿชฏ", - "name": "khanda" - }, - { - "emoji": "โ™ˆ", - "name": "Aries" - }, - { - "emoji": "โ™‰", - "name": "Taurus" - }, - { - "emoji": "โ™Š", - "name": "Gemini" - }, - { - "emoji": "โ™‹", - "name": "Cancer" - }, - { - "emoji": "โ™Œ", - "name": "Leo" - }, - { - "emoji": "โ™", - "name": "Virgo" - }, - { - "emoji": "โ™Ž", - "name": "Libra" - }, - { - "emoji": "โ™", - "name": "Scorpio" - }, - { - "emoji": "โ™", - "name": "Sagittarius" - }, - { - "emoji": "โ™‘", - "name": "Capricorn" - }, - { - "emoji": "โ™’", - "name": "Aquarius" - }, - { - "emoji": "โ™“", - "name": "Pisces" - }, - { - "emoji": "โ›Ž", - "name": "Ophiuchus" - }, - { - "emoji": "๐Ÿ”€", - "name": "shuffle tracks button" - }, - { - "emoji": "๐Ÿ”", - "name": "repeat button" - }, - { - "emoji": "๐Ÿ”‚", - "name": "repeat single button" - }, - { - "emoji": "โ–ถ๏ธ", - "name": "play button" - }, - { - "emoji": "โฉ", - "name": "fast-forward button" - }, - { - "emoji": "โญ๏ธ", - "name": "next track button" - }, - { - "emoji": "โฏ๏ธ", - "name": "play or pause button" - }, - { - "emoji": "โ—€๏ธ", - "name": "reverse button" - }, - { - "emoji": "โช", - "name": "fast reverse button" - }, - { - "emoji": "โฎ๏ธ", - "name": "last track button" - }, - { - "emoji": "๐Ÿ”ผ", - "name": "upwards button" - }, - { - "emoji": "โซ", - "name": "fast up button" - }, - { - "emoji": "๐Ÿ”ฝ", - "name": "downwards button" - }, - { - "emoji": "โฌ", - "name": "fast down button" - }, - { - "emoji": "โธ๏ธ", - "name": "pause button" - }, - { - "emoji": "โน๏ธ", - "name": "stop button" - }, - { - "emoji": "โบ๏ธ", - "name": "record button" - }, - { - "emoji": "โ๏ธ", - "name": "eject button" - }, - { - "emoji": "๐ŸŽฆ", - "name": "cinema" - }, - { - "emoji": "๐Ÿ”…", - "name": "dim button" - }, - { - "emoji": "๐Ÿ”†", - "name": "bright button" - }, - { - "emoji": "๐Ÿ“ถ", - "name": "antenna bars" - }, - { - "emoji": "๐Ÿ›œ", - "name": "wireless" - }, - { - "emoji": "๐Ÿ“ณ", - "name": "vibration mode" - }, - { - "emoji": "๐Ÿ“ด", - "name": "mobile phone off" - }, - { - "emoji": "โ™€๏ธ", - "name": "female sign" - }, - { - "emoji": "โ™‚๏ธ", - "name": "male sign" - }, - { - "emoji": "โšง๏ธ", - "name": "transgender symbol" - }, - { - "emoji": "โœ–๏ธ", - "name": "multiply" - }, - { - "emoji": "โž•", - "name": "plus" - }, - { - "emoji": "โž–", - "name": "minus" - }, - { - "emoji": "โž—", - "name": "divide" - }, - { - "emoji": "๐ŸŸฐ", - "name": "heavy equals sign" - }, - { - "emoji": "โ™พ๏ธ", - "name": "infinity" - }, - { - "emoji": "โ€ผ๏ธ", - "name": "double exclamation mark" - }, - { - "emoji": "โ‰๏ธ", - "name": "exclamation question mark" - }, - { - "emoji": "โ“", - "name": "red question mark" - }, - { - "emoji": "โ”", - "name": "white question mark" - }, - { - "emoji": "โ•", - "name": "white exclamation mark" - }, - { - "emoji": "โ—", - "name": "red exclamation mark" - }, - { - "emoji": "ใ€ฐ๏ธ", - "name": "wavy dash" - }, - { - "emoji": "๐Ÿ’ฑ", - "name": "currency exchange" - }, - { - "emoji": "๐Ÿ’ฒ", - "name": "heavy dollar sign" - }, - { - "emoji": "โš•๏ธ", - "name": "medical symbol" - }, - { - "emoji": "โ™ป๏ธ", - "name": "recycling symbol" - }, - { - "emoji": "โšœ๏ธ", - "name": "fleur-de-lis" - }, - { - "emoji": "๐Ÿ”ฑ", - "name": "trident emblem" - }, - { - "emoji": "๐Ÿ“›", - "name": "name badge" - }, - { - "emoji": "๐Ÿ”ฐ", - "name": "Japanese symbol for beginner" - }, - { - "emoji": "โญ•", - "name": "hollow red circle" - }, - { - "emoji": "โœ…", - "name": "check mark button" - }, - { - "emoji": "โ˜‘๏ธ", - "name": "check box with check" - }, - { - "emoji": "โœ”๏ธ", - "name": "check mark" - }, - { - "emoji": "โŒ", - "name": "cross mark" - }, - { - "emoji": "โŽ", - "name": "cross mark button" - }, - { - "emoji": "โžฐ", - "name": "curly loop" - }, - { - "emoji": "โžฟ", - "name": "double curly loop" - }, - { - "emoji": "ใ€ฝ๏ธ", - "name": "part alternation mark" - }, - { - "emoji": "โœณ๏ธ", - "name": "eight-spoked asterisk" - }, - { - "emoji": "โœด๏ธ", - "name": "eight-pointed star" - }, - { - "emoji": "โ‡๏ธ", - "name": "sparkle" - }, - { - "emoji": "ยฉ๏ธ", - "name": "copyright" - }, - { - "emoji": "ยฎ๏ธ", - "name": "registered" - }, - { - "emoji": "โ„ข๏ธ", - "name": "trade mark" - }, - { - "emoji": "#๏ธโƒฃ", - "name": "keycap #" - }, - { - "emoji": "*๏ธโƒฃ", - "name": "keycap *" - }, - { - "emoji": "0๏ธโƒฃ", - "name": "keycap 0" - }, - { - "emoji": "1๏ธโƒฃ", - "name": "keycap 1" - }, - { - "emoji": "2๏ธโƒฃ", - "name": "keycap 2" - }, - { - "emoji": "3๏ธโƒฃ", - "name": "keycap 3" - }, - { - "emoji": "4๏ธโƒฃ", - "name": "keycap 4" - }, - { - "emoji": "5๏ธโƒฃ", - "name": "keycap 5" - }, - { - "emoji": "6๏ธโƒฃ", - "name": "keycap 6" - }, - { - "emoji": "7๏ธโƒฃ", - "name": "keycap 7" - }, - { - "emoji": "8๏ธโƒฃ", - "name": "keycap 8" - }, - { - "emoji": "9๏ธโƒฃ", - "name": "keycap 9" - }, - { - "emoji": "๐Ÿ”Ÿ", - "name": "keycap 10" - }, - { - "emoji": "๐Ÿ” ", - "name": "input latin uppercase" - }, - { - "emoji": "๐Ÿ”ก", - "name": "input latin lowercase" - }, - { - "emoji": "๐Ÿ”ข", - "name": "input numbers" - }, - { - "emoji": "๐Ÿ”ฃ", - "name": "input symbols" - }, - { - "emoji": "๐Ÿ”ค", - "name": "input latin letters" - }, - { - "emoji": "๐Ÿ…ฐ๏ธ", - "name": "A button (blood type)" - }, - { - "emoji": "๐Ÿ†Ž", - "name": "AB button (blood type)" - }, - { - "emoji": "๐Ÿ…ฑ๏ธ", - "name": "B button (blood type)" - }, - { - "emoji": "๐Ÿ†‘", - "name": "CL button" - }, - { - "emoji": "๐Ÿ†’", - "name": "COOL button" - }, - { - "emoji": "๐Ÿ†“", - "name": "FREE button" - }, - { - "emoji": "โ„น๏ธ", - "name": "information" - }, - { - "emoji": "๐Ÿ†”", - "name": "ID button" - }, - { - "emoji": "โ“‚๏ธ", - "name": "circled M" - }, - { - "emoji": "๐Ÿ†•", - "name": "NEW button" - }, - { - "emoji": "๐Ÿ†–", - "name": "NG button" - }, - { - "emoji": "๐Ÿ…พ๏ธ", - "name": "O button (blood type)" - }, - { - "emoji": "๐Ÿ†—", - "name": "OK button" - }, - { - "emoji": "๐Ÿ…ฟ๏ธ", - "name": "P button" - }, - { - "emoji": "๐Ÿ†˜", - "name": "SOS button" - }, - { - "emoji": "๐Ÿ†™", - "name": "UP! button" - }, - { - "emoji": "๐Ÿ†š", - "name": "VS button" - }, - { - "emoji": "๐Ÿˆ", - "name": "Japanese โ€œhereโ€ button" - }, - { - "emoji": "๐Ÿˆ‚๏ธ", - "name": "Japanese โ€œservice chargeโ€ button" - }, - { - "emoji": "๐Ÿˆท๏ธ", - "name": "Japanese โ€œmonthly amountโ€ button" - }, - { - "emoji": "๐Ÿˆถ", - "name": "Japanese โ€œnot free of chargeโ€ button" - }, - { - "emoji": "๐Ÿˆฏ", - "name": "Japanese โ€œreservedโ€ button" - }, - { - "emoji": "๐Ÿ‰", - "name": "Japanese โ€œbargainโ€ button" - }, - { - "emoji": "๐Ÿˆน", - "name": "Japanese โ€œdiscountโ€ button" - }, - { - "emoji": "๐Ÿˆš", - "name": "Japanese โ€œfree of chargeโ€ button" - }, - { - "emoji": "๐Ÿˆฒ", - "name": "Japanese โ€œprohibitedโ€ button" - }, - { - "emoji": "๐Ÿ‰‘", - "name": "Japanese โ€œacceptableโ€ button" - }, - { - "emoji": "๐Ÿˆธ", - "name": "Japanese โ€œapplicationโ€ button" - }, - { - "emoji": "๐Ÿˆด", - "name": "Japanese โ€œpassing gradeโ€ button" - }, - { - "emoji": "๐Ÿˆณ", - "name": "Japanese โ€œvacancyโ€ button" - }, - { - "emoji": "ใŠ—๏ธ", - "name": "Japanese โ€œcongratulationsโ€ button" - }, - { - "emoji": "ใŠ™๏ธ", - "name": "Japanese โ€œsecretโ€ button" - }, - { - "emoji": "๐Ÿˆบ", - "name": "Japanese โ€œopen for businessโ€ button" - }, - { - "emoji": "๐Ÿˆต", - "name": "Japanese โ€œno vacancyโ€ button" - }, - { - "emoji": "๐Ÿ”ด", - "name": "red circle" - }, - { - "emoji": "๐ŸŸ ", - "name": "orange circle" - }, - { - "emoji": "๐ŸŸก", - "name": "yellow circle" - }, - { - "emoji": "๐ŸŸข", - "name": "green circle" - }, - { - "emoji": "๐Ÿ”ต", - "name": "blue circle" - }, - { - "emoji": "๐ŸŸฃ", - "name": "purple circle" - }, - { - "emoji": "๐ŸŸค", - "name": "brown circle" - }, - { - "emoji": "โšซ", - "name": "black circle" - }, - { - "emoji": "โšช", - "name": "white circle" - }, - { - "emoji": "๐ŸŸฅ", - "name": "red square" - }, - { - "emoji": "๐ŸŸง", - "name": "orange square" - }, - { - "emoji": "๐ŸŸจ", - "name": "yellow square" - }, - { - "emoji": "๐ŸŸฉ", - "name": "green square" - }, - { - "emoji": "๐ŸŸฆ", - "name": "blue square" - }, - { - "emoji": "๐ŸŸช", - "name": "purple square" - }, - { - "emoji": "๐ŸŸซ", - "name": "brown square" - }, - { - "emoji": "โฌ›", - "name": "black large square" - }, - { - "emoji": "โฌœ", - "name": "white large square" - }, - { - "emoji": "โ—ผ๏ธ", - "name": "black medium square" - }, - { - "emoji": "โ—ป๏ธ", - "name": "white medium square" - }, - { - "emoji": "โ—พ", - "name": "black medium-small square" - }, - { - "emoji": "โ—ฝ", - "name": "white medium-small square" - }, - { - "emoji": "โ–ช๏ธ", - "name": "black small square" - }, - { - "emoji": "โ–ซ๏ธ", - "name": "white small square" - }, - { - "emoji": "๐Ÿ”ถ", - "name": "large orange diamond" - }, - { - "emoji": "๐Ÿ”ท", - "name": "large blue diamond" - }, - { - "emoji": "๐Ÿ”ธ", - "name": "small orange diamond" - }, - { - "emoji": "๐Ÿ”น", - "name": "small blue diamond" - }, - { - "emoji": "๐Ÿ”บ", - "name": "red triangle pointed up" - }, - { - "emoji": "๐Ÿ”ป", - "name": "red triangle pointed down" - }, - { - "emoji": "๐Ÿ’ ", - "name": "diamond with a dot" - }, - { - "emoji": "๐Ÿ”˜", - "name": "radio button" - }, - { - "emoji": "๐Ÿ”ณ", - "name": "white square button" - }, - { - "emoji": "๐Ÿ”ฒ", - "name": "black square button" - } - ], - "Flags": [ - { - "emoji": "๐Ÿ", - "name": "chequered flag" - }, - { - "emoji": "๐Ÿšฉ", - "name": "triangular flag" - }, - { - "emoji": "๐ŸŽŒ", - "name": "crossed flags" - }, - { - "emoji": "๐Ÿด", - "name": "black flag" - }, - { - "emoji": "๐Ÿณ๏ธ", - "name": "white flag" - }, - { - "emoji": "๐Ÿณ๏ธโ€๐ŸŒˆ", - "name": "rainbow flag" - }, - { - "emoji": "๐Ÿณ๏ธโ€โšง๏ธ", - "name": "transgender flag" - }, - { - "emoji": "๐Ÿดโ€โ˜ ๏ธ", - "name": "pirate flag" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡จ", - "name": "flag Ascension Island" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฉ", - "name": "flag Andorra" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ช", - "name": "flag United Arab Emirates" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ซ", - "name": "flag Afghanistan" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฌ", - "name": "flag Antigua & Barbuda" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฎ", - "name": "flag Anguilla" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฑ", - "name": "flag Albania" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฒ", - "name": "flag Armenia" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ด", - "name": "flag Angola" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ถ", - "name": "flag Antarctica" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ท", - "name": "flag Argentina" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ธ", - "name": "flag American Samoa" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡น", - "name": "flag Austria" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡บ", - "name": "flag Australia" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ผ", - "name": "flag Aruba" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฝ", - "name": "flag ร…land Islands" - }, - { - "emoji": "๐Ÿ‡ฆ๐Ÿ‡ฟ", - "name": "flag Azerbaijan" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฆ", - "name": "flag Bosnia & Herzegovina" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ง", - "name": "flag Barbados" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฉ", - "name": "flag Bangladesh" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ช", - "name": "flag Belgium" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ซ", - "name": "flag Burkina Faso" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฌ", - "name": "flag Bulgaria" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ญ", - "name": "flag Bahrain" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฎ", - "name": "flag Burundi" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฏ", - "name": "flag Benin" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฑ", - "name": "flag St. Barthรฉlemy" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฒ", - "name": "flag Bermuda" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ณ", - "name": "flag Brunei" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ด", - "name": "flag Bolivia" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ถ", - "name": "flag Caribbean Netherlands" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ท", - "name": "flag Brazil" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ธ", - "name": "flag Bahamas" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡น", - "name": "flag Bhutan" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ป", - "name": "flag Bouvet Island" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ผ", - "name": "flag Botswana" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡พ", - "name": "flag Belarus" - }, - { - "emoji": "๐Ÿ‡ง๐Ÿ‡ฟ", - "name": "flag Belize" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฆ", - "name": "flag Canada" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡จ", - "name": "flag Cocos (Keeling) Islands" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฉ", - "name": "flag Congo - Kinshasa" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ซ", - "name": "flag Central African Republic" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฌ", - "name": "flag Congo - Brazzaville" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ญ", - "name": "flag Switzerland" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฎ", - "name": "flag Cรดte dโ€™Ivoire" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฐ", - "name": "flag Cook Islands" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฑ", - "name": "flag Chile" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฒ", - "name": "flag Cameroon" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ณ", - "name": "flag China" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ด", - "name": "flag Colombia" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ต", - "name": "flag Clipperton Island" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ท", - "name": "flag Costa Rica" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡บ", - "name": "flag Cuba" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ป", - "name": "flag Cape Verde" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ผ", - "name": "flag Curaรงao" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฝ", - "name": "flag Christmas Island" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡พ", - "name": "flag Cyprus" - }, - { - "emoji": "๐Ÿ‡จ๐Ÿ‡ฟ", - "name": "flag Czechia" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ช", - "name": "flag Germany" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฌ", - "name": "flag Diego Garcia" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฏ", - "name": "flag Djibouti" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฐ", - "name": "flag Denmark" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฒ", - "name": "flag Dominica" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ด", - "name": "flag Dominican Republic" - }, - { - "emoji": "๐Ÿ‡ฉ๐Ÿ‡ฟ", - "name": "flag Algeria" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡ฆ", - "name": "flag Ceuta & Melilla" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡จ", - "name": "flag Ecuador" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡ช", - "name": "flag Estonia" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡ฌ", - "name": "flag Egypt" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡ญ", - "name": "flag Western Sahara" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡ท", - "name": "flag Eritrea" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡ธ", - "name": "flag Spain" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡น", - "name": "flag Ethiopia" - }, - { - "emoji": "๐Ÿ‡ช๐Ÿ‡บ", - "name": "flag European Union" - }, - { - "emoji": "๐Ÿ‡ซ๐Ÿ‡ฎ", - "name": "flag Finland" - }, - { - "emoji": "๐Ÿ‡ซ๐Ÿ‡ฏ", - "name": "flag Fiji" - }, - { - "emoji": "๐Ÿ‡ซ๐Ÿ‡ฐ", - "name": "flag Falkland Islands" - }, - { - "emoji": "๐Ÿ‡ซ๐Ÿ‡ฒ", - "name": "flag Micronesia" - }, - { - "emoji": "๐Ÿ‡ซ๐Ÿ‡ด", - "name": "flag Faroe Islands" - }, - { - "emoji": "๐Ÿ‡ซ๐Ÿ‡ท", - "name": "flag France" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฆ", - "name": "flag Gabon" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ง", - "name": "flag United Kingdom" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฉ", - "name": "flag Grenada" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ช", - "name": "flag Georgia" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ซ", - "name": "flag French Guiana" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฌ", - "name": "flag Guernsey" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ญ", - "name": "flag Ghana" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฎ", - "name": "flag Gibraltar" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฑ", - "name": "flag Greenland" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ฒ", - "name": "flag Gambia" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ณ", - "name": "flag Guinea" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ต", - "name": "flag Guadeloupe" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ถ", - "name": "flag Equatorial Guinea" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ท", - "name": "flag Greece" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ธ", - "name": "flag South Georgia & South Sandwich Islands" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡น", - "name": "flag Guatemala" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡บ", - "name": "flag Guam" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡ผ", - "name": "flag Guinea-Bissau" - }, - { - "emoji": "๐Ÿ‡ฌ๐Ÿ‡พ", - "name": "flag Guyana" - }, - { - "emoji": "๐Ÿ‡ญ๐Ÿ‡ฐ", - "name": "flag Hong Kong SAR China" - }, - { - "emoji": "๐Ÿ‡ญ๐Ÿ‡ฒ", - "name": "flag Heard & McDonald Islands" - }, - { - "emoji": "๐Ÿ‡ญ๐Ÿ‡ณ", - "name": "flag Honduras" - }, - { - "emoji": "๐Ÿ‡ญ๐Ÿ‡ท", - "name": "flag Croatia" - }, - { - "emoji": "๐Ÿ‡ญ๐Ÿ‡น", - "name": "flag Haiti" - }, - { - "emoji": "๐Ÿ‡ญ๐Ÿ‡บ", - "name": "flag Hungary" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡จ", - "name": "flag Canary Islands" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ฉ", - "name": "flag Indonesia" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ช", - "name": "flag Ireland" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ฑ", - "name": "flag Israel" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ฒ", - "name": "flag Isle of Man" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ณ", - "name": "flag India" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ด", - "name": "flag British Indian Ocean Territory" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ถ", - "name": "flag Iraq" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ท", - "name": "flag Iran" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡ธ", - "name": "flag Iceland" - }, - { - "emoji": "๐Ÿ‡ฎ๐Ÿ‡น", - "name": "flag Italy" - }, - { - "emoji": "๐Ÿ‡ฏ๐Ÿ‡ช", - "name": "flag Jersey" - }, - { - "emoji": "๐Ÿ‡ฏ๐Ÿ‡ฒ", - "name": "flag Jamaica" - }, - { - "emoji": "๐Ÿ‡ฏ๐Ÿ‡ด", - "name": "flag Jordan" - }, - { - "emoji": "๐Ÿ‡ฏ๐Ÿ‡ต", - "name": "flag Japan" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ช", - "name": "flag Kenya" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฌ", - "name": "flag Kyrgyzstan" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ญ", - "name": "flag Cambodia" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฎ", - "name": "flag Kiribati" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฒ", - "name": "flag Comoros" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ณ", - "name": "flag St. Kitts & Nevis" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ต", - "name": "flag North Korea" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ท", - "name": "flag South Korea" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ผ", - "name": "flag Kuwait" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡พ", - "name": "flag Cayman Islands" - }, - { - "emoji": "๐Ÿ‡ฐ๐Ÿ‡ฟ", - "name": "flag Kazakhstan" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ฆ", - "name": "flag Laos" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ง", - "name": "flag Lebanon" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡จ", - "name": "flag St. Lucia" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ฎ", - "name": "flag Liechtenstein" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ฐ", - "name": "flag Sri Lanka" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ท", - "name": "flag Liberia" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ธ", - "name": "flag Lesotho" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡น", - "name": "flag Lithuania" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡บ", - "name": "flag Luxembourg" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡ป", - "name": "flag Latvia" - }, - { - "emoji": "๐Ÿ‡ฑ๐Ÿ‡พ", - "name": "flag Libya" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฆ", - "name": "flag Morocco" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡จ", - "name": "flag Monaco" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฉ", - "name": "flag Moldova" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ช", - "name": "flag Montenegro" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ซ", - "name": "flag St. Martin" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฌ", - "name": "flag Madagascar" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ญ", - "name": "flag Marshall Islands" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฐ", - "name": "flag North Macedonia" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฑ", - "name": "flag Mali" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฒ", - "name": "flag Myanmar (Burma)" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ณ", - "name": "flag Mongolia" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ด", - "name": "flag Macao SAR China" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ต", - "name": "flag Northern Mariana Islands" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ถ", - "name": "flag Martinique" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ท", - "name": "flag Mauritania" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ธ", - "name": "flag Montserrat" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡น", - "name": "flag Malta" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡บ", - "name": "flag Mauritius" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ป", - "name": "flag Maldives" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ผ", - "name": "flag Malawi" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฝ", - "name": "flag Mexico" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡พ", - "name": "flag Malaysia" - }, - { - "emoji": "๐Ÿ‡ฒ๐Ÿ‡ฟ", - "name": "flag Mozambique" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ฆ", - "name": "flag Namibia" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡จ", - "name": "flag New Caledonia" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ช", - "name": "flag Niger" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ซ", - "name": "flag Norfolk Island" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ฌ", - "name": "flag Nigeria" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ฎ", - "name": "flag Nicaragua" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ฑ", - "name": "flag Netherlands" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ด", - "name": "flag Norway" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ต", - "name": "flag Nepal" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ท", - "name": "flag Nauru" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡บ", - "name": "flag Niue" - }, - { - "emoji": "๐Ÿ‡ณ๐Ÿ‡ฟ", - "name": "flag New Zealand" - }, - { - "emoji": "๐Ÿ‡ด๐Ÿ‡ฒ", - "name": "flag Oman" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ฆ", - "name": "flag Panama" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ช", - "name": "flag Peru" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ซ", - "name": "flag French Polynesia" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ฌ", - "name": "flag Papua New Guinea" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ญ", - "name": "flag Philippines" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ฐ", - "name": "flag Pakistan" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ฑ", - "name": "flag Poland" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ฒ", - "name": "flag St. Pierre & Miquelon" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ณ", - "name": "flag Pitcairn Islands" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ท", - "name": "flag Puerto Rico" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ธ", - "name": "flag Palestinian Territories" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡น", - "name": "flag Portugal" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡ผ", - "name": "flag Palau" - }, - { - "emoji": "๐Ÿ‡ต๐Ÿ‡พ", - "name": "flag Paraguay" - }, - { - "emoji": "๐Ÿ‡ถ๐Ÿ‡ฆ", - "name": "flag Qatar" - }, - { - "emoji": "๐Ÿ‡ท๐Ÿ‡ช", - "name": "flag Rรฉunion" - }, - { - "emoji": "๐Ÿ‡ท๐Ÿ‡ด", - "name": "flag Romania" - }, - { - "emoji": "๐Ÿ‡ท๐Ÿ‡ธ", - "name": "flag Serbia" - }, - { - "emoji": "๐Ÿ‡ท๐Ÿ‡บ", - "name": "flag Russia" - }, - { - "emoji": "๐Ÿ‡ท๐Ÿ‡ผ", - "name": "flag Rwanda" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฆ", - "name": "flag Saudi Arabia" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ง", - "name": "flag Solomon Islands" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡จ", - "name": "flag Seychelles" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฉ", - "name": "flag Sudan" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ช", - "name": "flag Sweden" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฌ", - "name": "flag Singapore" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ญ", - "name": "flag St. Helena" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฎ", - "name": "flag Slovenia" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฏ", - "name": "flag Svalbard & Jan Mayen" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฐ", - "name": "flag Slovakia" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฑ", - "name": "flag Sierra Leone" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฒ", - "name": "flag San Marino" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ณ", - "name": "flag Senegal" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ด", - "name": "flag Somalia" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ท", - "name": "flag Suriname" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ธ", - "name": "flag South Sudan" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡น", - "name": "flag Sรฃo Tomรฉ & Prรญncipe" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ป", - "name": "flag El Salvador" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฝ", - "name": "flag Sint Maarten" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡พ", - "name": "flag Syria" - }, - { - "emoji": "๐Ÿ‡ธ๐Ÿ‡ฟ", - "name": "flag Eswatini" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฆ", - "name": "flag Tristan da Cunha" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡จ", - "name": "flag Turks & Caicos Islands" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฉ", - "name": "flag Chad" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ซ", - "name": "flag French Southern Territories" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฌ", - "name": "flag Togo" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ญ", - "name": "flag Thailand" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฏ", - "name": "flag Tajikistan" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฐ", - "name": "flag Tokelau" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฑ", - "name": "flag Timor-Leste" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฒ", - "name": "flag Turkmenistan" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ณ", - "name": "flag Tunisia" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ด", - "name": "flag Tonga" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ท", - "name": "flag Tรผrkiye" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡น", - "name": "flag Trinidad & Tobago" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ป", - "name": "flag Tuvalu" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ผ", - "name": "flag Taiwan" - }, - { - "emoji": "๐Ÿ‡น๐Ÿ‡ฟ", - "name": "flag Tanzania" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡ฆ", - "name": "flag Ukraine" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡ฌ", - "name": "flag Uganda" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡ฒ", - "name": "flag U.S. Outlying Islands" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡ณ", - "name": "flag United Nations" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡ธ", - "name": "flag United States" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡พ", - "name": "flag Uruguay" - }, - { - "emoji": "๐Ÿ‡บ๐Ÿ‡ฟ", - "name": "flag Uzbekistan" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡ฆ", - "name": "flag Vatican City" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡จ", - "name": "flag St. Vincent & Grenadines" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡ช", - "name": "flag Venezuela" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡ฌ", - "name": "flag British Virgin Islands" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡ฎ", - "name": "flag U.S. Virgin Islands" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡ณ", - "name": "flag Vietnam" - }, - { - "emoji": "๐Ÿ‡ป๐Ÿ‡บ", - "name": "flag Vanuatu" - }, - { - "emoji": "๐Ÿ‡ผ๐Ÿ‡ซ", - "name": "flag Wallis & Futuna" - }, - { - "emoji": "๐Ÿ‡ผ๐Ÿ‡ธ", - "name": "flag Samoa" - }, - { - "emoji": "๐Ÿ‡ฝ๐Ÿ‡ฐ", - "name": "flag Kosovo" - }, - { - "emoji": "๐Ÿ‡พ๐Ÿ‡ช", - "name": "flag Yemen" - }, - { - "emoji": "๐Ÿ‡พ๐Ÿ‡น", - "name": "flag Mayotte" - }, - { - "emoji": "๐Ÿ‡ฟ๐Ÿ‡ฆ", - "name": "flag South Africa" - }, - { - "emoji": "๐Ÿ‡ฟ๐Ÿ‡ฒ", - "name": "flag Zambia" - }, - { - "emoji": "๐Ÿ‡ฟ๐Ÿ‡ผ", - "name": "flag Zimbabwe" - }, - { - "emoji": "๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ", - "name": "flag England" - }, - { - "emoji": "๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ", - "name": "flag Scotland" - }, - { - "emoji": "๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ", - "name": "flag Wales" - } - ] -} \ No newline at end of file diff --git a/src/@angor/angor.provider.ts b/src/@angor/angor.provider.ts index 27e6a58..e78028b 100644 --- a/src/@angor/angor.provider.ts +++ b/src/@angor/angor.provider.ts @@ -1,15 +1,3 @@ -import { provideHttpClient, withInterceptors } from '@angular/common/http'; -import { - APP_INITIALIZER, - ENVIRONMENT_INITIALIZER, - EnvironmentProviders, - Provider, - importProvidersFrom, - inject, -} from '@angular/core'; -import { MATERIAL_SANITY_CHECKS } from '@angular/material/core'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; import { ANGOR_MOCK_API_DEFAULT_DELAY, mockApiInterceptor, @@ -25,6 +13,18 @@ import { AngorMediaWatcherService } from '@angor/services/media-watcher'; import { AngorPlatformService } from '@angor/services/platform'; import { AngorSplashScreenService } from '@angor/services/splash-screen'; import { AngorUtilsService } from '@angor/services/utils'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { + APP_INITIALIZER, + ENVIRONMENT_INITIALIZER, + EnvironmentProviders, + Provider, + importProvidersFrom, + inject, +} from '@angular/core'; +import { MATERIAL_SANITY_CHECKS } from '@angular/material/core'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; export type AngorProviderConfig = { mockApi?: { @@ -40,10 +40,8 @@ export type AngorProviderConfig = { export const provideAngor = ( config: AngorProviderConfig ): Array => { - const providers: Array = [ { - provide: MATERIAL_SANITY_CHECKS, useValue: { doctype: true, @@ -52,7 +50,6 @@ export const provideAngor = ( }, }, { - provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'fill', @@ -103,7 +100,6 @@ export const provideAngor = ( }, ]; - if (config?.mockApi?.services) { providers.push( provideHttpClient(withInterceptors([mockApiInterceptor])), @@ -116,6 +112,5 @@ export const provideAngor = ( ); } - return providers; }; diff --git a/src/@angor/animations/defaults.ts b/src/@angor/animations/defaults.ts index f08a551..3cd6c41 100644 --- a/src/@angor/animations/defaults.ts +++ b/src/@angor/animations/defaults.ts @@ -2,17 +2,17 @@ * Defines animation curves for Angor. */ export class AngorAnimationCurves { - static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; // Standard animation curve - static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; // Deceleration curve - static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)'; // Acceleration curve - static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)'; // Sharp curve + static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; // Standard animation curve + static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; // Deceleration curve + static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)'; // Acceleration curve + static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)'; // Sharp curve } /** * Defines animation durations for Angor. */ export class AngorAnimationDurations { - static complex = '375ms'; // Duration for complex animations - static entering = '225ms'; // Duration for entering animations - static exiting = '195ms'; // Duration for exiting animations + static complex = '375ms'; // Duration for complex animations + static entering = '225ms'; // Duration for entering animations + static exiting = '195ms'; // Duration for exiting animations } diff --git a/src/@angor/animations/expand-collapse.ts b/src/@angor/animations/expand-collapse.ts index 15bf1eb..2d1b731 100644 --- a/src/@angor/animations/expand-collapse.ts +++ b/src/@angor/animations/expand-collapse.ts @@ -1,3 +1,7 @@ +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; import { animate, state, @@ -5,10 +9,6 @@ import { transition, trigger, } from '@angular/animations'; -import { - AngorAnimationCurves, - AngorAnimationDurations, -} from '@angor/animations/defaults'; /** * Animation trigger for expand/collapse transitions diff --git a/src/@angor/animations/fade.ts b/src/@angor/animations/fade.ts index 8813db4..f2539ac 100644 --- a/src/@angor/animations/fade.ts +++ b/src/@angor/animations/fade.ts @@ -1,3 +1,7 @@ +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; import { animate, state, @@ -5,10 +9,6 @@ import { transition, trigger, } from '@angular/animations'; -import { - AngorAnimationCurves, - AngorAnimationDurations, -} from '@angor/animations/defaults'; /** * Fade in animation trigger diff --git a/src/@angor/animations/shake.ts b/src/@angor/animations/shake.ts index a0c2d88..0b5e56e 100644 --- a/src/@angor/animations/shake.ts +++ b/src/@angor/animations/shake.ts @@ -21,15 +21,42 @@ const shake = trigger('shake', [ '{{timings}}', keyframes([ style({ transform: 'translate3d(0, 0, 0)', offset: 0 }), - style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.1 }), - style({ transform: 'translate3d(10px, 0, 0)', offset: 0.2 }), - style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.3 }), - style({ transform: 'translate3d(10px, 0, 0)', offset: 0.4 }), - style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.5 }), - style({ transform: 'translate3d(10px, 0, 0)', offset: 0.6 }), - style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.7 }), - style({ transform: 'translate3d(10px, 0, 0)', offset: 0.8 }), - style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.9 }), + style({ + transform: 'translate3d(-10px, 0, 0)', + offset: 0.1, + }), + style({ + transform: 'translate3d(10px, 0, 0)', + offset: 0.2, + }), + style({ + transform: 'translate3d(-10px, 0, 0)', + offset: 0.3, + }), + style({ + transform: 'translate3d(10px, 0, 0)', + offset: 0.4, + }), + style({ + transform: 'translate3d(-10px, 0, 0)', + offset: 0.5, + }), + style({ + transform: 'translate3d(10px, 0, 0)', + offset: 0.6, + }), + style({ + transform: 'translate3d(-10px, 0, 0)', + offset: 0.7, + }), + style({ + transform: 'translate3d(10px, 0, 0)', + offset: 0.8, + }), + style({ + transform: 'translate3d(-10px, 0, 0)', + offset: 0.9, + }), style({ transform: 'translate3d(0, 0, 0)', offset: 1 }), ]) ), diff --git a/src/@angor/animations/slide.ts b/src/@angor/animations/slide.ts index 57b6222..da2fa89 100644 --- a/src/@angor/animations/slide.ts +++ b/src/@angor/animations/slide.ts @@ -1,3 +1,7 @@ +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; import { animate, state, @@ -5,10 +9,6 @@ import { transition, trigger, } from '@angular/animations'; -import { - AngorAnimationCurves, - AngorAnimationDurations, -} from '@angor/animations/defaults'; /** * Slide in from top animation trigger diff --git a/src/@angor/animations/zoom.ts b/src/@angor/animations/zoom.ts index 1775f97..0203bdd 100644 --- a/src/@angor/animations/zoom.ts +++ b/src/@angor/animations/zoom.ts @@ -1,3 +1,7 @@ +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; import { animate, state, @@ -5,10 +9,6 @@ import { transition, trigger, } from '@angular/animations'; -import { - AngorAnimationCurves, - AngorAnimationDurations, -} from '@angor/animations/defaults'; /** * Creates a reusable animation trigger with configurable parameters. diff --git a/src/@angor/components/alert/alert.component.ts b/src/@angor/components/alert/alert.component.ts index ea9ebec..a008e1d 100644 --- a/src/@angor/components/alert/alert.component.ts +++ b/src/@angor/components/alert/alert.component.ts @@ -1,3 +1,10 @@ +import { angorAnimations } from '@angor/animations'; +import { AngorAlertService } from '@angor/components/alert/alert.service'; +import { + AngorAlertAppearance, + AngorAlertType, +} from '@angor/components/alert/alert.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { ChangeDetectionStrategy, @@ -16,13 +23,6 @@ import { } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { angorAnimations } from '@angor/animations'; -import { AngorAlertService } from '@angor/components/alert/alert.service'; -import { - AngorAlertAppearance, - AngorAlertType, -} from '@angor/components/alert/alert.types'; -import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { Subject, filter, takeUntil } from 'rxjs'; @Component({ @@ -86,16 +86,22 @@ export class AngorAlertComponent implements OnChanges, OnInit, OnDestroy { */ ngOnChanges(changes: SimpleChanges): void { if ('dismissed' in changes) { - this.dismissed = coerceBooleanProperty(changes.dismissed.currentValue); + this.dismissed = coerceBooleanProperty( + changes.dismissed.currentValue + ); this._toggleDismiss(this.dismissed); } if ('dismissible' in changes) { - this.dismissible = coerceBooleanProperty(changes.dismissible.currentValue); + this.dismissible = coerceBooleanProperty( + changes.dismissible.currentValue + ); } if ('showIcon' in changes) { - this.showIcon = coerceBooleanProperty(changes.showIcon.currentValue); + this.showIcon = coerceBooleanProperty( + changes.showIcon.currentValue + ); } } diff --git a/src/@angor/components/card/card.component.ts b/src/@angor/components/card/card.component.ts index 9d981be..99e43f8 100644 --- a/src/@angor/components/card/card.component.ts +++ b/src/@angor/components/card/card.component.ts @@ -1,3 +1,5 @@ +import { angorAnimations } from '@angor/animations'; +import { AngorCardFace } from '@angor/components/card/card.types'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { Component, @@ -7,8 +9,6 @@ import { SimpleChanges, ViewEncapsulation, } from '@angular/core'; -import { angorAnimations } from '@angor/animations'; -import { AngorCardFace } from '@angor/components/card/card.types'; @Component({ selector: 'angor-card', @@ -47,11 +47,15 @@ export class AngorCardComponent implements OnChanges { */ ngOnChanges(changes: SimpleChanges): void { if ('expanded' in changes) { - this.expanded = coerceBooleanProperty(changes.expanded.currentValue); + this.expanded = coerceBooleanProperty( + changes.expanded.currentValue + ); } if ('flippable' in changes) { - this.flippable = coerceBooleanProperty(changes.flippable.currentValue); + this.flippable = coerceBooleanProperty( + changes.flippable.currentValue + ); } } } diff --git a/src/@angor/components/drawer/drawer.component.ts b/src/@angor/components/drawer/drawer.component.ts index 9eea79f..50aa7fa 100644 --- a/src/@angor/components/drawer/drawer.component.ts +++ b/src/@angor/components/drawer/drawer.component.ts @@ -1,3 +1,9 @@ +import { AngorDrawerService } from '@angor/components/drawer/drawer.service'; +import { + AngorDrawerMode, + AngorDrawerPosition, +} from '@angor/components/drawer/drawer.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { animate, AnimationBuilder, @@ -11,6 +17,7 @@ import { EventEmitter, HostBinding, HostListener, + inject, Input, OnChanges, OnDestroy, @@ -19,14 +26,7 @@ import { Renderer2, SimpleChanges, ViewEncapsulation, - inject, } from '@angular/core'; -import { AngorDrawerService } from '@angor/components/drawer/drawer.service'; -import { - AngorDrawerMode, - AngorDrawerPosition, -} from '@angor/components/drawer/drawer.types'; -import { AngorUtilsService } from '@angor/services/utils/utils.service'; @Component({ selector: 'angor-drawer', @@ -56,7 +56,8 @@ export class AngorDrawerComponent implements OnChanges, OnInit, OnDestroy { @Output() readonly fixedChanged = new EventEmitter(); @Output() readonly modeChanged = new EventEmitter(); @Output() readonly openedChanged = new EventEmitter(); - @Output() readonly positionChanged = new EventEmitter(); + @Output() readonly positionChanged = + new EventEmitter(); private _animationsEnabled: boolean = false; private _hovered: boolean = false; @@ -107,7 +108,11 @@ export class AngorDrawerComponent implements OnChanges, OnInit, OnDestroy { this._hideOverlay(); } - if (previousMode === 'side' && currentMode === 'over' && this.opened) { + if ( + previousMode === 'side' && + currentMode === 'over' && + this.opened + ) { this._showOverlay(); } @@ -125,7 +130,9 @@ export class AngorDrawerComponent implements OnChanges, OnInit, OnDestroy { } if ('transparentOverlay' in changes) { - this.transparentOverlay = coerceBooleanProperty(changes.transparentOverlay.currentValue); + this.transparentOverlay = coerceBooleanProperty( + changes.transparentOverlay.currentValue + ); } } diff --git a/src/@angor/components/drawer/drawer.service.ts b/src/@angor/components/drawer/drawer.service.ts index daa219b..a059da5 100644 --- a/src/@angor/components/drawer/drawer.service.ts +++ b/src/@angor/components/drawer/drawer.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@angular/core'; import { AngorDrawerComponent } from '@angor/components/drawer/drawer.component'; +import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AngorDrawerService { diff --git a/src/@angor/components/highlight/highlight.component.ts b/src/@angor/components/highlight/highlight.component.ts index 0bf3b27..8aca97b 100644 --- a/src/@angor/components/highlight/highlight.component.ts +++ b/src/@angor/components/highlight/highlight.component.ts @@ -1,3 +1,4 @@ +import { AngorHighlightService } from '@angor/components/highlight/highlight.service'; import { NgClass } from '@angular/common'; import { AfterViewInit, @@ -16,7 +17,6 @@ import { ViewEncapsulation, } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; -import { AngorHighlightService } from '@angor/components/highlight/highlight.service'; @Component({ selector: 'textarea[angor-highlight]', diff --git a/src/@angor/components/highlight/highlight.service.ts b/src/@angor/components/highlight/highlight.service.ts index ddd6028..d1855f6 100644 --- a/src/@angor/components/highlight/highlight.service.ts +++ b/src/@angor/components/highlight/highlight.service.ts @@ -3,7 +3,6 @@ import hljs from 'highlight.js'; @Injectable({ providedIn: 'root' }) export class AngorHighlightService { - /** * Highlights the provided code using the specified language. */ @@ -28,15 +27,17 @@ export class AngorHighlightService { } // Determine the smallest indentation - lines.filter(line => line.length).forEach((line, index) => { - if (index === 0) { - indentation = line.search(/\S|$/); - } else { - indentation = Math.min(line.search(/\S|$/), indentation); - } - }); + lines + .filter((line) => line.length) + .forEach((line, index) => { + if (index === 0) { + indentation = line.search(/\S|$/); + } else { + indentation = Math.min(line.search(/\S|$/), indentation); + } + }); // Remove extra indentation and return formatted code - return lines.map(line => line.substring(indentation)).join('\n'); + return lines.map((line) => line.substring(indentation)).join('\n'); } } diff --git a/src/@angor/components/loading-bar/loading-bar.component.ts b/src/@angor/components/loading-bar/loading-bar.component.ts index 19ca3ce..6bb806d 100644 --- a/src/@angor/components/loading-bar/loading-bar.component.ts +++ b/src/@angor/components/loading-bar/loading-bar.component.ts @@ -1,7 +1,16 @@ -import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { Component, inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; import { AngorLoadingService } from '@angor/services/loading'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + Component, + inject, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewEncapsulation, +} from '@angular/core'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; import { Subject, takeUntil } from 'rxjs'; @Component({ diff --git a/src/@angor/components/masonry/masonry.component.ts b/src/@angor/components/masonry/masonry.component.ts index f3a99cd..6b60e56 100644 --- a/src/@angor/components/masonry/masonry.component.ts +++ b/src/@angor/components/masonry/masonry.component.ts @@ -1,3 +1,4 @@ +import { angorAnimations } from '@angor/animations'; import { NgTemplateOutlet } from '@angular/common'; import { AfterViewInit, @@ -8,7 +9,6 @@ import { TemplateRef, ViewEncapsulation, } from '@angular/core'; -import { angorAnimations } from '@angor/animations'; @Component({ selector: 'angor-masonry', diff --git a/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts b/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts index c92fad1..cda7d59 100644 --- a/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts +++ b/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts @@ -1,3 +1,7 @@ +import { AngorHorizontalNavigationComponent } from '@angor/components/navigation/horizontal/horizontal.component'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { NgClass, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, @@ -16,10 +20,6 @@ import { RouterLink, RouterLinkActive, } from '@angular/router'; -import { AngorHorizontalNavigationComponent } from '@angor/components/navigation/horizontal/horizontal.component'; -import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; -import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; -import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { Subject, takeUntil } from 'rxjs'; @Component({ @@ -69,7 +69,7 @@ export class AngorHorizontalNavigationBasicItemComponent // "isActiveMatchOptions" or the equivalent form of // item's "exactMatch" option this.isActiveMatchOptions = - this.item.isActiveMatchOptions ?? this.item.exactMatch + (this.item.isActiveMatchOptions ?? this.item.exactMatch) ? this._angorUtilsService.exactMatchOptions : this._angorUtilsService.subsetMatchOptions; diff --git a/src/@angor/components/navigation/horizontal/components/branch/branch.component.html b/src/@angor/components/navigation/horizontal/components/branch/branch.component.html index 2dc5797..b6dc826 100644 --- a/src/@angor/components/navigation/horizontal/components/branch/branch.component.html +++ b/src/@angor/components/navigation/horizontal/components/branch/branch.component.html @@ -66,7 +66,10 @@ @if (item.type === 'divider') { -
+
diff --git a/src/@angor/components/navigation/vertical/vertical.component.ts b/src/@angor/components/navigation/vertical/vertical.component.ts index 47e06fe..236f06a 100644 --- a/src/@angor/components/navigation/vertical/vertical.component.ts +++ b/src/@angor/components/navigation/vertical/vertical.component.ts @@ -1,3 +1,19 @@ +import { angorAnimations } from '@angor/animations'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { + AngorNavigationItem, + AngorVerticalNavigationAppearance, + AngorVerticalNavigationMode, + AngorVerticalNavigationPosition, +} from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationAsideItemComponent } from '@angor/components/navigation/vertical/components/aside/aside.component'; +import { AngorVerticalNavigationBasicItemComponent } from '@angor/components/navigation/vertical/components/basic/basic.component'; +import { AngorVerticalNavigationCollapsableItemComponent } from '@angor/components/navigation/vertical/components/collapsable/collapsable.component'; +import { AngorVerticalNavigationDividerItemComponent } from '@angor/components/navigation/vertical/components/divider/divider.component'; +import { AngorVerticalNavigationGroupItemComponent } from '@angor/components/navigation/vertical/components/group/group.component'; +import { AngorVerticalNavigationSpacerItemComponent } from '@angor/components/navigation/vertical/components/spacer/spacer.component'; +import { AngorScrollbarDirective } from '@angor/directives/scrollbar/scrollbar.directive'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { animate, AnimationBuilder, @@ -30,22 +46,6 @@ import { ViewEncapsulation, } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; -import { angorAnimations } from '@angor/animations'; -import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; -import { - AngorNavigationItem, - AngorVerticalNavigationAppearance, - AngorVerticalNavigationMode, - AngorVerticalNavigationPosition, -} from '@angor/components/navigation/navigation.types'; -import { AngorVerticalNavigationAsideItemComponent } from '@angor/components/navigation/vertical/components/aside/aside.component'; -import { AngorVerticalNavigationBasicItemComponent } from '@angor/components/navigation/vertical/components/basic/basic.component'; -import { AngorVerticalNavigationCollapsableItemComponent } from '@angor/components/navigation/vertical/components/collapsable/collapsable.component'; -import { AngorVerticalNavigationDividerItemComponent } from '@angor/components/navigation/vertical/components/divider/divider.component'; -import { AngorVerticalNavigationGroupItemComponent } from '@angor/components/navigation/vertical/components/group/group.component'; -import { AngorVerticalNavigationSpacerItemComponent } from '@angor/components/navigation/vertical/components/spacer/spacer.component'; -import { AngorScrollbarDirective } from '@angor/directives/scrollbar/scrollbar.directive'; -import { AngorUtilsService } from '@angor/services/utils/utils.service'; import { delay, filter, diff --git a/src/@angor/directives/scroll-reset/scroll-reset.directive.ts b/src/@angor/directives/scroll-reset/scroll-reset.directive.ts index ba91485..732bf34 100644 --- a/src/@angor/directives/scroll-reset/scroll-reset.directive.ts +++ b/src/@angor/directives/scroll-reset/scroll-reset.directive.ts @@ -21,7 +21,7 @@ export class AngorScrollResetDirective implements OnInit, OnDestroy { ngOnInit(): void { this._router.events .pipe( - filter(event => event instanceof NavigationEnd), + filter((event) => event instanceof NavigationEnd), takeUntil(this._unsubscribeAll) ) .subscribe(() => { diff --git a/src/@angor/directives/scrollbar/scrollbar.directive.ts b/src/@angor/directives/scrollbar/scrollbar.directive.ts index 4792f83..c1c7118 100644 --- a/src/@angor/directives/scrollbar/scrollbar.directive.ts +++ b/src/@angor/directives/scrollbar/scrollbar.directive.ts @@ -1,3 +1,7 @@ +import { + ScrollbarGeometry, + ScrollbarPosition, +} from '@angor/directives/scrollbar/scrollbar.types'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { Platform } from '@angular/cdk/platform'; import { @@ -10,10 +14,6 @@ import { SimpleChanges, inject, } from '@angular/core'; -import { - ScrollbarGeometry, - ScrollbarPosition, -} from '@angor/directives/scrollbar/scrollbar.types'; import { merge } from 'lodash-es'; import PerfectScrollbar from 'perfect-scrollbar'; import { Subject, debounceTime, fromEvent, takeUntil } from 'rxjs'; @@ -47,12 +47,20 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { ngOnChanges(changes: SimpleChanges): void { if ('angorScrollbar' in changes) { - this.angorScrollbar = coerceBooleanProperty(changes.angorScrollbar.currentValue); - this.angorScrollbar ? this._initScrollbar() : this._destroyScrollbar(); + this.angorScrollbar = coerceBooleanProperty( + changes.angorScrollbar.currentValue + ); + this.angorScrollbar + ? this._initScrollbar() + : this._destroyScrollbar(); } if ('angorScrollbarOptions' in changes) { - this._options = merge({}, this._options, changes.angorScrollbarOptions.currentValue); + this._options = merge( + {}, + this._options, + changes.angorScrollbarOptions.currentValue + ); this._reinitializeScrollbar(); } } @@ -92,7 +100,10 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { position(absolute: boolean = false): ScrollbarPosition { if (!absolute && this._ps) { - return new ScrollbarPosition(this._ps.reach.x || 0, this._ps.reach.y || 0); + return new ScrollbarPosition( + this._ps.reach.x || 0, + this._ps.reach.y || 0 + ); } else { return new ScrollbarPosition( this._elementRef.nativeElement.scrollLeft, @@ -115,7 +126,6 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { } } - scrollToX(x: number, speed?: number): void { this.animateScrolling('scrollLeft', x, speed); } @@ -129,7 +139,9 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { } scrollToBottom(offset: number = 0, speed?: number): void { - const top = this._elementRef.nativeElement.scrollHeight - this._elementRef.nativeElement.clientHeight; + const top = + this._elementRef.nativeElement.scrollHeight - + this._elementRef.nativeElement.clientHeight; this.animateScrolling('scrollTop', top - offset, speed); } @@ -138,7 +150,9 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { } scrollToRight(offset: number = 0, speed?: number): void { - const left = this._elementRef.nativeElement.scrollWidth - this._elementRef.nativeElement.clientWidth; + const left = + this._elementRef.nativeElement.scrollWidth - + this._elementRef.nativeElement.clientWidth; this.animateScrolling('scrollLeft', left - offset, speed); } @@ -152,14 +166,29 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { if (!element) return; const elementPos = element.getBoundingClientRect(); - const scrollerPos = this._elementRef.nativeElement.getBoundingClientRect(); + const scrollerPos = + this._elementRef.nativeElement.getBoundingClientRect(); if (this._elementRef.nativeElement.classList.contains('ps--active-x')) { - this._scrollToInAxis(elementPos.left, scrollerPos.left, 'scrollLeft', offset, ignoreVisible, speed); + this._scrollToInAxis( + elementPos.left, + scrollerPos.left, + 'scrollLeft', + offset, + ignoreVisible, + speed + ); } if (this._elementRef.nativeElement.classList.contains('ps--active-y')) { - this._scrollToInAxis(elementPos.top, scrollerPos.top, 'scrollTop', offset, ignoreVisible, speed); + this._scrollToInAxis( + elementPos.top, + scrollerPos.top, + 'scrollTop', + offset, + ignoreVisible, + speed + ); } } @@ -176,8 +205,16 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { } private _initScrollbar(): void { - if (this._ps || this._platform.ANDROID || this._platform.IOS || !this._platform.isBrowser) return; - this._ps = new PerfectScrollbar(this._elementRef.nativeElement, { ...this._options }); + if ( + this._ps || + this._platform.ANDROID || + this._platform.IOS || + !this._platform.isBrowser + ) + return; + this._ps = new PerfectScrollbar(this._elementRef.nativeElement, { + ...this._options, + }); } private _destroyScrollbar(): void { @@ -198,7 +235,8 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { ignoreVisible: boolean, speed?: number ): void { - if (ignoreVisible && elementPos <= scrollerPos - Math.abs(offset)) return; + if (ignoreVisible && elementPos <= scrollerPos - Math.abs(offset)) + return; const currentPos = this._elementRef.nativeElement[target]; const position = elementPos - scrollerPos + currentPos; @@ -213,7 +251,9 @@ export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { const step = (newTimestamp: number) => { scrollCount += Math.PI / (speed / (newTimestamp - oldTimestamp)); - const newValue = Math.round(value + cosParameter + cosParameter * Math.cos(scrollCount)); + const newValue = Math.round( + value + cosParameter + cosParameter * Math.cos(scrollCount) + ); if (this._elementRef.nativeElement[target] === oldValue) { if (scrollCount >= Math.PI) { diff --git a/src/@angor/lib/mock-api/mock-api.interceptor.ts b/src/@angor/lib/mock-api/mock-api.interceptor.ts index ca5a2c0..371718e 100644 --- a/src/@angor/lib/mock-api/mock-api.interceptor.ts +++ b/src/@angor/lib/mock-api/mock-api.interceptor.ts @@ -1,3 +1,5 @@ +import { ANGOR_MOCK_API_DEFAULT_DELAY } from '@angor/lib/mock-api/mock-api.constants'; +import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; import { HttpErrorResponse, HttpEvent, @@ -6,8 +8,6 @@ import { HttpResponse, } from '@angular/common/http'; import { inject } from '@angular/core'; -import { ANGOR_MOCK_API_DEFAULT_DELAY } from '@angor/lib/mock-api/mock-api.constants'; -import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; import { Observable, delay, of, switchMap, throwError } from 'rxjs'; import { AngorMockApiMethods } from './mock-api.types'; @@ -43,11 +43,14 @@ export const mockApiInterceptor = ( switchMap((response) => { // If no response is returned, generate a 404 error if (!response) { - return throwError(() => new HttpErrorResponse({ - error: 'NOT FOUND', - status: 404, - statusText: 'NOT FOUND', - })); + return throwError( + () => + new HttpErrorResponse({ + error: 'NOT FOUND', + status: 404, + statusText: 'NOT FOUND', + }) + ); } // Parse the response data (status and body) @@ -58,19 +61,24 @@ export const mockApiInterceptor = ( // If status code is between 200 and 300, return a successful response if (data.status >= 200 && data.status < 300) { - return of(new HttpResponse({ - body: data.body, - status: data.status, - statusText: 'OK', - })); + return of( + new HttpResponse({ + body: data.body, + status: data.status, + statusText: 'OK', + }) + ); } // For other status codes, throw an error response - return throwError(() => new HttpErrorResponse({ - error: data.body?.error, - status: data.status, - statusText: 'ERROR', - })); + return throwError( + () => + new HttpErrorResponse({ + error: data.body?.error, + status: data.status, + statusText: 'ERROR', + }) + ); }) ); }; diff --git a/src/@angor/lib/mock-api/mock-api.request-handler.ts b/src/@angor/lib/mock-api/mock-api.request-handler.ts index fed2e4c..1534082 100644 --- a/src/@angor/lib/mock-api/mock-api.request-handler.ts +++ b/src/@angor/lib/mock-api/mock-api.request-handler.ts @@ -1,5 +1,5 @@ -import { HttpRequest } from '@angular/common/http'; import { AngorMockApiReplyCallback } from '@angor/lib/mock-api/mock-api.types'; +import { HttpRequest } from '@angular/common/http'; import { Observable, of, take, throwError } from 'rxjs'; export class AngorMockApiHandler { @@ -17,7 +17,10 @@ export class AngorMockApiHandler { * @param url - The URL for the mock API handler * @param delay - Optional delay for the response */ - constructor(public url: string, public delay?: number) {} + constructor( + public url: string, + public delay?: number + ) {} /** * Getter for the response observable. @@ -27,12 +30,16 @@ export class AngorMockApiHandler { get response(): Observable { // Check if the execution limit has been reached if (this._replyCount > 0 && this._replyCount <= this._replied) { - return throwError(() => new Error('Execution limit has been reached!')); + return throwError( + () => new Error('Execution limit has been reached!') + ); } // Ensure the response callback exists if (!this._reply) { - return throwError(() => new Error('Response callback function does not exist!')); + return throwError( + () => new Error('Response callback function does not exist!') + ); } // Ensure the request exists diff --git a/src/@angor/lib/mock-api/mock-api.service.ts b/src/@angor/lib/mock-api/mock-api.service.ts index a74c081..d4e2189 100644 --- a/src/@angor/lib/mock-api/mock-api.service.ts +++ b/src/@angor/lib/mock-api/mock-api.service.ts @@ -1,11 +1,14 @@ -import { Injectable } from '@angular/core'; import { AngorMockApiHandler } from '@angor/lib/mock-api/mock-api.request-handler'; import { AngorMockApiMethods } from '@angor/lib/mock-api/mock-api.types'; -import { compact, fromPairs } from 'lodash-es'; +import { Injectable } from '@angular/core'; +import { fromPairs } from 'lodash-es'; @Injectable({ providedIn: 'root' }) export class AngorMockApiService { - private readonly _handlers: Record> = { + private readonly _handlers: Record< + AngorMockApiMethods, + Map + > = { get: new Map(), post: new Map(), patch: new Map(), @@ -26,24 +29,36 @@ export class AngorMockApiService { findHandler( method: AngorMockApiMethods, url: string - ): { handler: AngorMockApiHandler | undefined; urlParams: Record } { - const matchingHandler = { handler: undefined, urlParams: {} as Record }; + ): { + handler: AngorMockApiHandler | undefined; + urlParams: Record; + } { + const matchingHandler = { + handler: undefined, + urlParams: {} as Record, + }; const urlParts = url.split('/'); - const handlers = this._handlers[method.toLowerCase() as AngorMockApiMethods]; + const handlers = + this._handlers[method.toLowerCase() as AngorMockApiMethods]; for (const [handlerUrl, handler] of handlers) { const handlerUrlParts = handlerUrl.split('/'); if (urlParts.length === handlerUrlParts.length) { - const matches = handlerUrlParts.every((part, index) => - part.startsWith(':') || part === urlParts[index] + const matches = handlerUrlParts.every( + (part, index) => + part.startsWith(':') || part === urlParts[index] ); if (matches) { matchingHandler.handler = handler; matchingHandler.urlParams = fromPairs( handlerUrlParts - .map((part, index) => (part.startsWith(':') ? [part.substring(1), urlParts[index]] : undefined)) + .map((part, index) => + part.startsWith(':') + ? [part.substring(1), urlParts[index]] + : undefined + ) .filter(Boolean) ); break; @@ -150,7 +165,11 @@ export class AngorMockApiService { * @param delay - (Optional) Delay for the response in milliseconds * @returns An instance of AngorMockApiHandler */ - private _registerHandler(method: AngorMockApiMethods, url: string, delay?: number): AngorMockApiHandler { + private _registerHandler( + method: AngorMockApiMethods, + url: string, + delay?: number + ): AngorMockApiHandler { const handler = new AngorMockApiHandler(url, delay); this._handlers[method].set(url, handler); return handler; diff --git a/src/@angor/pipes/find-by-key/find-by-key.pipe.ts b/src/@angor/pipes/find-by-key/find-by-key.pipe.ts index 44ba391..4885afe 100644 --- a/src/@angor/pipes/find-by-key/find-by-key.pipe.ts +++ b/src/@angor/pipes/find-by-key/find-by-key.pipe.ts @@ -18,7 +18,11 @@ export class AngorFindByKeyPipe implements PipeTransform { * @param source The array of objects to search within. * @returns A single object if `value` is a string, or an array of objects if `value` is an array. */ - transform(value: string | string[], key: string, source: any[]): any | any[] { + transform( + value: string | string[], + key: string, + source: any[] + ): any | any[] { // If value is an array of strings, map each to its corresponding object in the source. if (Array.isArray(value)) { return value.map((item) => diff --git a/src/@angor/services/config/config.service.ts b/src/@angor/services/config/config.service.ts index 4199db8..b6991bf 100644 --- a/src/@angor/services/config/config.service.ts +++ b/src/@angor/services/config/config.service.ts @@ -1,12 +1,14 @@ -import { inject, Injectable } from '@angular/core'; import { ANGOR_CONFIG } from '@angor/services/config/config.constants'; +import { inject, Injectable } from '@angular/core'; import { merge } from 'lodash-es'; import { BehaviorSubject, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AngorConfigService { private readonly _defaultConfig = inject(ANGOR_CONFIG); - private readonly _configSubject = new BehaviorSubject(this._defaultConfig); + private readonly _configSubject = new BehaviorSubject( + this._defaultConfig + ); /** * Getter for config as an Observable. diff --git a/src/@angor/services/config/config.types.ts b/src/@angor/services/config/config.types.ts index 5eda7b0..eafc614 100644 --- a/src/@angor/services/config/config.types.ts +++ b/src/@angor/services/config/config.types.ts @@ -9,9 +9,9 @@ export type Themes = Array<{ id: string; name: string }>; * This ensures consistency when defining or updating app settings. */ export interface AngorConfig { - layout: string; // Layout type (e.g., 'vertical', 'horizontal') - scheme: Scheme; // Color scheme: 'auto', 'dark', or 'light' - screens: Screens; // Screen breakpoints, e.g., { 'xs': '600px', ... } - theme: Theme; // Active theme identifier, e.g., 'theme-default' - themes: Themes; // List of available themes, each with an id and name + layout: string; // Layout type (e.g., 'vertical', 'horizontal') + scheme: Scheme; // Color scheme: 'auto', 'dark', or 'light' + screens: Screens; // Screen breakpoints, e.g., { 'xs': '600px', ... } + theme: Theme; // Active theme identifier, e.g., 'theme-default' + themes: Themes; // List of available themes, each with an id and name } diff --git a/src/@angor/services/confirmation/confirmation.service.ts b/src/@angor/services/confirmation/confirmation.service.ts index c48c2c2..72b1fc0 100644 --- a/src/@angor/services/confirmation/confirmation.service.ts +++ b/src/@angor/services/confirmation/confirmation.service.ts @@ -1,7 +1,7 @@ -import { inject, Injectable } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { AngorConfirmationConfig } from '@angor/services/confirmation/confirmation.types'; import { AngorConfirmationDialogComponent } from '@angor/services/confirmation/dialog/dialog.component'; +import { inject, Injectable } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { merge } from 'lodash-es'; @Injectable({ providedIn: 'root' }) diff --git a/src/@angor/services/confirmation/dialog/dialog.component.ts b/src/@angor/services/confirmation/dialog/dialog.component.ts index 455a491..51dc48c 100644 --- a/src/@angor/services/confirmation/dialog/dialog.component.ts +++ b/src/@angor/services/confirmation/dialog/dialog.component.ts @@ -1,9 +1,9 @@ +import { AngorConfirmationConfig } from '@angor/services/confirmation/confirmation.types'; import { NgClass } from '@angular/common'; import { Component, ViewEncapsulation, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; -import { AngorConfirmationConfig } from '@angor/services/confirmation/confirmation.types'; @Component({ selector: 'angor-confirmation-dialog', diff --git a/src/@angor/services/loading/loading.interceptor.ts b/src/@angor/services/loading/loading.interceptor.ts index c715411..4407b26 100644 --- a/src/@angor/services/loading/loading.interceptor.ts +++ b/src/@angor/services/loading/loading.interceptor.ts @@ -1,6 +1,6 @@ +import { AngorLoadingService } from '@angor/services/loading/loading.service'; import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http'; import { inject } from '@angular/core'; -import { AngorLoadingService } from '@angor/services/loading/loading.service'; import { Observable, finalize, take } from 'rxjs'; export const angorLoadingInterceptor = ( diff --git a/src/@angor/services/media-watcher/media-watcher.service.ts b/src/@angor/services/media-watcher/media-watcher.service.ts index 8e14c95..ebd4181 100644 --- a/src/@angor/services/media-watcher/media-watcher.service.ts +++ b/src/@angor/services/media-watcher/media-watcher.service.ts @@ -1,6 +1,6 @@ +import { AngorConfigService } from '@angor/services/config'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { Injectable, inject } from '@angular/core'; -import { AngorConfigService } from '@angor/services/config'; import { fromPairs } from 'lodash-es'; import { Observable, ReplaySubject, map, switchMap } from 'rxjs'; diff --git a/src/@angor/styles/components/example-viewer.scss b/src/@angor/styles/components/example-viewer.scss index 157feb3..fa1c4ff 100644 --- a/src/@angor/styles/components/example-viewer.scss +++ b/src/@angor/styles/components/example-viewer.scss @@ -1,6 +1,3 @@ -/* ----------------------------------------------------------------------------------------------------- */ -/* @ Example viewer -/* ----------------------------------------------------------------------------------------------------- */ .example-viewer { display: flex; flex-direction: column; diff --git a/src/@angor/styles/themes.scss b/src/@angor/styles/themes.scss index 24eaa12..50972f1 100644 --- a/src/@angor/styles/themes.scss +++ b/src/@angor/styles/themes.scss @@ -80,7 +80,6 @@ $dark-base: ( ), ); - /* Include the core Angular Material styles */ @include mat.core(); diff --git a/src/@angor/tailwind/plugins/theming.js b/src/@angor/tailwind/plugins/theming.js index fd1d743..0cac64f 100644 --- a/src/@angor/tailwind/plugins/theming.js +++ b/src/@angor/tailwind/plugins/theming.js @@ -13,17 +13,6 @@ const jsonToSassMap = require( path.resolve(__dirname, '../utils/json-to-sass-map') ); -// ----------------------------------------------------------------------------------------------------- -// @ Utilities -// ----------------------------------------------------------------------------------------------------- - -/** - * Normalizes the provided theme by omitting empty values and values that - * start with "on" from each palette. Also sets the correct DEFAULT value - * of each palette. - * - * @param theme - */ const normalizeTheme = (theme) => { return _.fromPairs( _.map( @@ -43,17 +32,10 @@ const normalizeTheme = (theme) => { ); }; -// ----------------------------------------------------------------------------------------------------- -// @ ANGOR TailwindCSS Main Plugin -// ----------------------------------------------------------------------------------------------------- const theming = plugin.withOptions( (options) => ({ addComponents, e, theme }) => { - /** - * Create user themes object by going through the provided themes and - * merging them with the provided "default" so, we can have a complete - * set of color palettes for each user theme. - */ + const userThemes = _.fromPairs( _.map(options.themes, (theme, themeName) => [ themeName, @@ -61,10 +43,6 @@ const theming = plugin.withOptions( ]) ); - /** - * Normalize the themes and assign it to the themes object. This will - * be the final object that we create a SASS map from - */ let themes = _.fromPairs( _.map(userThemes, (theme, themeName) => [ themeName, @@ -72,10 +50,6 @@ const theming = plugin.withOptions( ]) ); - /** - * Go through the themes to generate the contrasts and filter the - * palettes to only have "primary", "accent" and "warn" objects. - */ themes = _.fromPairs( _.map(themes, (theme, themeName) => [ themeName, @@ -105,10 +79,6 @@ const theming = plugin.withOptions( ]) ); - /** - * Go through the themes and attach appropriate class selectors so, - * we can use them to encapsulate each theme. - */ themes = _.fromPairs( _.map(themes, (theme, themeName) => [ themeName, @@ -119,18 +89,15 @@ const theming = plugin.withOptions( ]) ); - /* Generate the SASS map using the themes object */ const sassMap = jsonToSassMap( JSON.stringify({ 'user-themes': themes }) ); - /* Get the file path */ const filename = path.resolve( __dirname, '../../styles/user-themes.scss' ); - /* Read the file and get its data */ let data; try { data = fs.readFileSync(filename, { encoding: 'utf8' }); @@ -138,7 +105,6 @@ const theming = plugin.withOptions( console.error(err); } - /* Write the file if the map has been changed */ if (data !== sassMap) { try { fs.writeFileSync(filename, sassMap, { encoding: 'utf8' }); @@ -147,11 +113,6 @@ const theming = plugin.withOptions( } } - /** - * Iterate through the user's themes and build Tailwind components containing - * CSS Custom Properties using the colors from them. This allows switching - * themes by simply replacing a class name as well as nesting them. - */ addComponents( _.fromPairs( _.map(options.themes, (theme, themeName) => [ @@ -214,9 +175,7 @@ const theming = plugin.withOptions( ) ); - /** - * Generate scheme based css custom properties and utility classes - */ + const schemeCustomProps = _.map( ['light', 'dark'], (colorScheme) => { @@ -234,31 +193,9 @@ const theming = plugin.withOptions( return { [isDark ? darkSchemeSelectors : lightSchemeSelectors]: { - /** - * If a custom property is not available, browsers will use - * the fallback value. In this case, we want to use '--is-dark' - * as the indicator of a dark theme so, we can use it like this: - * background-color: var(--is-dark, red); - * - * If we set '--is-dark' as "true" on dark themes, the above rule - * won't work because of the said "fallback value" logic. Therefore, - * we set the '--is-dark' to "false" on light themes and not set it - * at all on dark themes so that the fallback value can be used on - * dark themes. - * - * On light themes, since '--is-dark' exists, the above rule will be - * interpolated as: - * "background-color: false" - * - * On dark themes, since '--is-dark' doesn't exist, the fallback value - * will be used ('red' in this case) and the rule will be interpolated as: - * "background-color: red" - * - * It's easier to understand and remember like this. - */ + ...(!isDark ? { '--is-dark': 'false' } : {}), - /* Generate custom properties from customProps */ ..._.fromPairs( _.flatten( _.map(background, (value, key) => [ @@ -287,7 +224,6 @@ const theming = plugin.withOptions( ); const schemeUtilities = (() => { - /* Generate general styles & utilities */ return {}; })(); @@ -298,11 +234,6 @@ const theming = plugin.withOptions( return { theme: { extend: { - /** - * Add 'Primary', 'Accent' and 'Warn' palettes as colors so all color utilities - * are generated for them; "bg-primary", "text-on-primary", "bg-accent-600" etc. - * This will also allow using arbitrary values with them such as opacity and such. - */ colors: _.fromPairs( _.flatten( _.map( @@ -398,7 +329,6 @@ const theming = plugin.withOptions( }, }, }, - }, }; } diff --git a/src/@angor/tailwind/plugins/utilities.js b/src/@angor/tailwind/plugins/utilities.js index ccad64c..30ce41e 100644 --- a/src/@angor/tailwind/plugins/utilities.js +++ b/src/@angor/tailwind/plugins/utilities.js @@ -1,11 +1,6 @@ const plugin = require('tailwindcss/plugin'); module.exports = plugin(({ addComponents }) => { - /* - * Add base components. These are very important for everything to look - * correct. We are adding these to the 'components' layer because they must - * be defined before pretty much everything else. - */ addComponents({ '.mat-icon': { '--tw-text-opacity': '1', diff --git a/src/app/app.component.scss b/src/app/app.component.scss index a8b9eb0..9c62f14 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,6 +1,6 @@ :host { display: flex; - flex: 1 1 auto; // Flex-grow, flex-shrink, and basis set to auto - width: 100%; // Full width of the container - height: 100%; // Full height of the container + flex: 1 1 auto; // Flex-grow, flex-shrink, and basis set to auto + width: 100%; // Full width of the container + height: 100%; // Full height of the container } diff --git a/src/app/app.config.ts b/src/app/app.config.ts index c49ccef..64a603b 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,5 +1,11 @@ +import { provideAngor } from '@angor'; import { provideHttpClient } from '@angular/common/http'; -import { APP_INITIALIZER, ApplicationConfig, inject, isDevMode } from '@angular/core'; +import { + APP_INITIALIZER, + ApplicationConfig, + inject, + isDevMode, +} from '@angular/core'; import { LuxonDateAdapter } from '@angular/material-luxon-adapter'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; import { provideAnimations } from '@angular/platform-browser/animations'; @@ -9,20 +15,18 @@ import { withInMemoryScrolling, withPreloading, } from '@angular/router'; -import { provideAngor } from '@angor'; +import { provideServiceWorker } from '@angular/service-worker'; import { TranslocoService, provideTransloco } from '@ngneat/transloco'; +import { WebLNProvider } from '@webbtc/webln-types'; import { appRoutes } from 'app/app.routes'; import { provideIcons } from 'app/core/icons/icons.provider'; import { firstValueFrom } from 'rxjs'; import { TranslocoHttpLoader } from './core/transloco/transloco.http-loader'; -import { provideServiceWorker } from '@angular/service-worker'; -import { HashService } from './services/hash.service'; import { navigationServices } from './layout/navigation/navigation.services'; -import { WebLNProvider } from '@webbtc/webln-types'; +import { HashService } from './services/hash.service'; import { NostrWindow } from './types/nostr'; export function initializeApp(hashService: HashService) { - console.log('initializeApp. Getting hashService.load.'); return (): Promise => hashService.load(); } export const appConfig: ApplicationConfig = { @@ -31,7 +35,7 @@ export const appConfig: ApplicationConfig = { provideHttpClient(), provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode(), - registrationStrategy: 'registerWhenStable:30000' + registrationStrategy: 'registerWhenStable:30000', }), { provide: APP_INITIALIZER, @@ -54,12 +58,12 @@ export const appConfig: ApplicationConfig = { provide: MAT_DATE_FORMATS, useValue: { parse: { - dateInput: 'D', // Date format for parsing + dateInput: 'D', // Date format for parsing }, display: { - dateInput: 'DDD', // Date format for input display - monthYearLabel: 'LLL yyyy', // Format for month-year labels - dateA11yLabel: 'DD', // Accessible format for dates + dateInput: 'DDD', // Date format for input display + monthYearLabel: 'LLL yyyy', // Format for month-year labels + dateA11yLabel: 'DD', // Accessible format for dates monthYearA11yLabel: 'LLLL yyyy', // Accessible format for month-year }, }, @@ -72,7 +76,7 @@ export const appConfig: ApplicationConfig = { { id: 'en', label: 'English', - } + }, ], defaultLang: 'en', fallbackLang: 'en', @@ -121,13 +125,11 @@ export const appConfig: ApplicationConfig = { ], }, }), - ], }; declare global { interface Window { - webln?: WebLNProvider; - nostr?: NostrWindow; + webln?: WebLNProvider; + nostr?: NostrWindow; } - } - +} diff --git a/src/app/app.resolvers.ts b/src/app/app.resolvers.ts index 30ccba7..0ae4057 100644 --- a/src/app/app.resolvers.ts +++ b/src/app/app.resolvers.ts @@ -9,11 +9,11 @@ import { forkJoin } from 'rxjs'; */ export const initialDataResolver = () => { const navigationService = inject(NavigationService); - const quickChatService = inject(QuickChatService); + const quickChatService = inject(QuickChatService); // Combine API calls into a single observable return forkJoin([ - navigationService.get(), // Fetch navigation data + navigationService.get(), // Fetch navigation data // quickChatService.getChats(), // Fetch chat data ]); }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index e64ac3e..d302ed5 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,31 +1,30 @@ import { Route } from '@angular/router'; import { initialDataResolver } from 'app/app.resolvers'; - import { LayoutComponent } from 'app/layout/layout.component'; +import { LayoutComponent } from 'app/layout/layout.component'; import { authGuard } from './core/auth/auth.guard'; /** * Application routes configuration */ export const appRoutes: Route[] = [ - // Redirect root path to '/explore' { path: '', pathMatch: 'full', - redirectTo: 'home' + redirectTo: 'home', }, { path: 'project/:pubkey', pathMatch: 'full', - redirectTo: 'explore' + redirectTo: 'explore', }, // Redirect login user to '/explore' { path: 'login-redirect', pathMatch: 'full', - redirectTo: 'explore' + redirectTo: 'explore', }, // Routes for guests @@ -36,13 +35,15 @@ export const appRoutes: Route[] = [ children: [ { path: 'login', - loadChildren: () => import('app/components/auth/login/login.routes') + loadChildren: () => + import('app/components/auth/login/login.routes'), }, { path: 'register', - loadChildren: () => import('app/components/auth/register/register.routes') - } - ] + loadChildren: () => + import('app/components/auth/register/register.routes'), + }, + ], }, // Routes for authenticated users @@ -55,13 +56,12 @@ export const appRoutes: Route[] = [ children: [ { path: 'logout', - loadChildren: () => import('app/components/auth/logout/logout.routes') - } - ] + loadChildren: () => + import('app/components/auth/logout/logout.routes'), + }, + ], }, - - // Authenticated routes for Angor { path: '', @@ -72,41 +72,49 @@ export const appRoutes: Route[] = [ children: [ { path: 'home', - loadChildren: () => import('app/components/home/home.routes') + loadChildren: () => import('app/components/home/home.routes'), }, { path: 'explore', - loadChildren: () => import('app/components/explore/explore.routes') + loadChildren: () => + import('app/components/explore/explore.routes'), }, { path: 'profile', - loadChildren: () => import('app/components/profile/profile.routes') + loadChildren: () => + import('app/components/profile/profile.routes'), }, { path: 'profile/:pubkey', - loadChildren: () => import('app/components/profile/profile.routes') + loadChildren: () => + import('app/components/profile/profile.routes'), }, { path: 'settings', - loadChildren: () => import('app/components/settings/settings.routes') + loadChildren: () => + import('app/components/settings/settings.routes'), }, { path: 'settings/:id', - loadChildren: () => import('app/components/settings/settings.routes') + loadChildren: () => + import('app/components/settings/settings.routes'), }, { path: 'chat', - loadChildren: () => import('app/components/chat/chat.routes') + loadChildren: () => import('app/components/chat/chat.routes'), }, { path: '404-not-found', pathMatch: 'full', - loadChildren: () => import('app/components/pages/error/error-404/error-404.routes') + loadChildren: () => + import( + 'app/components/pages/error/error-404/error-404.routes' + ), }, { path: '**', - redirectTo: '404-not-found' - } - ] - } + redirectTo: '404-not-found', + }, + ], + }, ]; diff --git a/src/app/components/auth/login/login.component.html b/src/app/components/auth/login/login.component.html index ab4633f..2529259 100644 --- a/src/app/components/auth/login/login.component.html +++ b/src/app/components/auth/login/login.component.html @@ -1,18 +1,33 @@ -
+
+ class="w-full px-4 py-8 sm:bg-card sm:w-auto sm:rounded-2xl sm:p-12 sm:shadow md:flex md:h-full md:w-1/2 md:items-center md:justify-end md:rounded-none md:p-16 md:shadow-none" + >
-
+
Login
Don't have an account?
- Register + Register
- + {{ secAlert.message }} @@ -26,8 +41,16 @@
-
@@ -40,7 +63,11 @@
-
+
@@ -49,8 +76,14 @@
Secret Key - - @if (SecretKeyLoginForm.get('secretKey').hasError('required')) { + + @if ( + SecretKeyLoginForm.get('secretKey').hasError('required') + ) { Secret key is required } @@ -58,47 +91,94 @@ Password - - - Password is required + + Password is required -
-
Or enter menemonic
- + {{ menemonicAlert.message }} -
+ Menemonic - - @if (MenemonicLoginForm.get('menemonic').hasError('required')) { + + @if ( + MenemonicLoginForm.get('menemonic').hasError('required') + ) { Menemonic is required } @@ -106,66 +186,156 @@ Passphrase (Optional) - - - Passphrase is required + + Passphrase is required Password - - - Password is required + + Password is required -
diff --git a/src/app/components/auth/logout/logout.component.ts b/src/app/components/auth/logout/logout.component.ts index 43d89de..4a476fd 100644 --- a/src/app/components/auth/logout/logout.component.ts +++ b/src/app/components/auth/logout/logout.component.ts @@ -19,7 +19,10 @@ export class LogoutComponent implements OnInit, OnDestroy { }; private _unsubscribeAll: Subject = new Subject(); - constructor(private _router: Router, private _signerService: SignerService) {} + constructor( + private _router: Router, + private _signerService: SignerService + ) {} ngOnInit(): void { timer(1000, 1000) @@ -35,7 +38,6 @@ export class LogoutComponent implements OnInit, OnDestroy { .subscribe(); } - ngOnDestroy(): void { this._unsubscribeAll.next(null); this._unsubscribeAll.complete(); @@ -44,6 +46,6 @@ export class LogoutComponent implements OnInit, OnDestroy { logout(): void { this._signerService.clearPassword(); this._signerService.logout(); - console.log("User logged out and keys removed from localStorage."); + console.log('User logged out and keys removed from localStorage.'); } } diff --git a/src/app/components/auth/register/register.component.html b/src/app/components/auth/register/register.component.html index dcb51fe..4da0a84 100644 --- a/src/app/components/auth/register/register.component.html +++ b/src/app/components/auth/register/register.component.html @@ -1,107 +1,231 @@ -
+
+ class="w-full px-4 py-8 sm:bg-card sm:w-auto sm:rounded-2xl sm:p-12 sm:shadow md:flex md:h-full md:w-1/2 md:items-center md:justify-end md:rounded-none md:p-16 md:shadow-none" + >
-
+
Register
Already have an account?
- Login + Login
@if (showAlert) { - - {{ alert.message }} - + + {{ alert.message }} + } -
+ Full name - - Full name is required + + + Full name is required + Username - - Username is required + + + Username is required + About - + Avatar URL - + - + Password - - - Password is required + + Password is required +
- + I agree with - Terms + Terms and - Privacy Policy + Privacy Policy
-
-
diff --git a/src/app/components/auth/register/register.component.ts b/src/app/components/auth/register/register.component.ts index cba6cd1..a143e9c 100644 --- a/src/app/components/auth/register/register.component.ts +++ b/src/app/components/auth/register/register.component.ts @@ -1,3 +1,6 @@ +import { angorAnimations } from '@angor/animations'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; +import { CommonModule } from '@angular/common'; import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { FormsModule, @@ -14,11 +17,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { Router, RouterLink } from '@angular/router'; -import { angorAnimations } from '@angor/animations'; -import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; -import { SecurityService } from 'app/services/security.service'; import { SignerService } from 'app/services/signer.service'; -import { CommonModule } from '@angular/common'; @Component({ selector: 'auth-register', @@ -37,7 +36,7 @@ import { CommonModule } from '@angular/common'; MatIconModule, MatCheckboxModule, MatProgressSpinnerModule, - CommonModule + CommonModule, ], }) export class RegisterComponent implements OnInit { @@ -89,7 +88,10 @@ export class RegisterComponent implements OnInit { if (!keys) { // If key generation failed, enable the form and show an error this.registerForm.enable(); - this.alert = { type: 'error', message: 'Error generating keys. Please try again.' }; + this.alert = { + type: 'error', + message: 'Error generating keys. Please try again.', + }; this.showAlert = true; return; } @@ -112,11 +114,13 @@ export class RegisterComponent implements OnInit { console.log('User Metadata:', userMetadata); // Display success alert - this.alert = { type: 'success', message: 'Account created successfully!' }; + this.alert = { + type: 'success', + message: 'Account created successfully!', + }; this.showAlert = true; // Redirect to home this._router.navigateByUrl('/home'); } - } diff --git a/src/app/components/chat/chat.routes.ts b/src/app/components/chat/chat.routes.ts index e0c9af8..6a75382 100644 --- a/src/app/components/chat/chat.routes.ts +++ b/src/app/components/chat/chat.routes.ts @@ -25,7 +25,8 @@ const conversationResolver = ( const chatService = inject(ChatService); const router = inject(Router); - let chatId = route.paramMap.get('id') || localStorage.getItem('currentChatId'); + let chatId = + route.paramMap.get('id') || localStorage.getItem('currentChatId'); if (!chatId) { const parentUrl = state.url.split('/').slice(0, -1).join('/'); @@ -45,7 +46,6 @@ const conversationResolver = ( ); }; - export default [ { path: '', diff --git a/src/app/components/chat/chat.service.ts b/src/app/components/chat/chat.service.ts index 6609af0..e44d791 100644 --- a/src/app/components/chat/chat.service.ts +++ b/src/app/components/chat/chat.service.ts @@ -1,14 +1,28 @@ import { Injectable, OnDestroy } from '@angular/core'; -import { BehaviorSubject, Observable, Subject, throwError, of, Subscriber, from } from 'rxjs'; -import { catchError, filter, map, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { Chat, Contact, Profile } from 'app/components/chat/chat.types'; import { IndexedDBService } from 'app/services/indexed-db.service'; import { MetadataService } from 'app/services/metadata.service'; -import { SignerService } from 'app/services/signer.service'; -import { Filter, NostrEvent } from 'nostr-tools'; import { RelayService } from 'app/services/relay.service'; +import { SignerService } from 'app/services/signer.service'; +import { Filter, getEventHash, NostrEvent } from 'nostr-tools'; import { EncryptedDirectMessage } from 'nostr-tools/kinds'; -import { getEventHash } from 'nostr-tools'; +import { + BehaviorSubject, + from, + Observable, + of, + Subject, + throwError, +} from 'rxjs'; +import { + catchError, + filter, + map, + switchMap, + take, + takeUntil, + tap, +} from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class ChatService implements OnDestroy { @@ -18,22 +32,26 @@ export class ChatService implements OnDestroy { private isDecrypting = false; private recipientPublicKey: string; private message: string; - private decryptedPrivateKey: string = ""; + private decryptedPrivateKey: string = ''; private _chat: BehaviorSubject = new BehaviorSubject(null); private _chats: BehaviorSubject = new BehaviorSubject(null); - private _contact: BehaviorSubject = new BehaviorSubject(null); - private _contacts: BehaviorSubject = new BehaviorSubject(null); - private _profile: BehaviorSubject = new BehaviorSubject(null); + private _contact: BehaviorSubject = new BehaviorSubject( + null + ); + private _contacts: BehaviorSubject = new BehaviorSubject( + null + ); + private _profile: BehaviorSubject = new BehaviorSubject( + null + ); private _unsubscribeAll: Subject = new Subject(); constructor( private _metadataService: MetadataService, private _signerService: SignerService, private _indexedDBService: IndexedDBService, - private _relayService: RelayService, - - - ) { } + private _relayService: RelayService + ) {} get profile$(): Observable { return this._profile.asObservable(); } @@ -54,46 +72,50 @@ export class ChatService implements OnDestroy { return this._contacts.asObservable(); } - checkCurrentChatOnPageRefresh(chatIdFromURL: string): void { if (chatIdFromURL) { const currentChat = this._chat.value; - this.getChatById(chatIdFromURL).subscribe(chat => { + this.getChatById(chatIdFromURL).subscribe((chat) => { if (chat) { this._chat.next(chat); this.loadChatHistory(chatIdFromURL); } }); - } } - async getContact(pubkey: string): Promise { try { if (!pubkey) { return; } - const metadata = await this._metadataService.fetchMetadataWithCache(pubkey); + const metadata = + await this._metadataService.fetchMetadataWithCache(pubkey); if (metadata) { const contact: Contact = { pubKey: pubkey, displayName: metadata.name ? metadata.name : 'Unknown', picture: metadata.picture, - about: metadata.about + about: metadata.about, }; this._contact.next(contact); - this._indexedDBService.getMetadataStream() + this._indexedDBService + .getMetadataStream() .pipe(takeUntil(this._unsubscribeAll)) .subscribe((updatedMetadata) => { - if (updatedMetadata && updatedMetadata.pubkey === pubkey) { + if ( + updatedMetadata && + updatedMetadata.pubkey === pubkey + ) { const updatedContact: Contact = { pubKey: pubkey, - displayName: updatedMetadata.metadata.name ? updatedMetadata.metadata.name : 'Unknown', + displayName: updatedMetadata.metadata.name + ? updatedMetadata.metadata.name + : 'Unknown', picture: updatedMetadata.metadata.picture, - about: updatedMetadata.metadata.about + about: updatedMetadata.metadata.about, }; this._contact.next(updatedContact); } @@ -104,18 +126,23 @@ export class ChatService implements OnDestroy { } } - getContacts(): Observable { return new Observable((observer) => { - this._indexedDBService.getAllUsers() + this._indexedDBService + .getAllUsers() .then((cachedContacts: Contact[]) => { if (cachedContacts && cachedContacts.length > 0) { - const validatedContacts = cachedContacts.map(contact => { - if (!contact.pubKey) { - console.error('Contact is missing pubKey:', contact); + const validatedContacts = cachedContacts.map( + (contact) => { + if (!contact.pubKey) { + console.error( + 'Contact is missing pubKey:', + contact + ); + } + return contact; } - return contact; - }); + ); this._contacts.next(validatedContacts); observer.next(validatedContacts); @@ -125,33 +152,49 @@ export class ChatService implements OnDestroy { observer.complete(); }) .catch((error) => { - console.error('Error loading cached contacts from IndexedDB:', error); + console.error( + 'Error loading cached contacts from IndexedDB:', + error + ); observer.next([]); observer.complete(); }); - return { unsubscribe() { } }; + return { unsubscribe() {} }; }); } async updateChatListMetadata(): Promise { - const pubKeys = this.chatList.map(chat => chat.contact?.pubKey).filter(pubKey => pubKey); + const pubKeys = this.chatList + .map((chat) => chat.contact?.pubKey) + .filter((pubKey) => pubKey); if (pubKeys.length > 0) { - const metadataList = await this._metadataService.fetchMetadataForMultipleKeys(pubKeys); + const metadataList = + await this._metadataService.fetchMetadataForMultipleKeys( + pubKeys + ); - metadataList.forEach(metadata => { - const contact = this._contacts.value?.find(contact => contact.pubKey === metadata.pubkey); + metadataList.forEach((metadata) => { + const contact = this._contacts.value?.find( + (contact) => contact.pubKey === metadata.pubkey + ); if (contact) { contact.displayName = metadata.metadata.name || 'Unknown'; - contact.picture = metadata.metadata.picture || contact.picture; + contact.picture = + metadata.metadata.picture || contact.picture; contact.about = metadata.metadata.about || contact.about; } - const chat = this.chatList.find(chat => chat.contact?.pubKey === metadata.pubkey); + const chat = this.chatList.find( + (chat) => chat.contact?.pubKey === metadata.pubkey + ); if (chat) { - chat.contact.displayName = metadata.metadata.name || 'Unknown'; - chat.contact.picture = metadata.metadata.picture || chat.contact.picture; - chat.contact.about = metadata.metadata.about || chat.contact.about; + chat.contact.displayName = + metadata.metadata.name || 'Unknown'; + chat.contact.picture = + metadata.metadata.picture || chat.contact.picture; + chat.contact.about = + metadata.metadata.about || chat.contact.about; } }); @@ -161,40 +204,60 @@ export class ChatService implements OnDestroy { } private subscribeToRealTimeMetadataUpdates(pubKey: string): void { - this._metadataService.getMetadataStream() - .pipe(filter(updatedMetadata => updatedMetadata && updatedMetadata.pubkey === pubKey)) - .subscribe(updatedMetadata => { - const chat = this.chatList.find(chat => chat.contact?.pubKey === pubKey); + this._metadataService + .getMetadataStream() + .pipe( + filter( + (updatedMetadata) => + updatedMetadata && updatedMetadata.pubkey === pubKey + ) + ) + .subscribe((updatedMetadata) => { + const chat = this.chatList.find( + (chat) => chat.contact?.pubKey === pubKey + ); if (chat) { - chat.contact.displayName = updatedMetadata.metadata.name || 'Unknown'; - chat.contact.picture = updatedMetadata.metadata.picture || chat.contact.picture; - chat.contact.about = updatedMetadata.metadata.about || chat.contact.about; + chat.contact.displayName = + updatedMetadata.metadata.name || 'Unknown'; + chat.contact.picture = + updatedMetadata.metadata.picture || + chat.contact.picture; + chat.contact.about = + updatedMetadata.metadata.about || chat.contact.about; this._chats.next(this.chatList); } - const contact = this._contacts.value?.find(contact => contact.pubKey === pubKey); + const contact = this._contacts.value?.find( + (contact) => contact.pubKey === pubKey + ); if (contact) { - contact.displayName = updatedMetadata.metadata.name || 'Unknown'; - contact.picture = updatedMetadata.metadata.picture || contact.picture; - contact.about = updatedMetadata.metadata.about || contact.about; + contact.displayName = + updatedMetadata.metadata.name || 'Unknown'; + contact.picture = + updatedMetadata.metadata.picture || contact.picture; + contact.about = + updatedMetadata.metadata.about || contact.about; this._contacts.next(this._contacts.value || []); } }); } - async getProfile(): Promise { try { const publicKey = this._signerService.getPublicKey(); - const metadata = await this._metadataService.fetchMetadataWithCache(publicKey); + const metadata = + await this._metadataService.fetchMetadataWithCache(publicKey); if (metadata) { this._profile.next(metadata); - - this._indexedDBService.getMetadataStream() + this._indexedDBService + .getMetadataStream() .pipe(takeUntil(this._unsubscribeAll)) .subscribe((updatedMetadata) => { - if (updatedMetadata && updatedMetadata.pubkey === publicKey) { + if ( + updatedMetadata && + updatedMetadata.pubkey === publicKey + ) { this._profile.next(updatedMetadata.metadata); } }); @@ -206,14 +269,14 @@ export class ChatService implements OnDestroy { async getChats(): Promise> { return this.getChatListStream().pipe( - tap(chats => { + tap((chats) => { if (chats && chats.length === 0) { return; } const pubKeys = chats - .filter(chat => chat.contact?.pubKey) - .map(chat => chat.contact!.pubKey); + .filter((chat) => chat.contact?.pubKey) + .map((chat) => chat.contact!.pubKey); // Subscribe to all metadata updates in parallel this.subscribeToRealTimeMetadataUpdatesBatch(pubKeys); @@ -226,12 +289,19 @@ export class ChatService implements OnDestroy { const useExtension = await this._signerService.isUsingExtension(); const useSecretKey = await this._signerService.isUsingSecretKey(); - this.decryptedPrivateKey = useSecretKey ? await this._signerService.getDecryptedSecretKey() : ''; + this.decryptedPrivateKey = useSecretKey + ? await this._signerService.getDecryptedSecretKey() + : ''; // Perform metadata and chat loading in parallel for speed await Promise.all([ this.updateChatListMetadata(), - this.subscribeToChatList(pubkey, useExtension, useSecretKey, this.decryptedPrivateKey) + this.subscribeToChatList( + pubkey, + useExtension, + useSecretKey, + this.decryptedPrivateKey + ), ]); return this.getChatListStream(); @@ -239,46 +309,80 @@ export class ChatService implements OnDestroy { private subscribeToRealTimeMetadataUpdatesBatch(pubKeys: string[]): void { // Batch subscribe to all pubKeys metadata updates for efficiency - pubKeys.forEach(pubKey => { + pubKeys.forEach((pubKey) => { this.subscribeToRealTimeMetadataUpdates(pubKey); }); } - subscribeToChatList(pubkey: string, useExtension: boolean, useSecretKey: boolean, decryptedSenderPrivateKey: string): Observable { + subscribeToChatList( + pubkey: string, + useExtension: boolean, + useSecretKey: boolean, + decryptedSenderPrivateKey: string + ): Observable { this._relayService.ensureConnectedRelays().then(async () => { const filters: Filter[] = [ - { kinds: [EncryptedDirectMessage], authors: [pubkey] , limit:1500}, - { kinds: [EncryptedDirectMessage], '#p': [pubkey] , limit:1500} + { + kinds: [EncryptedDirectMessage], + authors: [pubkey], + limit: 1500, + }, + { + kinds: [EncryptedDirectMessage], + '#p': [pubkey], + limit: 1500, + }, ]; - this._relayService.getPool().subscribeMany(this._relayService.getConnectedRelays(), filters, { - onevent: async (event: NostrEvent) => { - const otherPartyPubKey = event.pubkey === pubkey - ? event.tags.find(tag => tag[0] === 'p')?.[1] || '' - : event.pubkey; + this._relayService + .getPool() + .subscribeMany( + this._relayService.getConnectedRelays(), + filters, + { + onevent: async (event: NostrEvent) => { + const otherPartyPubKey = + event.pubkey === pubkey + ? event.tags.find( + (tag) => tag[0] === 'p' + )?.[1] || '' + : event.pubkey; - if (!otherPartyPubKey) return; + if (!otherPartyPubKey) return; - const lastTimestamp = this.latestMessageTimestamps[otherPartyPubKey] || 0; - if (event.created_at > lastTimestamp) { - this.messageQueue.push(event); - this.processNextMessage(pubkey, useExtension, useSecretKey, decryptedSenderPrivateKey); + const lastTimestamp = + this.latestMessageTimestamps[ + otherPartyPubKey + ] || 0; + if (event.created_at > lastTimestamp) { + this.messageQueue.push(event); + this.processNextMessage( + pubkey, + useExtension, + useSecretKey, + decryptedSenderPrivateKey + ); + } + }, + oneose: () => { + const currentChats = this.chatList || []; + if (currentChats.length > 0) { + this._chats.next(this.chatList); + } + }, } - }, - oneose: () => { - - const currentChats = this.chatList || []; - if (currentChats.length > 0) { - this._chats.next(this.chatList); - } - } - }); + ); }); return this.getChatListStream(); } - private async processNextMessage(pubkey: string, useExtension: boolean, useSecretKey: boolean, decryptedSenderPrivateKey: string): Promise { + private async processNextMessage( + pubkey: string, + useExtension: boolean, + useSecretKey: boolean, + decryptedSenderPrivateKey: string + ): Promise { if (this.isDecrypting || this.messageQueue.length === 0) return; this.isDecrypting = true; @@ -290,7 +394,7 @@ export class ChatService implements OnDestroy { const isSentByUser = event.pubkey === pubkey; const otherPartyPubKey = isSentByUser - ? event.tags.find(tag => tag[0] === 'p')?.[1] || '' + ? event.tags.find((tag) => tag[0] === 'p')?.[1] || '' : event.pubkey; if (!otherPartyPubKey) continue; @@ -304,7 +408,12 @@ export class ChatService implements OnDestroy { ); if (decryptedMessage) { - this.addOrUpdateChatList(otherPartyPubKey, decryptedMessage, event.created_at, isSentByUser); + this.addOrUpdateChatList( + otherPartyPubKey, + decryptedMessage, + event.created_at, + isSentByUser + ); } } } catch (error) { @@ -314,8 +423,15 @@ export class ChatService implements OnDestroy { } } - private addOrUpdateChatList(pubKey: string, message: string, createdAt: number, isMine: boolean): void { - const existingChat = this.chatList.find(chat => chat.contact?.pubKey === pubKey); + private addOrUpdateChatList( + pubKey: string, + message: string, + createdAt: number, + isMine: boolean + ): void { + const existingChat = this.chatList.find( + (chat) => chat.contact?.pubKey === pubKey + ); const newMessage = { id: `${pubKey}-${createdAt}`, @@ -327,11 +443,19 @@ export class ChatService implements OnDestroy { }; if (existingChat) { - const messageExists = existingChat.messages?.some(m => m.id === newMessage.id); + const messageExists = existingChat.messages?.some( + (m) => m.id === newMessage.id + ); if (!messageExists) { - existingChat.messages = [...(existingChat.messages || []), newMessage] - .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()); + existingChat.messages = [ + ...(existingChat.messages || []), + newMessage, + ].sort( + (a, b) => + new Date(a.createdAt).getTime() - + new Date(b.createdAt).getTime() + ); if (Number(existingChat.lastMessageAt) < createdAt) { existingChat.lastMessage = message; @@ -339,25 +463,34 @@ export class ChatService implements OnDestroy { } } } else { - const contactInfo = this._contacts.value?.find(contact => contact.pubKey === pubKey) || { pubKey }; + const contactInfo = this._contacts.value?.find( + (contact) => contact.pubKey === pubKey + ) || { pubKey }; const newChat: Chat = { id: pubKey, contact: { pubKey: contactInfo.pubKey, - name: contactInfo.name || "Unknown", - picture: contactInfo.picture || "/images/avatars/avatar-placeholder.png", - about: contactInfo.about || "", - displayName: contactInfo.displayName || contactInfo.name || "Unknown" + name: contactInfo.name || 'Unknown', + picture: + contactInfo.picture || + '/images/avatars/avatar-placeholder.png', + about: contactInfo.about || '', + displayName: + contactInfo.displayName || + contactInfo.name || + 'Unknown', }, lastMessage: message, lastMessageAt: createdAt.toString(), - messages: [newMessage] + messages: [newMessage], }; this.chatList.push(newChat); } - this.chatList.sort((a, b) => Number(b.lastMessageAt!) - Number(a.lastMessageAt!)); + this.chatList.sort( + (a, b) => Number(b.lastMessageAt!) - Number(a.lastMessageAt!) + ); this._chats.next(this.chatList); } @@ -373,9 +506,16 @@ export class ChatService implements OnDestroy { recipientPublicKey: string ): Promise { if (useExtension && !useSecretKey) { - return await this._signerService.decryptMessageWithExtension(recipientPublicKey, event.content); + return await this._signerService.decryptMessageWithExtension( + recipientPublicKey, + event.content + ); } else if (useSecretKey && !useExtension) { - return await this._signerService.decryptMessage(decryptedSenderPrivateKey, recipientPublicKey, event.content); + return await this._signerService.decryptMessage( + decryptedSenderPrivateKey, + recipientPublicKey, + event.content + ); } } @@ -383,83 +523,143 @@ export class ChatService implements OnDestroy { const myPubKey = this._signerService.getPublicKey(); const historyFilter: Filter[] = [ - { kinds: [EncryptedDirectMessage], authors: [myPubKey], '#p': [pubKey], limit: 10 }, - { kinds: [EncryptedDirectMessage], authors: [pubKey], '#p': [myPubKey], limit: 10 } + { + kinds: [EncryptedDirectMessage], + authors: [myPubKey], + '#p': [pubKey], + limit: 10, + }, + { + kinds: [EncryptedDirectMessage], + authors: [pubKey], + '#p': [myPubKey], + limit: 10, + }, ]; - this._relayService.getPool().subscribeMany(this._relayService.getConnectedRelays(), historyFilter, { - onevent: async (event: NostrEvent) => { - const isSentByMe = event.pubkey === myPubKey; - const senderOrRecipientPubKey = isSentByMe ? pubKey : event.pubkey; - const useExtension = await this._signerService.isUsingExtension(); - const useSecretKey = await this._signerService.isUsingSecretKey(); - const decryptedMessage = await this.decryptReceivedMessage( - event, - useExtension, - useSecretKey, - this.decryptedPrivateKey, - senderOrRecipientPubKey - ); + this._relayService + .getPool() + .subscribeMany( + this._relayService.getConnectedRelays(), + historyFilter, + { + onevent: async (event: NostrEvent) => { + const isSentByMe = event.pubkey === myPubKey; + const senderOrRecipientPubKey = isSentByMe + ? pubKey + : event.pubkey; + const useExtension = + await this._signerService.isUsingExtension(); + const useSecretKey = + await this._signerService.isUsingSecretKey(); + const decryptedMessage = + await this.decryptReceivedMessage( + event, + useExtension, + useSecretKey, + this.decryptedPrivateKey, + senderOrRecipientPubKey + ); - if (decryptedMessage) { - const messageTimestamp = Math.floor(event.created_at); + if (decryptedMessage) { + const messageTimestamp = Math.floor( + event.created_at + ); - this.addOrUpdateChatList(pubKey, decryptedMessage, messageTimestamp, isSentByMe); - this._chat.next(this.chatList.find(chat => chat.id === pubKey)); + this.addOrUpdateChatList( + pubKey, + decryptedMessage, + messageTimestamp, + isSentByMe + ); + this._chat.next( + this.chatList.find((chat) => chat.id === pubKey) + ); + } + }, + oneose: () => {}, } - }, - oneose: () => { - } - }); + ); } private async fetchChatHistory(pubKey: string): Promise { const myPubKey = this._signerService.getPublicKey(); const historyFilter: Filter[] = [ - { kinds: [EncryptedDirectMessage], authors: [myPubKey], '#p': [pubKey], limit: 10 }, - { kinds: [EncryptedDirectMessage], authors: [pubKey], '#p': [myPubKey], limit: 10 } + { + kinds: [EncryptedDirectMessage], + authors: [myPubKey], + '#p': [pubKey], + limit: 10, + }, + { + kinds: [EncryptedDirectMessage], + authors: [pubKey], + '#p': [myPubKey], + limit: 10, + }, ]; const messages: any[] = []; - this._relayService.getPool().subscribeMany(this._relayService.getConnectedRelays(), historyFilter, { - onevent: async (event: NostrEvent) => { - const isSentByMe = event.pubkey === myPubKey; - const senderOrRecipientPubKey = isSentByMe ? pubKey : event.pubkey; - const useExtension = await this._signerService.isUsingExtension(); - const useSecretKey = await this._signerService.isUsingSecretKey(); - const decryptedMessage = await this.decryptReceivedMessage( - event, - useExtension, - useSecretKey, - this.decryptedPrivateKey, - senderOrRecipientPubKey - ); + this._relayService + .getPool() + .subscribeMany( + this._relayService.getConnectedRelays(), + historyFilter, + { + onevent: async (event: NostrEvent) => { + const isSentByMe = event.pubkey === myPubKey; + const senderOrRecipientPubKey = isSentByMe + ? pubKey + : event.pubkey; + const useExtension = + await this._signerService.isUsingExtension(); + const useSecretKey = + await this._signerService.isUsingSecretKey(); + const decryptedMessage = + await this.decryptReceivedMessage( + event, + useExtension, + useSecretKey, + this.decryptedPrivateKey, + senderOrRecipientPubKey + ); - if (decryptedMessage) { - const messageTimestamp = Math.floor(event.created_at); + if (decryptedMessage) { + const messageTimestamp = Math.floor( + event.created_at + ); - const message = { - id: event.id, - chatId: pubKey, - contactId: senderOrRecipientPubKey, - isMine: isSentByMe, - value: decryptedMessage, - createdAt: new Date(messageTimestamp * 1000).toISOString(), - }; + const message = { + id: event.id, + chatId: pubKey, + contactId: senderOrRecipientPubKey, + isMine: isSentByMe, + value: decryptedMessage, + createdAt: new Date( + messageTimestamp * 1000 + ).toISOString(), + }; - messages.push(message); + messages.push(message); - this.addOrUpdateChatList(pubKey, decryptedMessage, messageTimestamp, isSentByMe); - this._chat.next(this.chatList.find(chat => chat.id === pubKey)); + this.addOrUpdateChatList( + pubKey, + decryptedMessage, + messageTimestamp, + isSentByMe + ); + this._chat.next( + this.chatList.find((chat) => chat.id === pubKey) + ); + } + }, + oneose: () => {}, } - }, - oneose: () => { - } - }); + ); - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); return messages; } @@ -484,10 +684,14 @@ export class ChatService implements OnDestroy { event.id = getEventHash(event); - return from(this._relayService.publishEventToRelays(event)).pipe( + return from( + this._relayService.publishEventToRelays(event) + ).pipe( map(() => { if (chats) { - const index = chats.findIndex((item) => item.id === id); + const index = chats.findIndex( + (item) => item.id === id + ); if (index !== -1) { chats[index] = chat; this._chats.next(chats); @@ -496,7 +700,10 @@ export class ChatService implements OnDestroy { return chat; }), catchError((error) => { - console.error('Failed to update chat via Nostr:', error); + console.error( + 'Failed to update chat via Nostr:', + error + ); return throwError(error); }) ); @@ -516,7 +723,7 @@ export class ChatService implements OnDestroy { return this.createNewChat(id, contact); } - const cachedChat = chats.find(chat => chat.id === id); + const cachedChat = chats.find((chat) => chat.id === id); if (cachedChat) { this._chat.next(cachedChat); this.loadChatHistory(this.recipientPublicKey); @@ -534,8 +741,6 @@ export class ChatService implements OnDestroy { ); } - - createNewChat(id: string, contact: Contact = null): Observable { // const existingChat = this._chats.value?.find(chat => chat.id === id); @@ -545,11 +750,21 @@ export class ChatService implements OnDestroy { const newChat: Chat = { id: id || '', contact: contact - ? { pubKey: contact.pubKey || id, name: contact.name || 'Unknown', picture: contact.picture || '/images/avatars/avatar-placeholder.png' } - : { pubKey: id, name: 'Unknown', picture: '/images/avatars/avatar-placeholder.png' }, + ? { + pubKey: contact.pubKey || id, + name: contact.name || 'Unknown', + picture: + contact.picture || + '/images/avatars/avatar-placeholder.png', + } + : { + pubKey: id, + name: 'Unknown', + picture: '/images/avatars/avatar-placeholder.png', + }, lastMessage: 'new chat...', lastMessageAt: Math.floor(Date.now() / 1000).toString() || '0', - messages: [] + messages: [], }; // const updatedChats = this._chats.value ? [...this._chats.value, newChat] : [newChat]; @@ -559,10 +774,13 @@ export class ChatService implements OnDestroy { map((metadata: any) => { newChat.contact = { pubKey: id, - name: metadata?.name || "Unknown", - picture: metadata?.picture || "/images/avatars/avatar-placeholder.png", - about: metadata?.about || "", - displayName: metadata?.displayName || metadata?.name || "Unknown" + name: metadata?.name || 'Unknown', + picture: + metadata?.picture || + '/images/avatars/avatar-placeholder.png', + about: metadata?.about || '', + displayName: + metadata?.displayName || metadata?.name || 'Unknown', }; return newChat; @@ -576,8 +794,10 @@ export class ChatService implements OnDestroy { chatId: id, contactId: id, isMine: true, - value: "new chat...", - createdAt: Math.floor(Date.now() / 1000).toString(), + value: 'new chat...', + createdAt: Math.floor( + Date.now() / 1000 + ).toString(), }; messages.push(testMessage); @@ -591,7 +811,9 @@ export class ChatService implements OnDestroy { newChat.lastMessageAt = lastMessage.createdAt; } - const updatedChatsWithMessages = this._chats.value ? [...this._chats.value, newChat] : [newChat]; + const updatedChatsWithMessages = this._chats.value + ? [...this._chats.value, newChat] + : [newChat]; this._chats.next(updatedChatsWithMessages); this._chat.next(newChat); @@ -602,14 +824,10 @@ export class ChatService implements OnDestroy { ); } - - - resetChat(): void { this._chat.next(null); } - public async sendPrivateMessage(message: string): Promise { try { this.message = message; @@ -620,20 +838,31 @@ export class ChatService implements OnDestroy { await this.handleMessageSendingWithExtension(); } else if (!useExtension && useSecretKey) { if (!this.isValidMessageSetup()) { - console.error('Message, sender private key, or recipient public key is not properly set.'); + console.error( + 'Message, sender private key, or recipient public key is not properly set.' + ); return; } - const encryptedMessage = await this._signerService.encryptMessage( - this.decryptedPrivateKey, - this.recipientPublicKey, - this.message + const encryptedMessage = + await this._signerService.encryptMessage( + this.decryptedPrivateKey, + this.recipientPublicKey, + this.message + ); + + const messageEvent = this._signerService.getUnsignedEvent( + 4, + [['p', this.recipientPublicKey]], + encryptedMessage ); - const messageEvent = this._signerService.getUnsignedEvent(4, [['p', this.recipientPublicKey]], encryptedMessage); + const signedEvent = this._signerService.getSignedEvent( + messageEvent, + this.decryptedPrivateKey + ); - const signedEvent = this._signerService.getSignedEvent(messageEvent, this.decryptedPrivateKey); - - const published = await this._relayService.publishEventToRelays(signedEvent); + const published = + await this._relayService.publishEventToRelays(signedEvent); if (published) { this.message = ''; @@ -641,7 +870,6 @@ export class ChatService implements OnDestroy { console.error('Failed to send the message.'); } } - } catch (error) { console.error('Error sending private message:', error); } @@ -649,20 +877,23 @@ export class ChatService implements OnDestroy { private async handleMessageSendingWithExtension(): Promise { try { - const encryptedMessage = await this._signerService.encryptMessageWithExtension( - this.message, - this.recipientPublicKey - ); + const encryptedMessage = + await this._signerService.encryptMessageWithExtension( + this.message, + this.recipientPublicKey + ); - const signedEvent = await this._signerService.signEventWithExtension({ - kind: 4, - pubkey: this._signerService.getPublicKey(), - tags: [['p', this.recipientPublicKey]], - content: encryptedMessage, - created_at: Math.floor(Date.now() / 1000), - }); + const signedEvent = + await this._signerService.signEventWithExtension({ + kind: 4, + pubkey: this._signerService.getPublicKey(), + tags: [['p', this.recipientPublicKey]], + content: encryptedMessage, + created_at: Math.floor(Date.now() / 1000), + }); - const published = await this._relayService.publishEventToRelays(signedEvent); + const published = + await this._relayService.publishEventToRelays(signedEvent); if (published) { this.message = ''; @@ -682,6 +913,4 @@ export class ChatService implements OnDestroy { this._unsubscribeAll.next(); this._unsubscribeAll.complete(); } - - } diff --git a/src/app/components/chat/chats/chats.component.html b/src/app/components/chat/chats/chats.component.html index f28af1e..7a7e3bc 100644 --- a/src/app/components/chat/chats/chats.component.html +++ b/src/app/components/chat/chats/chats.component.html @@ -1,16 +1,20 @@
- + @if (drawerComponent === 'new-chat') { - + } @if (drawerComponent === 'profile') { - + } @@ -18,180 +22,276 @@ @if (chats && chats.length > 0) { -
- -
-
-
-
- @if (profile?.picture) { - Profile picture - } - @if (!profile?.picture) { -
- {{ profile?.name?.charAt(0) }} +
+ +
+
+
+
+ @if (profile?.picture) { + Profile picture + } + @if (!profile?.picture) { +
+ {{ profile?.name?.charAt(0) }} +
+ } +
+
+ {{ profile?.name }}
- }
-
- {{ profile?.name }} -
-
- - + - + - + - + - - + + - - -
- -
- - + Settings + + + +
+ +
+ + - - + " + > + + +
-
- - - - } - } @else { -
- + -
- No chats -
+ " + >
+
+ No chats +
+
+ }
- }
-
} @else { -
- + -
- No chats + " + > +
+ No chats +
-
} @if (chats && chats.length > 0) { -
- -
+ }" + > + +
} diff --git a/src/app/components/chat/chats/chats.component.ts b/src/app/components/chat/chats/chats.component.ts index 9834b15..3e174fe 100644 --- a/src/app/components/chat/chats/chats.component.ts +++ b/src/app/components/chat/chats/chats.component.ts @@ -14,13 +14,13 @@ import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatSidenavModule } from '@angular/material/sidenav'; import { ActivatedRoute, RouterLink, RouterOutlet } from '@angular/router'; -import { catchError, of, Subject, takeUntil } from 'rxjs'; +import { AgoPipe } from 'app/shared/pipes/ago.pipe'; +import { CheckmessagePipe } from 'app/shared/pipes/checkmessage.pipe'; +import { Subject, takeUntil } from 'rxjs'; import { ChatService } from '../chat.service'; import { Chat, Profile } from '../chat.types'; import { NewChatComponent } from '../new-chat/new-chat.component'; import { ProfileComponent } from '../profile/profile.component'; -import { AgoPipe } from 'app/shared/pipes/ago.pipe'; -import { CheckmessagePipe } from 'app/shared/pipes/checkmessage.pipe'; @Component({ selector: 'chat-chats', @@ -42,7 +42,7 @@ import { CheckmessagePipe } from 'app/shared/pipes/checkmessage.pipe'; RouterOutlet, AgoPipe, CommonModule, - CheckmessagePipe + CheckmessagePipe, ], }) export class ChatsComponent implements OnInit, OnDestroy { @@ -67,9 +67,8 @@ export class ChatsComponent implements OnInit, OnDestroy { constructor( private _chatService: ChatService, private _changeDetectorRef: ChangeDetectorRef, - private route: ActivatedRoute, - - ) { } + private route: ActivatedRoute + ) {} /** * Angular lifecycle hook (ngOnInit) for component initialization. @@ -100,7 +99,7 @@ export class ChatsComponent implements OnInit, OnDestroy { this._markForCheck(); }); - this._chatService.InitSubscribeToChatList(); + this._chatService.InitSubscribeToChatList(); const savedChatId = localStorage.getItem('currentChatId'); @@ -128,7 +127,7 @@ export class ChatsComponent implements OnInit, OnDestroy { this.filteredChats = this.chats; } else { const lowerCaseQuery = query.toLowerCase(); - this.filteredChats = this.chats.filter(chat => + this.filteredChats = this.chats.filter((chat) => chat.contact?.name.toLowerCase().includes(lowerCaseQuery) ); } @@ -169,6 +168,4 @@ export class ChatsComponent implements OnInit, OnDestroy { private _markForCheck(): void { this._changeDetectorRef.markForCheck(); } - - } diff --git a/src/app/components/chat/contact-info/contact-info.component.html b/src/app/components/chat/contact-info/contact-info.component.html index 3869651..6cdcb39 100644 --- a/src/app/components/chat/contact-info/contact-info.component.html +++ b/src/app/components/chat/contact-info/contact-info.component.html @@ -29,12 +29,14 @@
}
-
{{ chat.contact?.name }}
-
+ +
{{ chat.contact?.about }}
- -
diff --git a/src/app/components/chat/contact-info/contact-info.component.ts b/src/app/components/chat/contact-info/contact-info.component.ts index bdfe17f..7f36775 100644 --- a/src/app/components/chat/contact-info/contact-info.component.ts +++ b/src/app/components/chat/contact-info/contact-info.component.ts @@ -7,15 +7,16 @@ import { import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatDrawer } from '@angular/material/sidenav'; +import { RouterModule } from '@angular/router'; import { Chat } from '../chat.types'; - + @Component({ selector: 'chat-contact-info', templateUrl: './contact-info.component.html', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [MatButtonModule, MatIconModule], + imports: [MatButtonModule, MatIconModule, RouterModule], }) export class ContactInfoComponent { @Input() chat: Chat; diff --git a/src/app/components/chat/conversation/conversation.component.css b/src/app/components/chat/conversation/conversation.component.css index 029551f..a0a8099 100644 --- a/src/app/components/chat/conversation/conversation.component.css +++ b/src/app/components/chat/conversation/conversation.component.css @@ -1,7 +1,8 @@ -.c-img{ - max-width: 100%; border-radius: 10px; +.c-img { + max-width: 100%; + border-radius: 10px; } -.c-video -{ - max-width: 100%; border-radius: 10px; +.c-video { + max-width: 100%; + border-radius: 10px; } diff --git a/src/app/components/chat/conversation/conversation.component.html b/src/app/components/chat/conversation/conversation.component.html index e96c2d6..16cf06f 100644 --- a/src/app/components/chat/conversation/conversation.component.html +++ b/src/app/components/chat/conversation/conversation.component.html @@ -176,33 +176,44 @@ >
- - @if ( - last || - chat.messages[i + 1].isMine !== message.isMine - ) { + class="relative max-w-3/4 rounded-lg px-2 py-2" + [ngClass]="{ + 'bg-gray-400 text-blue-50': + message.isMine, + 'bg-gray-500 text-gray-50': + !message.isMine, + }" + > + + @if ( + last || + chat.messages[i + 1].isMine !== + message.isMine + ) { +
+ +
+ } +
- -
- } - -
-
+ class="min-w-4 whitespace-normal break-words leading-5" + [innerHTML]=" + parseContent(message.value) + " + >
+
@if ( @@ -237,7 +248,7 @@ class="flex items-end border-t bg-gray-50 p-4 dark:bg-transparent" >
- + + + + + + + + + +
+
+
+ +
+
+ + + + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ โšก {{ event.zapCount }} zap +
+
+ +
+ + +
+
+ + +
+
+
+ {{
+                    currentUserMetadata?.display_name ||
+                        currentUserMetadata?.name ||
+                        'Avatar'
+                }} + + + + + + +
+
+
+ +
+ +
+ + +
+ +
+
+ + +
+
+
+ {{ reply.username }} +
+ + {{ reply.username }}: + {{ reply.content }} + +
+ Like + Reply + Hide replies + + {{getTimeFromNow(reply) }} +
+
+
+ +
+
+ + +
+ + +
+
+ Loading events... +
+ + +
+ +
No more events to load.
diff --git a/src/app/components/event-list/event-list.component.scss b/src/app/components/event-list/event-list.component.scss new file mode 100644 index 0000000..7d8519d --- /dev/null +++ b/src/app/components/event-list/event-list.component.scss @@ -0,0 +1,158 @@ +:host { + display: block; + max-width: 600px; + margin: 0 auto; + font-family: 'Arial', sans-serif; +} + +.loading-spinner { + display: flex; + justify-content: center; + align-items: center; + margin: 20px 0; + + .spinner { + border: 4px solid rgba(0, 0, 0, 0.1); + border-left-color: #009fb5; + border-radius: 50%; + width: 30px; + height: 30px; + animation: spin 1s linear infinite; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +} + +.event-list { + list-style: none; + padding: 0; + margin: 20px 0; + + .event-item { + background-color: #ffffff; + border-radius: 10px; + padding: 15px; + margin-bottom: 10px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.3s ease; + + &:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + } + + .event-header { + display: flex; + align-items: center; + + .profile-picture { + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 10px; + border: 2px solid #009fb5; + } + + .event-info { + .username { + font-weight: bold; + color: #333; + } + + .timestamp { + font-size: 0.9em; + color: #888; + } + } + } + + .event-content { + margin: 10px 0; + font-size: 1.1em; + color: #555; + } + + .event-actions { + display: flex; + gap: 10px; + margin-top: 10px; + + button { + background-color: transparent; + border: none; + cursor: pointer; + color: #009fb5; + font-size: 1.1em; + transition: color 0.2s ease; + + &:hover { + color: #007f91; + } + + &:disabled { + color: #999; + cursor: not-allowed; + } + } + } + + .event-replies { + margin-top: 15px; + border-top: 1px solid #e0e0e0; + padding-top: 10px; + + ul { + list-style: none; + padding: 0; + + .reply-item { + margin: 5px 0; + font-size: 0.9em; + + .reply-username { + font-weight: bold; + color: #009fb5; + } + + .reply-content { + color: #555; + } + } + } + } + } +} + +.no-more-events { + text-align: center; + color: #555; + margin: 20px 0; + font-size: 1.1em; +} + +.load-more-btn { + display: block; + width: 100%; + padding: 10px; + font-size: 1.1em; + background-color: #009fb5; + color: #ffffff; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: #007f91; + } + + &:focus { + outline: none; + } +} diff --git a/src/app/components/event-list/event-list.component.ts b/src/app/components/event-list/event-list.component.ts new file mode 100644 index 0000000..d871185 --- /dev/null +++ b/src/app/components/event-list/event-list.component.ts @@ -0,0 +1,252 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { Observable, Subscription } from 'rxjs'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { PaginatedEventService } from 'app/services/event.service'; +import { NewEvent } from 'app/types/NewEvent'; +import { AngorCardComponent } from '@angor/components/card'; +import { TextFieldModule } from '@angular/cdk/text-field'; +import { NgClass, CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterLink } from '@angular/router'; +import { PickerComponent } from '@ctrl/ngx-emoji-mart'; +import { QRCodeModule } from 'angularx-qrcode'; +import { SafeUrlPipe } from 'app/shared/pipes/safe-url.pipe'; +import { InfiniteScrollModule } from 'ngx-infinite-scroll'; + +@Component({ + selector: 'app-event-list', + templateUrl: './event-list.component.html', + styleUrls: ['./event-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [ + RouterLink, + AngorCardComponent, + MatIconModule, + MatButtonModule, + MatMenuModule, + MatFormFieldModule, + MatInputModule, + TextFieldModule, + MatDividerModule, + MatTooltipModule, + NgClass, + CommonModule, + FormsModule, + QRCodeModule, + PickerComponent, + MatSlideToggle, + SafeUrlPipe, + MatProgressSpinnerModule, + InfiniteScrollModule, + ] +}) +export class EventListComponent implements OnInit, OnDestroy { + @Input() pubkeys: string[] = []; + @Input() currentUserMetadata: any; + + events$: Observable; + eventStates: { showEmojiPicker: boolean; comment: string }[] = []; + subscriptions: Subscription[] = []; + + isLoading = false; + noMoreEvents = false; + + constructor( + private paginatedEventService: PaginatedEventService, + private changeDetectorRef: ChangeDetectorRef, + private sanitizer: DomSanitizer + ) { + this.events$ = this.paginatedEventService.getEventStream(); + } + + ngOnInit(): void { + this.resetAll(); + } + + + + subscribeToEvents(): void { + this.unsubscribeAll(); + + + if (!this.pubkeys || this.pubkeys.length === 0) { + console.warn('No public keys provided'); + return; + } + + + this.paginatedEventService.subscribeToEvents(this.pubkeys) + .then(() => { + console.log('Subscribed to events for the new user.'); + }) + .catch(error => { + console.error('Error subscribing to events:', error); + }); + + + const eventSub = this.events$.subscribe(events => { + const relevantEvents = events.filter(event => this.pubkeys.includes(event.pubkey)); + + this.eventStates = relevantEvents.map(() => ({ + showEmojiPicker: false, + comment: '' + })); + + this.changeDetectorRef.markForCheck(); + }); + + this.subscriptions.push(eventSub); + } + + + + resetAll(): void { + this.unsubscribeAll(); + this.clearComponentState(); + this.paginatedEventService.clearEvents(); + this.subscribeToEvents(); + this.loadInitialEvents(); + } + + + + + + + unsubscribeAll(): void { + this.subscriptions.forEach(sub => sub.unsubscribe()); + this.subscriptions = []; + } + + clearComponentState(): void { + this.eventStates = []; + this.isLoading = false; + this.noMoreEvents = false; + this.changeDetectorRef.markForCheck(); + } + + + loadInitialEvents(): void { + if (this.pubkeys.length === 0) { + console.warn('No pubkeys provided'); + return; + } + + this.isLoading = true; + this.paginatedEventService.loadMoreEvents(this.pubkeys).finally(() => { + this.isLoading = false; + this.changeDetectorRef.markForCheck(); + }); + } + + loadMoreEvents(): void { + if (!this.isLoading && !this.noMoreEvents) { + this.isLoading = true; + this.paginatedEventService.loadMoreEvents(this.pubkeys).finally(() => { + this.isLoading = false; + this.changeDetectorRef.markForCheck(); + }); + } + } + + + + + + getComment(index: number): string { + return this.eventStates[index]?.comment || ''; + } + + setComment(index: number, value: string): void { + if (this.eventStates[index]) { + this.eventStates[index].comment = value; + } + } + + getSanitizedContent(content: string): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(content); + } + + sendLike(event: NewEvent): void { + if (!event.likedByMe) { + this.paginatedEventService.sendLikeEvent(event).then(() => { + event.likedByMe = true; + event.likeCount++; + this.changeDetectorRef.markForCheck(); + }).catch(error => console.error('Failed to send like:', error)); + } + } + + toggleLike(event: NewEvent): void { + this.sendLike(event); + } + + toggleCommentEmojiPicker(index: number): void { + this.eventStates[index].showEmojiPicker = !this.eventStates[index].showEmojiPicker; + } + + addEmojiToComment(event: any, index: number): void { + this.eventStates[index].comment += event.emoji.native; + this.eventStates[index].showEmojiPicker = false; + } + + sendComment(event: NewEvent, index: number): void { + const comment = this.eventStates[index].comment; + if (comment.trim() !== '') { + this.paginatedEventService.sendReplyEvent(event, comment).then(() => { + this.eventStates[index].comment = ''; + this.changeDetectorRef.markForCheck(); + }); + } + } + + trackById(index: number, item: NewEvent): string { + return item.id; + } + + ngOnDestroy(): void { + this.unsubscribeAll(); + } + + getTimeFromNow(event: NewEvent): string { + return event.fromNow; + } + + parseContent(content: string): SafeHtml { + const urlRegex = /(https?:\/\/[^\s]+)/g; + const cleanedContent = content.replace(/["]+/g, ''); + const parsedContent = cleanedContent + .replace(urlRegex, (url) => { + if (url.match(/\.(jpeg|jpg|gif|png|bmp|svg|webp|tiff)$/) != null) { + return `Image`; + } else if (url.match(/\.(mp4|webm)$/) != null) { + return ``; + } else if (url.match(/(youtu\.be\/|youtube\.com\/watch\?v=)/)) { + let videoId; + if (url.includes('youtu.be/')) { + videoId = url.split('youtu.be/')[1]; + } else if (url.includes('watch?v=')) { + const urlParams = new URLSearchParams(url.split('?')[1]); + videoId = urlParams.get('v'); + } + return ``; + } else { + return `${url}`; + } + }) + .replace(/\n/g, '
'); + + return this.sanitizer.bypassSecurityTrustHtml(parsedContent); + } +} diff --git a/src/app/components/explore/explore.component.html b/src/app/components/explore/explore.component.html index 82d5327..e75c587 100644 --- a/src/app/components/explore/explore.component.html +++ b/src/app/components/explore/explore.component.html @@ -1,20 +1,37 @@
-
+
- - + +

Explore Projects

-
+
Whatโ€™s your next investment?
-
+
Check out our projects and find your next investment opportunity.
@@ -23,110 +40,188 @@
-
+
-
+
-
- - - +
+ + + - -
- + Hide completed
-
+
- Card cover image + " + onerror="this.onerror=null; this.src='/images/pages/profile/cover.jpg';" + alt="Card cover image" + />
- Project logo + " + onerror="this.onerror=null; this.src='/images/avatars/avatar-placeholder.png';" + alt="Project logo" + />
@if (project.displayName || project.name) { -
- {{ - project.displayName || - project.nostrPubKey - }} -
+ " + > + {{ + project.displayName || + project.nostrPubKey + }} +
} @if ( - !project.name && !project.displayName + !project.name && !project.displayName ) { -
- {{ - project.displayName || - project.nostrPubKey - }} -
+
+ {{ + project.displayName || + project.nostrPubKey + }} +
} -
+
{{ - project.about || - 'No description available' + project.about || + 'No description available' }}
@if (project.displayName || project.name) { -
- -
+ " + > + +
}

-
+
{{ project.totalInvestmentsCount || 0 }} investors
- + - Investor avatar {{ i + 1 }} + '-ml-3': + project.totalInvestmentsCount > + 1 && i > 0, + }" + [src]=" + 'images/avatars/avatar-placeholder.png' + " + alt="Investor avatar {{ + i + 1 + }}" + />
@@ -135,18 +230,32 @@
- -
- -
+ +
+ +
No project
-
-
diff --git a/src/app/components/explore/explore.component.ts b/src/app/components/explore/explore.component.ts index a54cfa2..76721df 100644 --- a/src/app/components/explore/explore.component.ts +++ b/src/app/components/explore/explore.component.ts @@ -1,7 +1,19 @@ -import { Component, OnInit, OnDestroy, ViewEncapsulation, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; -import { Router } from '@angular/router'; -import { ProjectsService } from '../../services/projects.service'; -import { StateService } from '../../services/state.service'; +import { AngorCardComponent } from '@angor/components/card'; +import { AngorFindByKeyPipe } from '@angor/pipes/find-by-key'; +import { CdkScrollable } from '@angular/cdk/scrolling'; +import { + CommonModule, + I18nPluralPipe, + NgClass, + PercentPipe, +} from '@angular/common'; +import { + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewEncapsulation, +} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatOptionModule } from '@angular/material/core'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -11,18 +23,16 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { RouterLink } from '@angular/router'; -import { AngorCardComponent } from '@angor/components/card'; -import { AngorFindByKeyPipe } from '@angor/pipes/find-by-key'; -import { CdkScrollable } from '@angular/cdk/scrolling'; -import { NgClass, PercentPipe, I18nPluralPipe, CommonModule } from '@angular/common'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { Router, RouterLink } from '@angular/router'; +import { Project } from 'app/interface/project.interface'; +import { IndexedDBService } from 'app/services/indexed-db.service'; import { MetadataService } from 'app/services/metadata.service'; import { Subject, takeUntil } from 'rxjs'; -import { IndexedDBService } from 'app/services/indexed-db.service'; -import { Project } from 'app/interface/project.interface'; -import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; -import { Contact } from '../chat/chat.types'; +import { ProjectsService } from '../../services/projects.service'; +import { StateService } from '../../services/state.service'; import { ChatService } from '../chat/chat.service'; +import { Contact } from '../chat/chat.types'; @Component({ selector: 'explore', @@ -30,10 +40,23 @@ import { ChatService } from '../chat/chat.service'; templateUrl: './explore.component.html', encapsulation: ViewEncapsulation.None, imports: [ - MatButtonModule, RouterLink, MatIconModule, AngorCardComponent, - CdkScrollable, MatFormFieldModule, MatSelectModule, MatOptionModule, - MatInputModule, MatSlideToggleModule, NgClass, MatTooltipModule, - MatProgressBarModule, AngorFindByKeyPipe, PercentPipe, I18nPluralPipe, CommonModule + MatButtonModule, + RouterLink, + MatIconModule, + AngorCardComponent, + CdkScrollable, + MatFormFieldModule, + MatSelectModule, + MatOptionModule, + MatInputModule, + MatSlideToggleModule, + NgClass, + MatTooltipModule, + MatProgressBarModule, + AngorFindByKeyPipe, + PercentPipe, + I18nPluralPipe, + CommonModule, ], }) export class ExploreComponent implements OnInit, OnDestroy { @@ -45,7 +68,6 @@ export class ExploreComponent implements OnInit, OnDestroy { filteredProjects: Project[] = []; showCloseSearchButton: boolean = false; - constructor( private projectService: ProjectsService, private router: Router, @@ -55,7 +77,7 @@ export class ExploreComponent implements OnInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private sanitizer: DomSanitizer, private _chatService: ChatService - ) { } + ) {} async ngOnInit(): Promise { this.loadInitialProjects(); @@ -96,21 +118,26 @@ export class ExploreComponent implements OnInit, OnDestroy { this.filteredProjects = [...this.projects]; this.stateService.setProjects(this.projects); - const pubkeys = projects.map(p => p.nostrPubKey); + const pubkeys = projects.map((p) => p.nostrPubKey); await this.loadMetadataForProjects(pubkeys); - } catch (error) { this.handleError('Error fetching projects from service'); } } private subscribeToMetadataUpdates(): void { - this.indexedDBService.getMetadataStream() + this.indexedDBService + .getMetadataStream() .pipe(takeUntil(this._unsubscribeAll)) .subscribe((updatedMetadata: any) => { if (updatedMetadata) { - const projectToUpdate = this.projects.find(p => p.nostrPubKey === updatedMetadata.pubkey); + const projectToUpdate = this.projects.find( + (p) => p.nostrPubKey === updatedMetadata.pubkey + ); if (projectToUpdate) { - this.updateProjectMetadata(projectToUpdate, updatedMetadata.metadata); + this.updateProjectMetadata( + projectToUpdate, + updatedMetadata.metadata + ); } } }); @@ -118,14 +145,15 @@ export class ExploreComponent implements OnInit, OnDestroy { private getProjectsWithoutMetadata(): string[] { return this.projects - .filter(project => !project.displayName || !project.about) - .map(project => project.nostrPubKey); + .filter((project) => !project.displayName || !project.about) + .map((project) => project.nostrPubKey); } private async loadMetadataForProjects(pubkeys: string[]): Promise { const metadataPromises = pubkeys.map(async (pubkey) => { // Check cache first - const cachedMetadata = await this.indexedDBService.getUserMetadata(pubkey); + const cachedMetadata = + await this.indexedDBService.getUserMetadata(pubkey); if (cachedMetadata) { return { pubkey, metadata: cachedMetadata }; } @@ -136,13 +164,15 @@ export class ExploreComponent implements OnInit, OnDestroy { // Filter out nulls (which represent pubkeys without cached metadata) const missingPubkeys = metadataResults - .filter(result => result === null) + .filter((result) => result === null) .map((_, index) => pubkeys[index]); // Update projects that have cached metadata - metadataResults.forEach(result => { + metadataResults.forEach((result) => { if (result && result.metadata) { - const project = this.projects.find(p => p.nostrPubKey === result.pubkey); + const project = this.projects.find( + (p) => p.nostrPubKey === result.pubkey + ); if (project) { this.updateProjectMetadata(project, result.metadata); } @@ -151,80 +181,103 @@ export class ExploreComponent implements OnInit, OnDestroy { // Fetch metadata for pubkeys that are not cached if (missingPubkeys.length > 0) { - await this.metadataService.fetchMetadataForMultipleKeys(missingPubkeys) + await this.metadataService + .fetchMetadataForMultipleKeys(missingPubkeys) .then((metadataList: any[]) => { - metadataList.forEach(metadata => { - const project = this.projects.find(p => p.nostrPubKey === metadata.pubkey); + metadataList.forEach((metadata) => { + const project = this.projects.find( + (p) => p.nostrPubKey === metadata.pubkey + ); if (project) { this.updateProjectMetadata(project, metadata); } }); this.changeDetectorRef.detectChanges(); }) - .catch(error => { - console.error('Error fetching metadata for projects:', error); + .catch((error) => { + console.error( + 'Error fetching metadata for projects:', + error + ); }); } } - - async loadProjects(): Promise { - if (this.loading || this.errorMessage === 'No more projects found') return; + if (this.loading || this.errorMessage === 'No more projects found') + return; this.loading = true; - this.projectService.fetchProjects().then(async (projects: Project[]) => { - if (projects.length === 0 && this.projects.length === 0) { - this.errorMessage = 'No projects found'; - } else if (projects.length === 0) { - this.errorMessage = 'No more projects found'; - } else { - this.projects = [...this.projects, ...projects]; - this.filteredProjects = [...this.projects]; + this.projectService + .fetchProjects() + .then(async (projects: Project[]) => { + if (projects.length === 0 && this.projects.length === 0) { + this.errorMessage = 'No projects found'; + } else if (projects.length === 0) { + this.errorMessage = 'No more projects found'; + } else { + this.projects = [...this.projects, ...projects]; + this.filteredProjects = [...this.projects]; - const pubkeys = projects.map(project => project.nostrPubKey); + const pubkeys = projects.map( + (project) => project.nostrPubKey + ); - await this.loadMetadataForProjects(pubkeys); + await this.loadMetadataForProjects(pubkeys); - this.stateService.setProjects(this.projects); + this.stateService.setProjects(this.projects); - this.projects.forEach(project => this.subscribeToProjectMetadata(project)); - } - this.loading = false; - this.changeDetectorRef.detectChanges(); - }).catch((error: any) => { - console.error('Error fetching projects:', error); - this.errorMessage = 'Error fetching projects. Please try again later.'; - this.loading = false; - this.changeDetectorRef.detectChanges(); - }); + this.projects.forEach((project) => + this.subscribeToProjectMetadata(project) + ); + } + this.loading = false; + this.changeDetectorRef.detectChanges(); + }) + .catch((error: any) => { + console.error('Error fetching projects:', error); + this.errorMessage = + 'Error fetching projects. Please try again later.'; + this.loading = false; + this.changeDetectorRef.detectChanges(); + }); } async loadMetadataForProject(project: Project): Promise { try { - const metadata = await this.metadataService.fetchMetadataWithCache(project.nostrPubKey); + const metadata = await this.metadataService.fetchMetadataWithCache( + project.nostrPubKey + ); if (metadata) { this.updateProjectMetadata(project, metadata); } else { - console.warn(`No metadata found for project ${project.nostrPubKey}`); + console.warn( + `No metadata found for project ${project.nostrPubKey}` + ); } } catch (error) { - console.error(`Error fetching metadata for project ${project.nostrPubKey}:`, error); + console.error( + `Error fetching metadata for project ${project.nostrPubKey}:`, + error + ); } } updateProjectMetadata(project: Project, metadata: any): void { - const updatedProject: Project = { ...project, displayName: metadata.name || '', - about: metadata.about ? metadata.about.replace(/<\/?[^>]+(>|$)/g, '') : '', + about: metadata.about + ? metadata.about.replace(/<\/?[^>]+(>|$)/g, '') + : '', picture: metadata.picture || '', - banner: metadata.banner || '' + banner: metadata.banner || '', }; - const index = this.projects.findIndex(p => p.projectIdentifier === project.projectIdentifier); + const index = this.projects.findIndex( + (p) => p.projectIdentifier === project.projectIdentifier + ); if (index !== -1) { this.projects[index] = updatedProject; this.projects = [...this.projects]; @@ -235,11 +288,18 @@ export class ExploreComponent implements OnInit, OnDestroy { } subscribeToProjectMetadata(project: Project): void { - this.metadataService.getMetadataStream() + this.metadataService + .getMetadataStream() .pipe(takeUntil(this._unsubscribeAll)) .subscribe((updatedMetadata: any) => { - if (updatedMetadata && updatedMetadata.pubkey === project.nostrPubKey) { - this.updateProjectMetadata(project, updatedMetadata.metadata); + if ( + updatedMetadata && + updatedMetadata.pubkey === project.nostrPubKey + ) { + this.updateProjectMetadata( + project, + updatedMetadata.metadata + ); } }); } @@ -247,7 +307,8 @@ export class ExploreComponent implements OnInit, OnDestroy { goToProjectDetails(project: Project): void { this.loading = true; - this.projectService.fetchAndSaveProjectStats(project.projectIdentifier) + this.projectService + .fetchAndSaveProjectStats(project.projectIdentifier) .then((stats) => { if (stats) { this.navigateToProfile(project.nostrPubKey); @@ -278,17 +339,30 @@ export class ExploreComponent implements OnInit, OnDestroy { const lowerCaseQuery = query.toLowerCase(); - this.filteredProjects = this.projects.filter(project => { + this.filteredProjects = this.projects.filter((project) => { return ( - (project.displayName && project.displayName.toLowerCase().includes(lowerCaseQuery)) || - (project.about && project.about.toLowerCase().includes(lowerCaseQuery)) || - (project.displayName && project.displayName.toLowerCase().includes(lowerCaseQuery)) || - (project.nostrPubKey && project.nostrPubKey.toLowerCase().includes(lowerCaseQuery)) || - (project.projectIdentifier && project.projectIdentifier.toLowerCase().includes(lowerCaseQuery)) + (project.displayName && + project.displayName + .toLowerCase() + .includes(lowerCaseQuery)) || + (project.about && + project.about.toLowerCase().includes(lowerCaseQuery)) || + (project.displayName && + project.displayName + .toLowerCase() + .includes(lowerCaseQuery)) || + (project.nostrPubKey && + project.nostrPubKey + .toLowerCase() + .includes(lowerCaseQuery)) || + (project.projectIdentifier && + project.projectIdentifier + .toLowerCase() + .includes(lowerCaseQuery)) ); }); - this.showCloseSearchButton = this.projects.length > 0 ; + this.showCloseSearchButton = this.projects.length > 0; this.changeDetectorRef.detectChanges(); } @@ -299,9 +373,7 @@ export class ExploreComponent implements OnInit, OnDestroy { this.showCloseSearchButton = false; } - toggleCompleted(event: any): void { - - } + toggleCompleted(event: any): void {} ngOnDestroy(): void { this._unsubscribeAll.next(null); @@ -319,7 +391,9 @@ export class ExploreComponent implements OnInit, OnDestroy { if (url && typeof url === 'string' && this.isImageUrl(url)) { return this.sanitizer.bypassSecurityTrustUrl(url); } else { - const defaultImage = isBanner ? '/images/pages/profile/cover.jpg' : 'images/avatars/avatar-placeholder.png'; + const defaultImage = isBanner + ? '/images/pages/profile/cover.jpg' + : 'images/avatars/avatar-placeholder.png'; return this.sanitizer.bypassSecurityTrustUrl(defaultImage); } } @@ -330,26 +404,34 @@ export class ExploreComponent implements OnInit, OnDestroy { async openChat(publicKey: string): Promise { try { - const metadata = await this.metadataService.fetchMetadataWithCache(publicKey); + const metadata = + await this.metadataService.fetchMetadataWithCache(publicKey); if (metadata) { const contact: Contact = { pubKey: publicKey, name: metadata.name || 'Unknown', - picture: metadata.picture || '/images/avatars/avatar-placeholder.png', + picture: + metadata.picture || + '/images/avatars/avatar-placeholder.png', about: metadata.about || '', - displayName: metadata.displayName || metadata.name || 'Unknown', + displayName: + metadata.displayName || metadata.name || 'Unknown', }; - this._chatService.getChatById(contact.pubKey, contact).subscribe((chat) => { - this.router.navigate(['/chat', contact.pubKey]); - }); + this._chatService + .getChatById(contact.pubKey, contact) + .subscribe((chat) => { + this.router.navigate(['/chat', contact.pubKey]); + }); } else { - console.error('No metadata found for the public key:', publicKey); + console.error( + 'No metadata found for the public key:', + publicKey + ); } } catch (error) { console.error('Error opening chat:', error); } } - } diff --git a/src/app/components/explore/explore.routes.ts b/src/app/components/explore/explore.routes.ts index 83fc2eb..379f2b9 100644 --- a/src/app/components/explore/explore.routes.ts +++ b/src/app/components/explore/explore.routes.ts @@ -3,7 +3,7 @@ import { ExploreComponent } from 'app/components/explore/explore.component'; export default [ { - path : '', + path: '', component: ExploreComponent, }, ] as Routes; diff --git a/src/app/components/home/home.component.html b/src/app/components/home/home.component.html index d18fc4a..72bcaf0 100644 --- a/src/app/components/home/home.component.html +++ b/src/app/components/home/home.component.html @@ -3,10 +3,18 @@

Angor Hub

- Angor Hub is a Nostr client that is customized around the Angor protocol, a decentralized crowdfunding platform. Leveraging the power of Nostr the platform allows you to explore projects that are raising funds using Angor, engage with investors, and connect directly with founders. + Angor Hub is a Nostr client that is customized around the Angor + protocol, a decentralized crowdfunding platform. Leveraging the + power of Nostr the platform allows you to explore projects that + are raising funds using Angor, engage with investors, and + connect directly with founders.

- Whether you're an investor looking for the next big opportunity or a project founder seeking funding, Angor Hub offers the tools you need to succeed. From project pages, secure messaging to group channels, Angor Hub ensures seamless interaction within a decentralized Nostr. + Whether you're an investor looking for the next big opportunity + or a project founder seeking funding, Angor Hub offers the tools + you need to succeed. From project pages, secure messaging to + group channels, Angor Hub ensures seamless interaction within a + decentralized Nostr.

diff --git a/src/app/components/home/home.component.ts b/src/app/components/home/home.component.ts index ff29176..e9476a0 100644 --- a/src/app/components/home/home.component.ts +++ b/src/app/components/home/home.component.ts @@ -8,7 +8,7 @@ import { RouterLink } from '@angular/router'; templateUrl: './home.component.html', encapsulation: ViewEncapsulation.None, standalone: true, - imports: [MatButtonModule, RouterLink, MatIconModule], + imports: [MatButtonModule, RouterLink, MatIconModule ], }) export class LandingHomeComponent { /** diff --git a/src/app/components/profile/profile.component.html b/src/app/components/profile/profile.component.html index c42049b..bf4af13 100644 --- a/src/app/components/profile/profile.component.html +++ b/src/app/components/profile/profile.component.html @@ -3,147 +3,237 @@
- {{metadata?.display_name || metadata?.name || 'Banner'}} + alt="{{ metadata?.display_name || metadata?.name || 'Banner' }}" + />
-
+
-
+
- {{metadata?.display_name || metadata?.name || 'Avatar'}} + alt="{{ + metadata?.display_name || metadata?.name || '' + }}" + /> - {{metadata?.display_name || metadata?.name || 'Avatar'}} + alt="{{ + metadata?.display_name || metadata?.name || '' + }}" + />
-
-
- {{metadata?.display_name || metadata?.name || 'Unknown User'}} +
+
+ {{ + metadata?.display_name || + metadata?.name || + 'Unknown User' + }}
-
- {{metadata?.username || metadata?.name}} +
+ {{ metadata?.username || metadata?.name }}
- - + -
+
{{ followers.length }} - FOLLOWERS + FOLLOWERS
{{ following.length }} - FOLLOWING + FOLLOWING
- + -
+
-
- - -
- - -
-
-
+