From 799e27a9b48672a36c71a68ad16f0f03c1319b0f Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Tue, 10 Mar 2026 09:38:59 +0100 Subject: [PATCH] Enable biome based formatter and reformat --- .editorconfig | 15 + .zed/settings.json | 6 +- Makefile | 4 + bin/plug-compile.ts | 6 +- biome.json | 91 +-- build_client.ts | 2 +- build_worker_runtime.ts | 6 +- client/asset_bundle/builder.ts | 4 +- client/asset_bundle/bundle.test.ts | 4 +- client/asset_bundle/bundle.ts | 12 +- client/boot.ts | 41 +- client/client.ts | 289 ++++------ client/client_system.ts | 51 +- client/codemirror/admonition.ts | 9 +- client/codemirror/attribute.ts | 7 +- client/codemirror/block.ts | 40 +- client/codemirror/code_copy.ts | 21 +- client/codemirror/editor_state.ts | 88 ++- client/codemirror/escapes.ts | 27 +- client/codemirror/fenced_code.ts | 16 +- client/codemirror/frontmatter.ts | 361 ++++++------ client/codemirror/hide_mark.ts | 17 +- client/codemirror/iframe_widget.ts | 47 +- client/codemirror/inline_content.ts | 29 +- client/codemirror/link.ts | 17 +- client/codemirror/lint.ts | 19 +- client/codemirror/lua_directive.ts | 40 +- client/codemirror/lua_widget.ts | 216 ++++--- client/codemirror/smart_quotes.ts | 13 +- client/codemirror/table.ts | 7 +- client/codemirror/task.ts | 8 +- client/codemirror/top_bottom_panels.ts | 24 +- client/codemirror/util.ts | 18 +- client/codemirror/widget_util.ts | 53 +- client/codemirror/wiki_link.ts | 4 +- client/codemirror/wiki_link_processor.ts | 29 +- client/components/anything_picker.tsx | 57 +- client/components/command_palette.tsx | 5 +- client/components/filter.tsx | 123 ++-- client/components/mini_editor.tsx | 14 +- client/components/panel.tsx | 41 +- client/components/top_bar.tsx | 63 +- client/components/widget_sandbox_iframe.ts | 171 +++--- client/config.ts | 5 +- client/data/data_augmenter.test.ts | 17 +- client/data/data_augmenter.ts | 16 +- client/data/datastore.ts | 5 +- client/data/encrypted_kv_primitives.ts | 16 +- client/data/indexeddb_kv_primitives.ts | 27 +- client/data/kv_primitives.test.ts | 23 +- client/data/memory_kv_primitives.test.ts | 15 +- client/data/memory_kv_primitives.ts | 7 +- client/data/mq.datastore.test.ts | 15 +- client/data/mq.datastore.ts | 42 +- client/data/object_index.ts | 61 +- client/document_editor.ts | 32 +- client/editor_ui.tsx | 162 +++--- client/languages.ts | 132 ++--- client/lib/box_proxy.ts | 3 +- client/lib/fuse_search.ts | 38 +- client/lib/logger.test.ts | 3 +- client/lib/logger.ts | 26 +- client/lib/polyfills.ts | 15 +- client/lib/url_prefix.test.ts | 126 ++-- client/lib/url_prefix.ts | 6 +- client/markdown_parser/constants.ts | 2 +- client/markdown_parser/extended_task.ts | 26 +- client/markdown_parser/parser.test.ts | 3 +- client/markdown_parser/parser.ts | 130 ++--- client/markdown_parser/table_parser.ts | 59 +- client/markdown_renderer/html_render.test.ts | 36 +- client/markdown_renderer/html_render.ts | 29 +- client/markdown_renderer/inline.ts | 56 +- client/markdown_renderer/justified_tables.ts | 10 +- .../markdown_renderer/markdown_render.test.ts | 3 +- client/markdown_renderer/markdown_render.ts | 45 +- client/markdown_renderer/result_render.ts | 45 +- client/navigator.ts | 47 +- client/plugos/hooks/code_widget.ts | 8 +- client/plugos/hooks/command.ts | 16 +- client/plugos/hooks/document_editor.ts | 22 +- client/plugos/hooks/event.ts | 69 ++- client/plugos/hooks/mq.ts | 34 +- client/plugos/hooks/plug_namespace.ts | 8 +- client/plugos/hooks/slash_command.ts | 39 +- client/plugos/hooks/syscall.ts | 6 +- client/plugos/manifest_cache.ts | 37 +- client/plugos/plug.ts | 7 +- client/plugos/plug_compile.ts | 94 ++- client/plugos/protocol.ts | 56 +- client/plugos/proxy_fetch.ts | 13 +- client/plugos/sandboxes/worker_sandbox.ts | 29 +- client/plugos/syscalls/asset.ts | 4 +- client/plugos/syscalls/code_widget.ts | 4 +- client/plugos/syscalls/config.ts | 6 +- client/plugos/syscalls/datastore.ts | 2 +- client/plugos/syscalls/editor.ts | 71 +-- client/plugos/syscalls/event.ts | 21 +- client/plugos/syscalls/fetch.ts | 37 +- client/plugos/syscalls/jsonschema.ts | 5 +- client/plugos/syscalls/markdown.ts | 5 +- client/plugos/syscalls/mq.ts | 14 +- client/plugos/syscalls/service_registry.ts | 5 +- client/plugos/syscalls/shell.ts | 9 +- client/plugos/syscalls/system.ts | 5 +- client/plugos/system.ts | 10 +- client/plugos/worker_runtime.ts | 7 +- client/reducer.ts | 5 +- client/service_registry.test.ts | 8 +- client/service_registry.ts | 35 +- client/service_worker.ts | 78 ++- client/service_worker/proxy_router.ts | 39 +- client/service_worker/sync_engine.ts | 30 +- client/service_worker/util.ts | 3 +- client/space.test.ts | 34 +- client/space.ts | 30 +- client/space_lua.ts | 35 +- client/space_lua/aggregates.test.ts | 29 +- client/space_lua/ast.ts | 5 +- client/space_lua/ast_narrow.ts | 6 +- client/space_lua/break_async.test.ts | 25 +- client/space_lua/close_attribute.test.ts | 12 +- client/space_lua/const_attribute.test.ts | 36 +- client/space_lua/context_errors.test.ts | 15 +- client/space_lua/eval.test.ts | 50 +- client/space_lua/eval.ts | 540 ++++++++---------- client/space_lua/labels.ts | 17 +- client/space_lua/lua.test.ts | 4 +- client/space_lua/numeric.ts | 13 +- client/space_lua/parse-lua.js | 93 ++- client/space_lua/parse-lua.terms.js | 5 +- client/space_lua/parse.test.ts | 43 +- client/space_lua/parse.ts | 196 ++++--- client/space_lua/query_collection.test.ts | 59 +- client/space_lua/query_collection.ts | 128 ++--- client/space_lua/render_lua_html.ts | 8 +- client/space_lua/rp.bench.ts | 11 +- client/space_lua/rp.ts | 6 +- client/space_lua/runtime.test.ts | 12 +- client/space_lua/runtime.ts | 89 ++- client/space_lua/stdlib.ts | 47 +- client/space_lua/stdlib/format.ts | 54 +- client/space_lua/stdlib/js.ts | 10 +- client/space_lua/stdlib/load.ts | 4 +- client/space_lua/stdlib/math.ts | 29 +- client/space_lua/stdlib/net.ts | 25 +- client/space_lua/stdlib/os.ts | 157 +++-- client/space_lua/stdlib/pattern.ts | 63 +- client/space_lua/stdlib/prng.ts | 27 +- client/space_lua/stdlib/space_lua.ts | 23 +- client/space_lua/stdlib/string.ts | 24 +- client/space_lua/stdlib/string_pack.ts | 42 +- client/space_lua/stdlib/table.ts | 12 +- client/space_lua/tonumber.ts | 119 ++-- client/space_lua/util.ts | 24 +- client/space_lua_api.ts | 36 +- client/spaces/checked_space_primitives.ts | 9 +- client/spaces/datastore_space_primitives.ts | 23 +- client/spaces/evented_space_primitives.ts | 32 +- client/spaces/filtered_space_primitives.ts | 3 +- client/spaces/http_space_primitives.ts | 42 +- client/spaces/space_primitives.test.ts | 28 +- client/spaces/space_primitives.ts | 4 +- client/spaces/sync.test.ts | 69 +-- client/spaces/sync.ts | 165 +++--- client/types/ui.ts | 138 ++--- package.json | 2 + plug-api/lib/async.test.ts | 30 +- plug-api/lib/async.ts | 5 +- plug-api/lib/crypto.ts | 11 +- plug-api/lib/dates.ts | 22 +- plug-api/lib/json.ts | 14 +- plug-api/lib/ref.test.ts | 24 +- plug-api/lib/ref.ts | 32 +- plug-api/lib/resolve.test.ts | 46 +- plug-api/lib/resolve.ts | 18 +- plug-api/lib/tags.ts | 12 +- plug-api/lib/transclusion.ts | 18 +- plug-api/lib/tree.test.ts | 4 +- plug-api/lib/tree.ts | 8 +- plug-api/lib/yaml.test.ts | 20 +- plug-api/lib/yaml.ts | 40 +- plug-api/syscalls/asset.ts | 2 +- plug-api/syscalls/config.ts | 5 +- plug-api/syscalls/editor.ts | 20 +- plug-api/syscalls/jsonschema.ts | 4 +- plug-api/syscalls/lua.ts | 12 +- plug-api/syscalls/mq.ts | 5 +- plug-api/syscalls/space.ts | 13 +- plug-api/syscalls/system.ts | 5 +- plug-api/syscalls/yaml.ts | 8 +- plug-api/types/event.ts | 10 +- plug-api/types/manifest.ts | 17 +- plugs/builtin_plugs.ts | 4 +- plugs/editor/complete.ts | 84 +-- plugs/editor/document.ts | 4 +- plugs/editor/editor.ts | 22 +- plugs/editor/navigate.ts | 20 +- plugs/editor/outline.test.ts | 267 +++++---- plugs/editor/outline_ops.ts | 124 ++-- plugs/editor/page.ts | 9 +- plugs/editor/system.ts | 4 +- plugs/editor/text.ts | 10 +- plugs/editor/upload.ts | 2 +- plugs/emoji/emoji.json | 2 +- plugs/emoji/emoji.ts | 12 +- plugs/index/api.ts | 5 +- plugs/index/attribute.ts | 4 +- plugs/index/cheap_yaml.test.ts | 10 +- plugs/index/cheap_yaml.ts | 4 +- plugs/index/complete.ts | 4 +- plugs/index/frontmatter.ts | 22 +- plugs/index/header.test.ts | 6 +- plugs/index/header.ts | 10 +- plugs/index/indexer.ts | 35 +- plugs/index/item.test.ts | 3 +- plugs/index/item.ts | 33 +- plugs/index/link.ts | 22 +- plugs/index/lint.ts | 52 +- plugs/index/page.ts | 26 +- plugs/index/paragraph.ts | 5 +- plugs/index/queue.ts | 3 +- plugs/index/refactor.ts | 129 +++-- plugs/index/snippet.test.ts | 48 +- plugs/index/snippet.ts | 18 +- plugs/index/table.ts | 105 ++-- plugs/index/tags.ts | 35 +- plugs/index/task.ts | 77 ++- plugs/plug-manager/plugmanager.ts | 22 +- plugs/sync/sync.ts | 19 +- 230 files changed, 4145 insertions(+), 4472 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c574b338 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.zed/settings.json b/.zed/settings.json index 117acbee..6ed4dd9f 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -6,7 +6,7 @@ "languages": { "Markdown": { "format_on_save": "off", - "remove_trailing_whitespace_on_save": false, - }, - }, + "remove_trailing_whitespace_on_save": false + } + } } diff --git a/Makefile b/Makefile index 12f63a7f..8f00804a 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,14 @@ check: npm run check # Frontend lint npx biome lint . + # Frontend format check + npx biome format . # Backend lint go vet fmt: + # Reformat frontend + npx biome format --write . # Reformat backend go fmt diff --git a/bin/plug-compile.ts b/bin/plug-compile.ts index f9a27fea..6a2cdbc2 100755 --- a/bin/plug-compile.ts +++ b/bin/plug-compile.ts @@ -15,7 +15,11 @@ program .option("--debug", "Do not minify code", false) .option("--info", "Print out size info per function", false) .option("-w, --watch", "Watch for changes and rebuild", false) - .option("--dist ", "Folder to put the resulting .plug.json file into", ".") + .option( + "--dist ", + "Folder to put the resulting .plug.json file into", + ".", + ) .action(async (manifestPaths: string[], options: any) => { await plugCompileCommand( { diff --git a/biome.json b/biome.json index eb97ca96..1f121cd2 100644 --- a/biome.json +++ b/biome.json @@ -1,47 +1,48 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.6/schema.json", - "formatter": { - "enabled": false - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "off", - "noAssignInExpressions": "off", - "noImplicitAnyLet": "off", - "noSelfCompare": "off", - "noShadowRestrictedNames": "off", - "noTemplateCurlyInString": "off", - "noThenProperty": "off", - "useIterableCallbackReturn": "off" - }, - "style": { - "noNonNullAssertion": "off" - }, - "complexity": { - "noBannedTypes": "off" - }, - "a11y": { - "recommended": false - }, - "correctness": { - "noInnerDeclarations": "off" - }, - "nursery": { - "noFloatingPromises": "error" - } - } - }, - "files": { - "includes": [ - "**", - "!**/node_modules", - "!**/dist", - "!**/client_bundle", - "!**/website_build", - "!**/*.html" - ] - } + "$schema": "https://biomejs.dev/schemas/2.4.6/schema.json", + "formatter": { + "enabled": true, + "useEditorconfig": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off", + "noAssignInExpressions": "off", + "noImplicitAnyLet": "off", + "noSelfCompare": "off", + "noShadowRestrictedNames": "off", + "noTemplateCurlyInString": "off", + "noThenProperty": "off", + "useIterableCallbackReturn": "off" + }, + "style": { + "noNonNullAssertion": "off" + }, + "complexity": { + "noBannedTypes": "off" + }, + "a11y": { + "recommended": false + }, + "correctness": { + "noInnerDeclarations": "off" + }, + "nursery": { + "noFloatingPromises": "error" + } + } + }, + "files": { + "includes": [ + "**", + "!**/node_modules", + "!**/dist", + "!**/client_bundle", + "!**/website_build", + "!**/*.html" + ] + } } diff --git a/build_client.ts b/build_client.ts index 878ea033..e25bc697 100644 --- a/build_client.ts +++ b/build_client.ts @@ -1,6 +1,6 @@ import { cp, mkdir, readFile, writeFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; -import { dirname, } from "node:path"; +import { dirname } from "node:path"; import * as sass from "sass"; import * as esbuild from "esbuild"; diff --git a/build_worker_runtime.ts b/build_worker_runtime.ts index e41ecaf6..62bc8bee 100644 --- a/build_worker_runtime.ts +++ b/build_worker_runtime.ts @@ -3,7 +3,11 @@ import { fileURLToPath } from "node:url"; // Build worker_runtime.ts into a standalone JS file await esbuild.build({ - entryPoints: [fileURLToPath(new URL("./client/plugos/worker_runtime.ts", import.meta.url))], + entryPoints: [ + fileURLToPath( + new URL("./client/plugos/worker_runtime.ts", import.meta.url), + ), + ], bundle: true, format: "esm", platform: "browser", diff --git a/client/asset_bundle/builder.ts b/client/asset_bundle/builder.ts index 553378a2..7ab4667a 100644 --- a/client/asset_bundle/builder.ts +++ b/client/asset_bundle/builder.ts @@ -26,9 +26,7 @@ export async function bundleAssets( return bundle; } const isMatch = picomatch(patterns); - for await ( - const file of walk(rootPath) - ) { + for await (const file of walk(rootPath)) { const cleanPath = file.path.substring(rootPath.length + 1); // console.log("Considering", rootPath, file.path, cleanPath); if (isMatch(cleanPath)) { diff --git a/client/asset_bundle/bundle.test.ts b/client/asset_bundle/bundle.test.ts index 2096a56b..7693aef2 100644 --- a/client/asset_bundle/bundle.test.ts +++ b/client/asset_bundle/bundle.test.ts @@ -11,6 +11,8 @@ test("Asset bundle", () => { buf[1] = 2; buf[2] = 3; assetBundle.writeFileSync("test.bin", "application/octet-stream", buf); - expect("application/octet-stream").toEqual(assetBundle.getMimeType("test.bin")); + expect("application/octet-stream").toEqual( + assetBundle.getMimeType("test.bin"), + ); expect(buf).toEqual(assetBundle.readFileSync("test.bin")); }); diff --git a/client/asset_bundle/bundle.ts b/client/asset_bundle/bundle.ts index 35d4fa5b..7cba5c57 100644 --- a/client/asset_bundle/bundle.ts +++ b/client/asset_bundle/bundle.ts @@ -23,9 +23,7 @@ export class AssetBundle { return Object.keys(this.bundle); } - readFileSync( - path: string, - ): Uint8Array { + readFileSync(path: string): Uint8Array { const content = this.bundle[path]; if (!content) { throw new Error(`No such file ${path}`); @@ -42,15 +40,11 @@ export class AssetBundle { return content.data; } - readTextFileSync( - path: string, - ): string { + readTextFileSync(path: string): string { return new TextDecoder().decode(this.readFileSync(path)); } - getMimeType( - path: string, - ): string { + getMimeType(path: string): string { const entry = this.bundle[path]; if (!entry) { throw new Error(`No such file ${path}`); diff --git a/client/boot.ts b/client/boot.ts index 699057b3..b983d49a 100644 --- a/client/boot.ts +++ b/client/boot.ts @@ -34,18 +34,17 @@ safeRun(async () => { // Try loading config and scripts try { let configJSONText: string; - [configJSONText, ...bootstrapLuaScriptPages] = await Promise - .all([ - cachedFetch(".config"), - // Some minimal bootstrap Lua: schema definition - cachedFetch(".fs/Library/Std/APIs/Schema.md"), - // Configuration option definitions and defaults - cachedFetch(".fs/Library/Std/Config.md"), - // Tag definition API - cachedFetch(".fs/Library/Std/APIs/Tag.md"), - // Custom configuration - cachedFetch(".fs/CONFIG.md"), - ]); + [configJSONText, ...bootstrapLuaScriptPages] = await Promise.all([ + cachedFetch(".config"), + // Some minimal bootstrap Lua: schema definition + cachedFetch(".fs/Library/Std/APIs/Schema.md"), + // Configuration option definitions and defaults + cachedFetch(".fs/Library/Std/Config.md"), + // Tag definition API + cachedFetch(".fs/Library/Std/APIs/Tag.md"), + // Custom configuration + cachedFetch(".fs/CONFIG.md"), + ]); bootConfig = JSON.parse(configJSONText); } catch (e: any) { if (e.message === offlineError.message) { @@ -59,9 +58,7 @@ safeRun(async () => { // Concatenate and evaluate try { config = await loadConfig( - bootstrapLuaScriptPages.map( - extractSpaceLuaFromPageText, - ).join("\n"), + bootstrapLuaScriptPages.map(extractSpaceLuaFromPageText).join("\n"), clientProxy.buildProxy(), ); } catch (e: any) { @@ -72,10 +69,10 @@ safeRun(async () => { try { config = await loadConfig( // Everything but CONFIG (at the end) - bootstrapLuaScriptPages.slice(0, bootstrapLuaScriptPages.length - 1) - .map( - extractSpaceLuaFromPageText, - ).join("\n"), + bootstrapLuaScriptPages + .slice(0, bootstrapLuaScriptPages.length - 1) + .map(extractSpaceLuaFromPageText) + .join("\n"), clientProxy.buildProxy(), ); } catch (e: any) { @@ -101,9 +98,9 @@ safeRun(async () => { console.log( "Service worker already running, querying it for an encryption key", ); - swController.postMessage( - { type: "get-encryption-key" } as ServiceWorkerTargetMessage, - ); + swController.postMessage({ + type: "get-encryption-key", + } as ServiceWorkerTargetMessage); await race([ new Promise((resolve) => { function keyListener(e: any) { diff --git a/client/client.ts b/client/client.ts index 66eb4fa2..472ae5df 100644 --- a/client/client.ts +++ b/client/client.ts @@ -147,20 +147,15 @@ export class Client { // Widget and image height caching private widgetCache = new LimitedMap(100); // bodyText -> WidgetCacheItem debouncedWidgetCacheFlush = throttle(() => { - this.ds.set(["cache", "widgets"], this.widgetCache.toJSON()) - .catch( - console.error, - ); + this.ds + .set(["cache", "widgets"], this.widgetCache.toJSON()) + .catch(console.error); }, 2000); private widgetHeightCache = new LimitedMap(1000); // bodytext -> height debouncedWidgetHeightCacheFlush = throttle(() => { - this.ds.set( - ["cache", "widgetHeight"], - this.widgetHeightCache.toJSON(), - ) - .catch( - console.error, - ); + this.ds + .set(["cache", "widgetHeight"], this.widgetHeightCache.toJSON()) + .catch(console.error); }, 2000); objectIndex!: ObjectIndex; @@ -193,10 +188,7 @@ export class Client { // See if we need to encrypt this if (encryptionKey) { - kvPrimitives = new EncryptedKvPrimitives( - kvPrimitives, - encryptionKey, - ); + kvPrimitives = new EncryptedKvPrimitives(kvPrimitives, encryptionKey); await (kvPrimitives as EncryptedKvPrimitives).init(); console.log("Enabled client-side encryption"); } @@ -338,21 +330,13 @@ export class Client { void this.eventedSpacePrimitives.fetchFileList(); }, fetchFileListInterval + jitter()); - this.eventHook.addLocalListener( - "file:changed", - async ( - name: string, - ) => { - console.log("Queueing index for", name); - await this.objectIndex.clearFileIndex(name); - await this.mq.send("indexQueue", name); - }, - ); + this.eventHook.addLocalListener("file:changed", async (name: string) => { + console.log("Queueing index for", name); + await this.objectIndex.clearFileIndex(name); + await this.mq.send("indexQueue", name); + }); - const space = new Space( - this.eventedSpacePrimitives, - this.eventHook, - ); + const space = new Space(this.eventedSpacePrimitives, this.eventHook); this.space = space; @@ -374,16 +358,13 @@ export class Client { this.eventHook.addLocalListener( "file:changed", - ( - path: string, - oldHash: number, - newHash: number, - ) => { + (path: string, oldHash: number, newHash: number) => { // Only reload when watching the current page or document (to avoid reloading when switching pages) if ( - this.space.watchInterval && this.currentPath() === path && + this.space.watchInterval && + this.currentPath() === path && // Avoid reloading if the page was just saved (5s window) - (!lastSaveTimestamp || (lastSaveTimestamp < Date.now() - 5000)) && + (!lastSaveTimestamp || lastSaveTimestamp < Date.now() - 5000) && // Avoid reloading if the previous hash was undefined (first load) oldHash !== undefined ) { @@ -410,17 +391,14 @@ export class Client { this.eventHook.addLocalListener("file:deleted", (fileName: string) => { this.clientSystem.allKnownFiles.delete(fileName); }); - this.eventHook.addLocalListener( - "file:listed", - (allFiles: FileMeta[]) => { - // Update list of known pages - this.clientSystem.allKnownFiles.clear(); - allFiles.forEach((f) => { - this.clientSystem.allKnownFiles.add(f.name); - }); - this.clientSystem.knownFilesLoaded = true; - }, - ); + this.eventHook.addLocalListener("file:listed", (allFiles: FileMeta[]) => { + // Update list of known pages + this.clientSystem.allKnownFiles.clear(); + allFiles.forEach((f) => { + this.clientSystem.allKnownFiles.add(f.name); + }); + this.clientSystem.knownFilesLoaded = true; + }); this.space.watch(); } @@ -467,10 +445,7 @@ export class Client { } this.saveTimeout = setTimeout( () => { - if ( - !this.ui.viewState.unsavedChanges || - this.isReadOnlyMode() - ) { + if (!this.ui.viewState.unsavedChanges || this.isReadOnlyMode()) { // No unsaved changes, or read-only mode, not gonna save return resolve(); } @@ -488,15 +463,9 @@ export class Client { return resolve(); } else { console.log("Saving page", this.currentPath()); - void this.dispatchAppEvent( - "editor:pageSaving", - this.currentName(), - ); + void this.dispatchAppEvent("editor:pageSaving", this.currentName()); this.space - .writePage( - this.currentName(), - this.editorView.state.sliceDoc(0), - ) + .writePage(this.currentName(), this.editorView.state.sliceDoc(0)) .then(async (meta) => { this.ui.viewDispatch({ type: "page-saved" }); await this.dispatchAppEvent( @@ -599,15 +568,13 @@ export class Client { async updatePageListCache() { console.log("Updating page list cache"); // Check if the initial sync has been completed - const initialIndexCompleted = await this.objectIndex - .hasFullIndexCompleted(); + const initialIndexCompleted = + await this.objectIndex.hasFullIndexCompleted(); let allPages: PageMeta[] = []; if (initialIndexCompleted) { - console.log( - "Initial index complete, loading full page list via index.", - ); + console.log("Initial index complete, loading full page list via index."); // Fetch indexed pages allPages = await this.queryLuaObjects("page", {}); // Overlay augmented meta values @@ -621,15 +588,17 @@ export class Client { // this.clientSystem.ds.query({prefix: }) // Map and push aspiring pages directly into allPages allPages.push( - ...aspiringPageNames.map((name): PageMeta => ({ - ref: name, - tag: "page", - _isAspiring: true, - name: name, - created: "", // Aspiring pages don't have timestamps yet - lastModified: "", // Aspiring pages don't have timestamps yet - perm: "rw", - })), + ...aspiringPageNames.map( + (name): PageMeta => ({ + ref: name, + tag: "page", + _isAspiring: true, + name: name, + created: "", // Aspiring pages don't have timestamps yet + lastModified: "", // Aspiring pages don't have timestamps yet + perm: "rw", + }), + ), ); } else { console.log( @@ -649,10 +618,7 @@ export class Client { } catch (e) { console.error("Failed to list pages directly from space:", e); // Handle error, maybe show notification or leave list empty - this.flashNotification( - "Could not fetch page list directly.", - "error", - ); + this.flashNotification("Could not fetch page list directly.", "error"); } } @@ -707,14 +673,11 @@ export class Client { if (this.progressTimeout) { clearTimeout(this.progressTimeout); } - this.progressTimeout = setTimeout( - () => { - this.ui.viewDispatch({ - type: "set-progress", - }); - }, - 5000, - ); + this.progressTimeout = setTimeout(() => { + this.ui.viewDispatch({ + type: "set-progress", + }); + }, 5000); } // Various UI elements @@ -740,10 +703,7 @@ export class Client { }); } - prompt( - message: string, - defaultValue = "", - ): Promise { + prompt(message: string, defaultValue = ""): Promise { return new Promise((resolve) => { this.ui.viewDispatch({ type: "show-prompt", @@ -758,9 +718,7 @@ export class Client { }); } - confirm( - message: string, - ): Promise { + confirm(message: string): Promise { return new Promise((resolve) => { this.ui.viewDispatch({ type: "show-confirm", @@ -853,8 +811,7 @@ export class Client { } isReadOnlyMode(): boolean { - return this.bootConfig.readOnly || - this.currentPageMeta()?.perm === "ro"; + return this.bootConfig.readOnly || this.currentPageMeta()?.perm === "ro"; } public extractParentNodes(editorState: EditorState, currentNode: SyntaxNode) { @@ -877,12 +834,11 @@ export class Client { return parentNodes; } - editorComplete( - context: CompletionContext, - ): Promise { - return this.completeWithEvent(context, "editor:complete") as Promise< - CompletionResult | null - >; + editorComplete(context: CompletionContext): Promise { + return this.completeWithEvent( + context, + "editor:complete", + ) as Promise; } async reloadEditor() { @@ -931,11 +887,7 @@ export class Client { return parseToRef(this.bootConfig.indexPage) || { path: "index.md" }; } - async navigate( - ref: Ref | null, - replaceState = false, - newWindow = false, - ) { + async navigate(ref: Ref | null, replaceState = false, newWindow = false) { ref ??= this.getIndexRef(); if (newWindow) { @@ -953,10 +905,7 @@ export class Client { return; } - await this.pageNavigator!.navigate( - ref, - replaceState, - ); + await this.pageNavigator!.navigate(ref, replaceState); this.focus(); } @@ -966,9 +915,9 @@ export class Client { const previousPath = this.ui.viewState.current?.path; const loadingDifferentPath = previousPath - ? (previousPath !== path) - // Always load as different editor if editor is loaded from scratch - : true; + ? previousPath !== path + : // Always load as different editor if editor is loaded from scratch + true; if (previousPath) { this.space.unwatchFile(previousPath); @@ -1012,13 +961,15 @@ export class Client { path: path, }); - this.eventHook.dispatchEvent( - loadingDifferentPath - ? "editor:documentLoaded" - : "editor:documentReloaded", - path, - previousPath, - ).catch(console.error); + this.eventHook + .dispatchEvent( + loadingDifferentPath + ? "editor:documentLoaded" + : "editor:documentReloaded", + path, + previousPath, + ) + .catch(console.error); } async loadPage( @@ -1030,9 +981,9 @@ export class Client { const previousPath = this.ui.viewState.current?.path; const loadingDifferentPath = previousPath - ? (previousPath !== path) - // Always load as different page if page is loaded from scratch - : true; + ? previousPath !== path + : // Always load as different page if page is loaded from scratch + true; const pageName = getNameFromPath(path); if (previousPath) { @@ -1083,10 +1034,9 @@ export class Client { }; // Let's dispatch a editor:pageCreating event to see if anybody wants to do something before the page is created - const results = await this.dispatchAppEvent( - "editor:pageCreating", - { name: pageName } as PageCreatingEvent, - ) as PageCreatingContent[]; + const results = (await this.dispatchAppEvent("editor:pageCreating", { + name: pageName, + } as PageCreatingEvent)) as PageCreatingContent[]; if (results.length === 1) { doc.text = results[0].text; @@ -1096,8 +1046,8 @@ export class Client { const idx = doc.text.indexOf(cursorMarker); if (idx !== -1) { markerIndex = idx; - doc.text = doc.text.slice(0, idx) + - doc.text.slice(idx + cursorMarker.length); + doc.text = + doc.text.slice(0, idx) + doc.text.slice(idx + cursorMarker.length); } } else if (results.length > 1) { console.error( @@ -1123,11 +1073,9 @@ export class Client { // decorations if (await this.objectIndex.hasFullIndexCompleted()) { try { - const enrichedMeta = await this.objectIndex.getObjectByRef( - pageName, - "page", - pageName, - ) ?? doc.meta; + const enrichedMeta = + (await this.objectIndex.getObjectByRef(pageName, "page", pageName)) ?? + doc.meta; const body = document.body; body.removeAttribute("class"); @@ -1177,11 +1125,13 @@ export class Client { } } // Note: these events are dispatched asynchronously deliberately (not waiting for results) - this.eventHook.dispatchEvent( - loadingDifferentPath ? "editor:pageLoaded" : "editor:pageReloaded", - pageName, - previousPath ? getNameFromPath(previousPath) : undefined, - ).catch(console.error); + this.eventHook + .dispatchEvent( + loadingDifferentPath ? "editor:pageLoaded" : "editor:pageReloaded", + pageName, + previousPath ? getNameFromPath(previousPath) : undefined, + ) + .catch(console.error); // If a cursor marker was found for a newly-created page, place the // cursor there now (after navigateWithinPage so it doesn't get @@ -1238,11 +1188,7 @@ export class Client { .then(async (meta) => { this.ui.viewDispatch({ type: "document-editor-saved" }); - await this.dispatchAppEvent( - "editor:documentSaved", - path, - meta, - ); + await this.dispatchAppEvent("editor:documentSaved", path, meta); }) .catch(() => { this.flashNotification( @@ -1287,31 +1233,30 @@ export class Client { console.warn("Not loading custom styles, since space style is disabled"); return; } - if (!await this.objectIndex.hasFullIndexCompleted()) { + if (!(await this.objectIndex.hasFullIndexCompleted())) { console.warn( "Not loading custom styles, since full indexing has not completed yet", ); return; } - const spaceStyles = await this.queryLuaObjects( - "space-style", - { - objectVariable: "_", - orderBy: [{ + const spaceStyles = await this.queryLuaObjects("space-style", { + objectVariable: "_", + orderBy: [ + { expr: parseExpressionString("_.priority"), desc: true, - }], - }, - ); + }, + ], + }); if (!spaceStyles) { return; } // Prepare separate ` - ).join("\n\n"); + const customStylesContent = spaceStyles + .map((s) => ``) + .join("\n\n"); this.ui.viewDispatch({ type: "set-ui-option", key: "customStyles", @@ -1333,9 +1278,7 @@ export class Client { } } - getCommandsByContext( - state: AppViewState, - ): Map { + getCommandsByContext(state: AppViewState): Map { const currentEditor = client.documentEditor?.name; const commands = new Map(state.commands); for (const [k, v] of state.commands.entries()) { @@ -1366,11 +1309,10 @@ export class Client { } async loadCaches() { - const [widgetHeightCache, widgetCache] = await this - .ds.batchGet([[ - "cache", - "widgetHeight", - ], ["cache", "widgets"]]); + const [widgetHeightCache, widgetCache] = await this.ds.batchGet([ + ["cache", "widgetHeight"], + ["cache", "widgets"], + ]); this.widgetHeightCache = new LimitedMap(1000, widgetHeightCache || {}); this.widgetCache = new LimitedMap(100, widgetCache || {}); } @@ -1431,7 +1373,7 @@ export class Client { if (!isMarkdownPath(pageState.path)) return; // We can't use getOffsetFromRef here, because it is asyncronous. - let pos: number | undefined ; + let pos: number | undefined; // Don't use getOffsetFromRef, so we can show error messages if (pageState.details?.type === "header") { @@ -1531,19 +1473,14 @@ export class Client { } // Persist this page as the last opened page, we'll use this for cold start PWA loads - await this.ds.set( - ["client", "lastOpenedPath"], - locationState.path, - ); + await this.ds.set(["client", "lastOpenedPath"], locationState.path); }); // Initial navigation let ref = this.onLoadRef; if (ref.details?.type === "header" && ref.details.header === "boot") { - const path = await this.ds.get( - ["client", "lastOpenedPath"], - ) as Path; + const path = (await this.ds.get(["client", "lastOpenedPath"])) as Path; if (path) { console.log("Navigating to last opened page", getNameFromPath(path)); @@ -1581,8 +1518,8 @@ export class Client { console.log( "Got data wipe confirm, uninstalling service worker now", ); - const registrations = await navigator.serviceWorker - .getRegistrations(); + const registrations = + await navigator.serviceWorker.getRegistrations(); for (const registration of registrations) { await registration.unregister(); } @@ -1596,9 +1533,9 @@ export class Client { "Sending data wipe request to service worker", registration, ); - registration?.active?.postMessage( - { type: "wipe-data" } as ServiceWorkerTargetMessage, - ); + registration?.active?.postMessage({ + type: "wipe-data", + } as ServiceWorkerTargetMessage); }); }); } else { diff --git a/client/client_system.ts b/client/client_system.ts index 69e09fdf..9f1df825 100644 --- a/client/client_system.ts +++ b/client/client_system.ts @@ -113,10 +113,7 @@ export class ClientSystem { this.system.addHook(this.documentEditorHook); // Command hook - this.commandHook = new CommandHook( - this.readOnlyMode, - this.scriptCommands, - ); + this.commandHook = new CommandHook(this.readOnlyMode, this.scriptCommands); this.commandHook.on({ commandsUpdated: (commandMap) => { this.client.ui?.viewDispatch({ @@ -178,20 +175,14 @@ export class ClientSystem { if (!this.readOnlyMode) { // Write syscalls - this.system.registerSyscalls( - [], - spaceWriteSyscalls(this.client), - ); + this.system.registerSyscalls([], spaceWriteSyscalls(this.client)); // Syscalls that require some additional permissions this.system.registerSyscalls( ["fetch"], sandboxFetchSyscalls(this.client), ); - this.system.registerSyscalls( - ["shell"], - shellSyscalls(this.client), - ); + this.system.registerSyscalls(["shell"], shellSyscalls(this.client)); } } @@ -200,7 +191,7 @@ export class ClientSystem { console.info("Space Lua scripts are disabled, skipping loading scripts"); return; } - if (!await this.objectIndex.hasFullIndexCompleted()) { + if (!(await this.objectIndex.hasFullIndexCompleted())) { console.info( "Not loading space scripts, since full indexing has not completed yet", ); @@ -215,11 +206,9 @@ export class ClientSystem { // Reset the space script commands this.scriptCommands.clear(); - for ( - const [name, command] of Object.entries( - this.client.config.get>("commands", {}), - ) - ) { + for (const [name, command] of Object.entries( + this.client.config.get>("commands", {}), + )) { this.scriptCommands.set(name, command); } @@ -244,17 +233,21 @@ export class ClientSystem { console.warn("Not loading custom plugs as `disablePlugs` has been set"); } - await Promise.all(allPlugs.map((fileMeta) => - this.system.loadPlug( - createWorkerSandboxFromLocalPath(fileMeta.name), - fileMeta.name, - fileMeta.lastModified, - ).catch((e) => - console.error( - `Could not load plug ${fileMeta.name} error: ${e.message}`, - ) - ) - )); + await Promise.all( + allPlugs.map((fileMeta) => + this.system + .loadPlug( + createWorkerSandboxFromLocalPath(fileMeta.name), + fileMeta.name, + fileMeta.lastModified, + ) + .catch((e) => + console.error( + `Could not load plug ${fileMeta.name} error: ${e.message}`, + ), + ), + ), + ); } localSyscall(name: string, args: any[]) { diff --git a/client/codemirror/admonition.ts b/client/codemirror/admonition.ts index 00affdcc..01eb231c 100644 --- a/client/codemirror/admonition.ts +++ b/client/codemirror/admonition.ts @@ -93,8 +93,13 @@ export function admonitionPlugin() { // icon further down. const iconRange = { from: from + 2, - to: from + preSpaces.length + 2 + admonitionType.length + - postSyntax.length + 1, + to: + from + + preSpaces.length + + 2 + + admonitionType.length + + postSyntax.length + + 1, }; // The first div is the title, attach title css class diff --git a/client/codemirror/attribute.ts b/client/codemirror/attribute.ts index c607dd23..6acf8629 100644 --- a/client/codemirror/attribute.ts +++ b/client/codemirror/attribute.ts @@ -22,10 +22,9 @@ export function attributePlugin() { 1, attributeText.indexOf(":"), ); - const attributeValue = attributeText.slice( - attributeText.indexOf(":") + 1, - attributeText.length - 1, - ).trim(); + const attributeValue = attributeText + .slice(attributeText.indexOf(":") + 1, attributeText.length - 1) + .trim(); // Wrap the tag in html anchor element widgets.push( diff --git a/client/codemirror/block.ts b/client/codemirror/block.ts index dad87b19..eddcd2e3 100644 --- a/client/codemirror/block.ts +++ b/client/codemirror/block.ts @@ -8,26 +8,24 @@ import { } from "./util.ts"; export function cleanBlockPlugin() { - return decoratorStateField( - (state: EditorState) => { - const widgets: any[] = []; + return decoratorStateField((state: EditorState) => { + const widgets: any[] = []; - syntaxTree(state).iterate({ - enter(node) { - if ( - node.name === "HorizontalRule" && - !isCursorInRange(state, [node.from, node.to]) - ) { - widgets.push(invisibleDecoration.range(node.from, node.to)); - widgets.push( - Decoration.line({ - class: "sb-line-hr", - }).range(node.from), - ); - } - }, - }); - return Decoration.set(widgets, true); - }, - ); + syntaxTree(state).iterate({ + enter(node) { + if ( + node.name === "HorizontalRule" && + !isCursorInRange(state, [node.from, node.to]) + ) { + widgets.push(invisibleDecoration.range(node.from, node.to)); + widgets.push( + Decoration.line({ + class: "sb-line-hr", + }).range(node.from), + ); + } + }, + }); + return Decoration.set(widgets, true); + }); } diff --git a/client/codemirror/code_copy.ts b/client/codemirror/code_copy.ts index ca41ca13..1bf41b30 100644 --- a/client/codemirror/code_copy.ts +++ b/client/codemirror/code_copy.ts @@ -16,7 +16,10 @@ const ICON_SVG = const EXCLUDE_LANGUAGES = ["template", "include", "query", "toc", "embed"]; class CodeCopyWidget extends WidgetType { - constructor(readonly value: string, readonly client: Client) { + constructor( + readonly value: string, + readonly client: Client, + ) { super(); } @@ -38,7 +41,8 @@ class CodeCopyWidget extends WidgetType { button.onclick = (e) => { e.stopPropagation(); e.preventDefault(); - navigator.clipboard.writeText(this.value) + navigator.clipboard + .writeText(this.value) .catch((err) => { this.client.flashNotification( `Error copying to clipboard: ${err}`, @@ -58,10 +62,7 @@ class CodeCopyWidget extends WidgetType { } } -function codeCopyDecoration( - view: EditorView, - client: Client, -) { +function codeCopyDecoration(view: EditorView, client: Client) { const widgets: Range[] = []; for (const { from, to } of view.visibleRanges) { syntaxTree(view.state).iterate({ @@ -77,10 +78,7 @@ function codeCopyDecoration( } const language = infoNode - ? view.state.doc.sliceString( - infoNode.from, - infoNode.to, - ) + ? view.state.doc.sliceString(infoNode.from, infoNode.to) : undefined; if (language && EXCLUDE_LANGUAGES.includes(language)) { @@ -115,7 +113,8 @@ export const codeCopyPlugin = (client: Client) => { update(update: ViewUpdate) { if ( - update.docChanged || update.viewportChanged || + update.docChanged || + update.viewportChanged || syntaxTree(update.startState) !== syntaxTree(update.state) ) { this.decorations = codeCopyDecoration(update.view, client); diff --git a/client/codemirror/editor_state.ts b/client/codemirror/editor_state.ts index 935722b6..3dacd60a 100644 --- a/client/codemirror/editor_state.ts +++ b/client/codemirror/editor_state.ts @@ -71,9 +71,7 @@ export function createEditorState( const regularKeyBindings = createRegularKeyBindings(client); client.indentUnitCompartment = new Compartment(); - const indentUnits = client.indentUnitCompartment.of( - indentUnit.of(" "), - ); + const indentUnits = client.indentUnitCompartment.of(indentUnit.of(" ")); client.undoHistoryCompartment = new Compartment(); const undoHistory = client.undoHistoryCompartment.of([history()]); @@ -82,9 +80,12 @@ export function createEditorState( doc: text, extensions: [ // Not using CM theming right now, but some extensions depend on the "dark" thing - EditorView.theme({}, { - dark: client.ui.viewState.uiOptions.darkMode, - }), + EditorView.theme( + {}, + { + dark: client.ui.viewState.uiOptions.darkMode, + }, + ), // Insert our command key bindings *before* vim mode. Vim in normal-mode is // greedy and captures all key events, preventing them from reaching our @@ -94,19 +95,19 @@ export function createEditorState( // Enable vim mode, or not [ - ...client.ui.viewState.uiOptions.vimMode + ...(client.ui.viewState.uiOptions.vimMode ? [ - vim({ status: true }), - EditorState.allowMultipleSelections.of(true), - ] - : [], + vim({ status: true }), + EditorState.allowMultipleSelections.of(true), + ] + : []), ], [ ...(readOnly || - client.ui.viewState.uiOptions.forcedROMode || - client.bootConfig.readOnly) + client.ui.viewState.uiOptions.forcedROMode || + client.bootConfig.readOnly ? [EditorView.editable.of(false), EditorState.readOnly.of(true)] - : [], + : []), ], // The uber markdown mode @@ -127,12 +128,9 @@ export function createEditorState( }), extendedMarkdownLanguage.data.of({ closeBrackets: { - brackets: client.config.get( - "autoCloseBrackets", - undefined, - )?.split( - "", - ), + brackets: client.config + .get("autoCloseBrackets", undefined) + ?.split(""), }, }), syntaxHighlighting(customMarkdownStyle()), @@ -311,7 +309,7 @@ export function createEditorState( inserted: inserted.toString(), oldRange: { from: fromA, to: toA }, newRange: { from: fromB, to: toB }, - }) + }), ); void client.dispatchAppEvent("editor:pageModified", { changes }); client.ui.viewDispatch({ type: "page-changed" }); @@ -333,21 +331,22 @@ export function isValidEditor( currentEditor: string | undefined, requiredEditor: string | undefined, ): boolean { - return (requiredEditor === undefined) || - (currentEditor === undefined && - requiredEditor === "page") || - (requiredEditor === "any") || - (currentEditor === requiredEditor) || - (currentEditor !== undefined && requiredEditor === "notpage"); + return ( + requiredEditor === undefined || + (currentEditor === undefined && requiredEditor === "page") || + requiredEditor === "any" || + currentEditor === requiredEditor || + (currentEditor !== undefined && requiredEditor === "notpage") + ); } export function createCommandKeyBindings(client: Client): Extension { const commandKeyBindings: KeyBinding[] = []; // Then add bindings for plug commands - for ( - const def of client.clientSystem.commandHook.buildAllCommands().values() - ) { + for (const def of client.clientSystem.commandHook + .buildAllCommands() + .values()) { const currentEditor = client.documentEditor?.name; const requiredEditor = def.requireEditor; @@ -363,7 +362,8 @@ export function createCommandKeyBindings(client: Client): Extension { .then(def.run) .catch((e: any) => { client.reportError(e, "key"); - }).then((returnValue: any) => { + }) + .then((returnValue: any) => { // Always be focusing the editor after running a command UNLESS it returns false if (returnValue !== false) { client.focus(); @@ -396,9 +396,7 @@ export function createCommandKeyBindings(client: Client): Extension { } } - return keymap.of([ - ...commandKeyBindings, - ]); + return keymap.of([...commandKeyBindings]); } export function createRegularKeyBindings(client: Client): Extension { @@ -408,18 +406,18 @@ export function createRegularKeyBindings(client: Client): Extension { return keymap.of([ ...createSmartQuoteKeyBindings(client), ...closeBracketsKeymap, - ...client.ui.viewState.uiOptions.vimMode + ...(client.ui.viewState.uiOptions.vimMode ? [ - // Workaround for https://github.com/replit/codemirror-vim/issues/182; - // without this, Enter does nothing for ordinary paragraphs in insert - // mode. - { - key: "Enter", - run: insertNewlineAndIndent, - shift: insertNewlineAndIndent, - }, - ] - : standardKeymap, + // Workaround for https://github.com/replit/codemirror-vim/issues/182; + // without this, Enter does nothing for ordinary paragraphs in insert + // mode. + { + key: "Enter", + run: insertNewlineAndIndent, + shift: insertNewlineAndIndent, + }, + ] + : standardKeymap), ...completionKeymap, { key: "Tab", run: acceptCompletion }, indentWithTab, diff --git a/client/codemirror/escapes.ts b/client/codemirror/escapes.ts index cbdc3fa3..87717801 100644 --- a/client/codemirror/escapes.ts +++ b/client/codemirror/escapes.ts @@ -8,21 +8,16 @@ import { } from "./util.ts"; export function cleanEscapePlugin() { - return decoratorStateField( - (state: EditorState) => { - const widgets: any[] = []; + return decoratorStateField((state: EditorState) => { + const widgets: any[] = []; - syntaxTree(state).iterate({ - enter({ type, from, to }) { - if ( - type.name === "Escape" && - !isCursorInRange(state, [from, to]) - ) { - widgets.push(invisibleDecoration.range(from, from + 1)); - } - }, - }); - return Decoration.set(widgets, true); - }, - ); + syntaxTree(state).iterate({ + enter({ type, from, to }) { + if (type.name === "Escape" && !isCursorInRange(state, [from, to])) { + widgets.push(invisibleDecoration.range(from, from + 1)); + } + }, + }); + return Decoration.set(widgets, true); + }); } diff --git a/client/codemirror/fenced_code.ts b/client/codemirror/fenced_code.ts index 00d55bca..2a1e487b 100644 --- a/client/codemirror/fenced_code.ts +++ b/client/codemirror/fenced_code.ts @@ -22,9 +22,8 @@ export function fencedCodePlugin(client: Client) { } const text = state.sliceDoc(from, to); const [_, lang] = text.match(/^(?:```+|~~~+)(\w+)?/)!; - const codeWidgetCallback = client.clientSystem.codeWidgetHook - .codeWidgetCallbacks - .get(lang); + const codeWidgetCallback = + client.clientSystem.codeWidgetHook.codeWidgetCallbacks.get(lang); // Only custom render when we have a custom renderer, and the current page is not a template if (codeWidgetCallback && shouldRenderWidgets(client)) { // We got a custom renderer! @@ -40,7 +39,8 @@ export function fencedCodePlugin(client: Client) { fromIt += line.length + 1; } - const firstLine = lines[0], lastLine = lines[lines.length - 1]; + const firstLine = lines[0], + lastLine = lines[lines.length - 1]; // In case of doubt, back out if (!firstLine || !lastLine) return; @@ -48,9 +48,7 @@ export function fencedCodePlugin(client: Client) { widgets.push( invisibleDecoration.range(firstLine.from, firstLine.to), ); - widgets.push( - invisibleDecoration.range(lastLine.from, lastLine.to), - ); + widgets.push(invisibleDecoration.range(lastLine.from, lastLine.to)); widgets.push( Decoration.line({ class: "sb-fenced-code-iframe", @@ -86,9 +84,7 @@ export function fencedCodePlugin(client: Client) { } return true; } - if ( - name === "CodeMark" - ) { + if (name === "CodeMark") { const parent = node.parent!; // Hide ONLY if CodeMark is not insine backticks (InlineCode) and the cursor is placed outside if ( diff --git a/client/codemirror/frontmatter.ts b/client/codemirror/frontmatter.ts index 5c0d04ca..44062c50 100644 --- a/client/codemirror/frontmatter.ts +++ b/client/codemirror/frontmatter.ts @@ -17,198 +17,187 @@ import { import { processWikiLink, type WikiLinkMatch } from "./wiki_link_processor.ts"; export function frontmatterPlugin(client: Client) { - return decoratorStateField( - (state: EditorState) => { - const widgets: any[] = []; - const foldRanges = foldedRanges(state); - const shortWikiLinks = client.config.get("shortWikiLinks", true); + return decoratorStateField((state: EditorState) => { + const widgets: any[] = []; + const foldRanges = foldedRanges(state); + const shortWikiLinks = client.config.get("shortWikiLinks", true); - syntaxTree(state).iterate({ - enter(node) { - if ( - node.name === "FrontMatterMarker" - ) { - const parent = node.node.parent!; + syntaxTree(state).iterate({ + enter(node) { + if (node.name === "FrontMatterMarker") { + const parent = node.node.parent!; - const folded = foldRanges.iter(); - let shouldShowFrontmatterBanner = false; - while (folded.value) { - // Check if cursor is in the folded range - if (isCursorInRange(state, [folded.from, folded.to])) { - // console.log("Cursor is in folded area, "); - shouldShowFrontmatterBanner = true; - break; - } - folded.next(); - } - if (!isCursorInRange(state, [parent.from, parent.to])) { - widgets.push( - Decoration.line({ - class: "sb-line-frontmatter-outside", - }).range(node.from), - ); + const folded = foldRanges.iter(); + let shouldShowFrontmatterBanner = false; + while (folded.value) { + // Check if cursor is in the folded range + if (isCursorInRange(state, [folded.from, folded.to])) { + // console.log("Cursor is in folded area, "); shouldShowFrontmatterBanner = true; + break; } - if (shouldShowFrontmatterBanner && parent.from === node.from) { - // Only put this on the first line of the frontmatter + folded.next(); + } + if (!isCursorInRange(state, [parent.from, parent.to])) { + widgets.push( + Decoration.line({ + class: "sb-line-frontmatter-outside", + }).range(node.from), + ); + shouldShowFrontmatterBanner = true; + } + if (shouldShowFrontmatterBanner && parent.from === node.from) { + // Only put this on the first line of the frontmatter + widgets.push( + Decoration.widget({ + widget: new HtmlWidget(`frontmatter`, "sb-frontmatter-marker"), + }).range(node.from), + ); + } + } + + // Render links inside frontmatter code as clickable anchors (external and wiki links) + if (node.name === "FrontMatterCode") { + const oFrom = node.from; + const oTo = node.to; + + if (isCursorInRange(state, [oFrom, oTo])) { + return; + } + + const otext = state.sliceDoc(oFrom, oTo); + + let oMatch: RegExpExecArray | null; + while ((oMatch = frontmatterQuotesRegex.exec(otext)) !== null) { + const from = oFrom + (oMatch.index ?? 0); + const to = from + oMatch[0].length; + const text = state.sliceDoc(from, to); + + // 1) External links: http(s), :// URLs + frontmatterUrlRegex.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = frontmatterUrlRegex.exec(text)) !== null) { + const mFrom = from + (match.index ?? 0); + const mTo = mFrom + match[0].length; + const url = match[1]; widgets.push( - Decoration.widget({ - widget: new HtmlWidget( - `frontmatter`, - "sb-frontmatter-marker", - ), - }).range(node.from), + Decoration.replace({ + widget: new LinkWidget({ + text: url, + title: `Open ${url}`, + href: url, + cssClass: "sb-external-link", + from: mFrom, + callback: (e) => { + if (e.altKey) { + // Move cursor into the link + client.editorView.dispatch({ + selection: { anchor: mFrom }, + }); + client.focus(); + return; + } + try { + // Open http(s) links in a new window/tab, open + // alternate schemes in the same page, as they'll + // bounce to another application. + if (/^https?:\/\//i.test(url)) { + globalThis.open(url, "_blank"); + } else { + globalThis.open(url, "_self"); + } + } catch (err) { + console.error("Failed to open external link", err); + } + }, + }), + }).range(mFrom, mTo), + ); + } + + // 2) Internal links: WikiLinks [[...]] (make navigable) + frontmatterWikiLinkRegex.lastIndex = 0; + let wMatch: RegExpExecArray | null; + while ((wMatch = frontmatterWikiLinkRegex.exec(text)) !== null) { + if (!wMatch || !wMatch.groups) { + return; + } + const mFrom = from + (wMatch.index ?? 0); + const mTo = mFrom + wMatch[0].length; + + const wikiLinkMatch: WikiLinkMatch = { + leadingTrivia: wMatch.groups.leadingTrivia, + stringRef: wMatch.groups.stringRef, + alias: wMatch.groups.alias, + trailingTrivia: wMatch.groups.trailingTrivia, + }; + + const decorations = processWikiLink({ + from, + to, + match: wikiLinkMatch, + matchFrom: mFrom, + matchTo: mTo, + client, + shortWikiLinks, + state, + callback: (e, ref) => { + if (e.altKey) { + // Move cursor into the link's content + client.editorView.dispatch({ + selection: { + anchor: mFrom + wikiLinkMatch.leadingTrivia.length, + }, + }); + client.focus(); + return; + } + void client.navigate(ref, false, e.ctrlKey || e.metaKey); + }, + }); + + widgets.push(...decorations); + } + + // 3) mailto:... links + frontmatterMailtoRegex.lastIndex = 0; + let mMatch: RegExpExecArray | null; + while ((mMatch = frontmatterMailtoRegex.exec(text)) !== null) { + const mFrom = from + (mMatch.index ?? 0); + const mTo = mFrom + mMatch[0].length; + const url = mMatch[1]; + const address = url.slice(7); + widgets.push( + Decoration.replace({ + widget: new LinkWidget({ + text: url, + title: `Mail ${address}`, + href: url, + cssClass: "sb-external-link", + from: mFrom, + callback: (e) => { + if (e.altKey) { + // Move cursor into the link + client.editorView.dispatch({ + selection: { anchor: mFrom }, + }); + client.focus(); + return; + } + try { + globalThis.open(url, "_self"); + } catch (err) { + console.error("Failed to open external link", err); + } + }, + }), + }).range(mFrom, mTo), ); } } - - // Render links inside frontmatter code as clickable anchors (external and wiki links) - if (node.name === "FrontMatterCode") { - const oFrom = node.from; - const oTo = node.to; - - if (isCursorInRange(state, [oFrom, oTo])) { - return; - } - - const otext = state.sliceDoc(oFrom, oTo); - - let oMatch: RegExpExecArray | null; - while ((oMatch = frontmatterQuotesRegex.exec(otext)) !== null) { - const from = oFrom + (oMatch.index ?? 0); - const to = from + oMatch[0].length; - const text = state.sliceDoc(from, to); - - // 1) External links: http(s), :// URLs - frontmatterUrlRegex.lastIndex = 0; - let match: RegExpExecArray | null; - while ((match = frontmatterUrlRegex.exec(text)) !== null) { - const mFrom = from + (match.index ?? 0); - const mTo = mFrom + match[0].length; - const url = match[1]; - widgets.push( - Decoration.replace({ - widget: new LinkWidget({ - text: url, - title: `Open ${url}`, - href: url, - cssClass: "sb-external-link", - from: mFrom, - callback: (e) => { - if (e.altKey) { - // Move cursor into the link - client.editorView.dispatch({ - selection: { anchor: mFrom }, - }); - client.focus(); - return; - } - try { - // Open http(s) links in a new window/tab, open - // alternate schemes in the same page, as they'll - // bounce to another application. - if (/^https?:\/\//i.test(url)) { - globalThis.open(url, "_blank"); - } else { - globalThis.open(url, "_self"); - } - } catch (err) { - console.error("Failed to open external link", err); - } - }, - }), - }).range(mFrom, mTo), - ); - } - - // 2) Internal links: WikiLinks [[...]] (make navigable) - frontmatterWikiLinkRegex.lastIndex = 0; - let wMatch: RegExpExecArray | null; - while ((wMatch = frontmatterWikiLinkRegex.exec(text)) !== null) { - if (!wMatch || !wMatch.groups) { - return; - } - const mFrom = from + (wMatch.index ?? 0); - const mTo = mFrom + wMatch[0].length; - - const wikiLinkMatch: WikiLinkMatch = { - leadingTrivia: wMatch.groups.leadingTrivia, - stringRef: wMatch.groups.stringRef, - alias: wMatch.groups.alias, - trailingTrivia: wMatch.groups.trailingTrivia, - }; - - const decorations = processWikiLink({ - from, - to, - match: wikiLinkMatch, - matchFrom: mFrom, - matchTo: mTo, - client, - shortWikiLinks, - state, - callback: (e, ref) => { - if (e.altKey) { - // Move cursor into the link's content - client.editorView.dispatch({ - selection: { - anchor: mFrom + wikiLinkMatch.leadingTrivia.length, - }, - }); - client.focus(); - return; - } - void client.navigate( - ref, - false, - e.ctrlKey || e.metaKey, - ); - }, - }); - - widgets.push(...decorations); - } - - // 3) mailto:... links - frontmatterMailtoRegex.lastIndex = 0; - let mMatch: RegExpExecArray | null; - while ((mMatch = frontmatterMailtoRegex.exec(text)) !== null) { - const mFrom = from + (mMatch.index ?? 0); - const mTo = mFrom + mMatch[0].length; - const url = mMatch[1]; - const address = url.slice(7); - widgets.push( - Decoration.replace({ - widget: new LinkWidget({ - text: url, - title: `Mail ${address}`, - href: url, - cssClass: "sb-external-link", - from: mFrom, - callback: (e) => { - if (e.altKey) { - // Move cursor into the link - client.editorView.dispatch({ - selection: { anchor: mFrom }, - }); - client.focus(); - return; - } - try { - globalThis.open(url, "_self"); - } catch (err) { - console.error("Failed to open external link", err); - } - }, - }), - }).range(mFrom, mTo), - ); - } - } - } - }, - }); - return Decoration.set(widgets, true); - }, - ); + } + }, + }); + return Decoration.set(widgets, true); + }); } diff --git a/client/codemirror/hide_mark.ts b/client/codemirror/hide_mark.ts index ed11ba79..f26e4ed2 100644 --- a/client/codemirror/hide_mark.ts +++ b/client/codemirror/hide_mark.ts @@ -52,10 +52,7 @@ export function hideMarksPlugin() { // There can be a possibility that the current node is a // child eg. a bold node in a emphasis node, so check // for that or else save the node range - if ( - parentRange && - checkRangeOverlap([from, to], parentRange) - ) { + if (parentRange && checkRangeOverlap([from, to], parentRange)) { return; } else parentRange = [from, to]; if (isCursorInRange(state, [from, to])) return; @@ -66,10 +63,7 @@ export function hideMarksPlugin() { // decoration if (!markTypes.includes(type.name)) return; widgets.push( - invisibleDecoration.range( - from + markFrom, - from + markTo, - ), + invisibleDecoration.range(from + markFrom, from + markTo), ); }, }); @@ -115,12 +109,7 @@ export function hideHeaderMarkPlugin() { // Not complete header return; } - widgets.push( - invisibleDecoration.range( - from, - from + spacePos + 1, - ), - ); + widgets.push(invisibleDecoration.range(from, from + spacePos + 1)); }, }); return Decoration.set(widgets, true); diff --git a/client/codemirror/iframe_widget.ts b/client/codemirror/iframe_widget.ts index daa3b5da..c1b75821 100644 --- a/client/codemirror/iframe_widget.ts +++ b/client/codemirror/iframe_widget.ts @@ -30,10 +30,7 @@ export class IFrameWidget extends WidgetType { const iframe = createWidgetSandboxIFrame( this.client, this.bodyText, - this.codeWidgetCallback( - this.bodyText, - this.client.currentName(), - ), + this.codeWidgetCallback(this.bodyText, this.client.currentName()), (message) => { switch (message.type) { case "blur": @@ -47,27 +44,22 @@ export class IFrameWidget extends WidgetType { void this.codeWidgetCallback( this.bodyText, this.client.currentName(), - ) - .then( - (widgetContent: CodeWidgetContent | null) => { - if (widgetContent === null) { - iframe.contentWindow!.postMessage({ - type: "html", - html: "", - theme: - document.getElementsByTagName("html")[0].dataset.theme, - }); - } else { - iframe.contentWindow!.postMessage({ - type: "html", - html: widgetContent.html, - script: widgetContent.script, - theme: - document.getElementsByTagName("html")[0].dataset.theme, - }); - } - }, - ); + ).then((widgetContent: CodeWidgetContent | null) => { + if (widgetContent === null) { + iframe.contentWindow!.postMessage({ + type: "html", + html: "", + theme: document.getElementsByTagName("html")[0].dataset.theme, + }); + } else { + iframe.contentWindow!.postMessage({ + type: "html", + html: widgetContent.html, + script: widgetContent.script, + theme: document.getElementsByTagName("html")[0].dataset.theme, + }); + } + }); break; } }, @@ -80,9 +72,6 @@ export class IFrameWidget extends WidgetType { } override eq(other: WidgetType): boolean { - return ( - other instanceof IFrameWidget && - other.bodyText === this.bodyText - ); + return other instanceof IFrameWidget && other.bodyText === this.bodyText; } } diff --git a/client/codemirror/inline_content.ts b/client/codemirror/inline_content.ts index 929c5333..1a4e45d7 100644 --- a/client/codemirror/inline_content.ts +++ b/client/codemirror/inline_content.ts @@ -42,8 +42,8 @@ export function inlineContentPlugin(client: Client) { return; } - const renderingSyntax = client.ui.viewState.uiOptions - .markdownSyntaxRendering; + const renderingSyntax = + client.ui.viewState.uiOptions.markdownSyntaxRendering; const cursorIsInRange = isCursorInRange(state, [from, to]); if (cursorIsInRange) { return; @@ -65,18 +65,19 @@ export function inlineContentPlugin(client: Client) { client.space, transclusion, ); - const content = result.text !== undefined - ? { - markdown: renderToText( - await expandMarkdown( - client.space, - nameFromTransclusion(transclusion), - parseMarkdown(result.text, result.offset), - client.clientSystem.spaceLuaEnv, - ), - ), - } - : { html: result }; + const content = + result.text !== undefined + ? { + markdown: renderToText( + await expandMarkdown( + client.space, + nameFromTransclusion(transclusion), + parseMarkdown(result.text, result.offset), + client.clientSystem.spaceLuaEnv, + ), + ), + } + : { html: result }; return { _isWidget: true, diff --git a/client/codemirror/link.ts b/client/codemirror/link.ts index 9ee01163..72d5c642 100644 --- a/client/codemirror/link.ts +++ b/client/codemirror/link.ts @@ -43,19 +43,11 @@ export function linkPlugin(client: Client) { let url = groups.url; if (isLocalURL(url)) { - url = resolveMarkdownLink( - client.currentName(), - decodeURI(url), - ); + url = resolveMarkdownLink(client.currentName(), decodeURI(url)); } // Hide the start [ - widgets.push( - invisibleDecoration.range( - from, - from + 1, - ), - ); + widgets.push(invisibleDecoration.range(from, from + 1)); // Wrap the link in a href widgets.push( Decoration.mark({ @@ -69,10 +61,7 @@ export function linkPlugin(client: Client) { ); // Hide the tail end of the link widgets.push( - invisibleDecoration.range( - from + 1 + groups.title.length, - to, - ), + invisibleDecoration.range(from + 1 + groups.title.length, to), ); }, }); diff --git a/client/codemirror/lint.ts b/client/codemirror/lint.ts index 4550aeb1..b994fdb1 100644 --- a/client/codemirror/lint.ts +++ b/client/codemirror/lint.ts @@ -7,16 +7,15 @@ import type { LintEvent } from "@silverbulletmd/silverbullet/type/client"; export function plugLinter(client: Client) { return linter(async (view): Promise => { const text = view.state.sliceDoc(); - const tree = parse( - extendedMarkdownLanguage, - text, - ); - const results = (await client.dispatchAppEvent("editor:lint", { - name: client.currentName(), - pageMeta: client.currentPageMeta(), - tree, - text, - } as LintEvent)).flat(); + const tree = parse(extendedMarkdownLanguage, text); + const results = ( + await client.dispatchAppEvent("editor:lint", { + name: client.currentName(), + pageMeta: client.currentPageMeta(), + tree, + text, + } as LintEvent) + ).flat(); return results; }); } diff --git a/client/codemirror/lua_directive.ts b/client/codemirror/lua_directive.ts index 5c5320d8..bee9abf7 100644 --- a/client/codemirror/lua_directive.ts +++ b/client/codemirror/lua_directive.ts @@ -33,9 +33,7 @@ export function luaDirectivePlugin(client: Client) { let shouldRender = true; // Don't render Lua directives of federated pages (security) - if ( - !client.clientSystem.scriptsLoaded - ) { + if (!client.clientSystem.scriptsLoaded) { return Decoration.none; } @@ -88,20 +86,21 @@ export function luaDirectivePlugin(client: Client) { } try { const parsedLua = parseLua(`_(${bodyText})`) as LuaBlock; - const expr = - (parsedLua.statements[0] as LuaFunctionCallStatement).call - .args[0]; + const expr = ( + parsedLua.statements[0] as LuaFunctionCallStatement + ).call.args[0]; const tl = new LuaEnv(); tl.setLocal( "currentPage", - currentPageMeta || (client.ui.viewState.current - ? { - name: getNameFromPath( - client.ui.viewState.current.path, - ), - } - : undefined), + currentPageMeta || + (client.ui.viewState.current + ? { + name: getNameFromPath( + client.ui.viewState.current.path, + ), + } + : undefined), ); const sf = LuaStackFrame.createWithGlobalEnv( client.clientSystem.spaceLuaEnv.env, @@ -112,15 +111,12 @@ export function luaDirectivePlugin(client: Client) { ); threadLocalizedEnv.setLocal("_CTX", tl); const rawResult = singleResult( - await evalExpression( - expr, - threadLocalizedEnv, - sf, - ), + await evalExpression(expr, threadLocalizedEnv, sf), ); // keep tagged floats as-is for proper formatting if ( - isTaggedFloat(rawResult) || typeof rawResult === "number" + isTaggedFloat(rawResult) || + typeof rawResult === "number" ) { return rawResult; } @@ -138,9 +134,9 @@ export function luaDirectivePlugin(client: Client) { const source = resolveASTReference(e.sf.astCtx); if (source) { // We know the origin node of the error, let's reference it - return `**Lua error:** ${e.message} (Origin: [[${ - encodeRef(source) - }]])`; + return `**Lua error:** ${e.message} (Origin: [[${encodeRef( + source, + )}]])`; } } } diff --git a/client/codemirror/lua_widget.ts b/client/codemirror/lua_widget.ts index cdeb8f6f..1b1192b7 100644 --- a/client/codemirror/lua_widget.ts +++ b/client/codemirror/lua_widget.ts @@ -31,19 +31,21 @@ export type EventPayLoad = { data: any; }; -export type LuaWidgetContent = { - // Magic marker - _isWidget?: true; - // Render as HTML - html?: string | HTMLElement; - // Render as markdown - markdown?: string; - // CSS classes for wrapper - cssClasses?: string[]; - display?: "block" | "inline"; - // Event handlers - events?: Record void>; -} | string; +export type LuaWidgetContent = + | { + // Magic marker + _isWidget?: true; + // Render as HTML + html?: string | HTMLElement; + // Render as markdown + markdown?: string; + // CSS classes for wrapper + cssClasses?: string[]; + display?: "block" | "inline"; + // Event handlers + events?: Record void>; + } + | string; export class LuaWidget extends WidgetType { public dom?: HTMLElement; @@ -97,38 +99,34 @@ export class LuaWidget extends WidgetType { private buildInlineRenderer(): (text: string) => string { return (text: string): string => { const mdTree = parse(extendedMarkdownLanguage, text); - return renderMarkdownToHtml(mdTree, { - shortWikiLinks: this.client.config.get("shortWikiLinks", false), - translateUrls: (url) => { - if (isLocalURL(url)) { - url = resolveMarkdownLink( - this.client.currentName(), - decodeURI(url), - ); - } - return url; + return renderMarkdownToHtml( + mdTree, + { + shortWikiLinks: this.client.config.get("shortWikiLinks", false), + translateUrls: (url) => { + if (isLocalURL(url)) { + url = resolveMarkdownLink( + this.client.currentName(), + decodeURI(url), + ); + } + return url; + }, + preserveAttributes: true, }, - preserveAttributes: true, - }, this.client.ui.viewState.allPages); + this.client.ui.viewState.allPages, + ); }; } - async renderContent( - div: HTMLElement, - ) { + async renderContent(div: HTMLElement) { const currentName = this.client.currentName(); - let widgetContent = await this.callback( - this.expressionText, - currentName, - ); + let widgetContent = await this.callback(this.expressionText, currentName); activeWidgets.add(this); if (widgetContent === null || widgetContent === undefined) { if (!this.renderEmpty) { div.innerHTML = ""; - this.client.setWidgetCache( - this.cacheKey, - { html: "", block: false }, - ); + this.client.setWidgetCache(this.cacheKey, { html: "", block: false }); this.client.setCachedWidgetHeight(this.cacheKey, div.clientHeight); return; } @@ -137,7 +135,7 @@ export class LuaWidget extends WidgetType { let html: HTMLElement | undefined; let block = false; - let copyContent: string | undefined ; + let copyContent: string | undefined; // Normalization (non-widget results go through dual-path rendering) if (typeof widgetContent === "string" || !widgetContent._isWidget) { @@ -153,9 +151,8 @@ export class LuaWidget extends WidgetType { widgetContent = { _isWidget: true, html: `${displayHtml}`, - display: (dataType === "table" || dataType === "list") - ? "block" - : "inline", + display: + dataType === "table" || dataType === "list" ? "block" : "inline", }; copyContent = markdownCopy; } @@ -183,10 +180,7 @@ export class LuaWidget extends WidgetType { } } if (wc.markdown) { - let mdTree = parse( - extendedMarkdownLanguage, - wc.markdown || "", - ); + let mdTree = parse(extendedMarkdownLanguage, wc.markdown || ""); mdTree = await expandMarkdown( client.space, @@ -201,15 +195,13 @@ export class LuaWidget extends WidgetType { if (!trimmedMarkdown) { // Net empty result after expansion div.innerHTML = ""; - this.client.setWidgetCache( - this.cacheKey, - { html: "", block: false }, - ); + this.client.setWidgetCache(this.cacheKey, { html: "", block: false }); this.client.setCachedWidgetHeight(this.cacheKey, div.clientHeight); return; } - block = wc._isWidget && wc.display === "block" || + block = + (wc._isWidget && wc.display === "block") || isBlockMarkdown(trimmedMarkdown); if (block) { div.className += " sb-lua-directive-block"; @@ -218,25 +210,28 @@ export class LuaWidget extends WidgetType { } // Parse the markdown again after trimming - mdTree = parse( - extendedMarkdownLanguage, - trimmedMarkdown, + mdTree = parse(extendedMarkdownLanguage, trimmedMarkdown); + + html = parseHtmlString( + renderMarkdownToHtml( + mdTree, + { + shortWikiLinks: this.client.config.get("shortWikiLinks", false), + translateUrls: (url) => { + if (isLocalURL(url)) { + url = resolveMarkdownLink( + this.client.currentName(), + decodeURI(url), + ); + } + + return url; + }, + preserveAttributes: true, + }, + this.client.ui.viewState.allPages, + ), ); - - html = parseHtmlString(renderMarkdownToHtml(mdTree, { - shortWikiLinks: this.client.config.get("shortWikiLinks", false), - translateUrls: (url) => { - if (isLocalURL(url)) { - url = resolveMarkdownLink( - this.client.currentName(), - decodeURI(url), - ); - } - - return url; - }, - preserveAttributes: true, - }, this.client.ui.viewState.allPages)); } if (html) { div.replaceChildren(this.wrapHtml(block, html, copyContent)); @@ -250,14 +245,11 @@ export class LuaWidget extends WidgetType { // Let's give it a tick, then measure and cache setTimeout(() => { - this.client.setWidgetCache( - this.cacheKey, - { - html: html?.outerHTML || "", - block, - copyContent: copyContent, - }, - ); + this.client.setWidgetCache(this.cacheKey, { + html: html?.outerHTML || "", + block, + copyContent: copyContent, + }); this.client.setCachedWidgetHeight(this.cacheKey, div.offsetHeight); // Because of the rejiggering of the DOM, we need to do a no-op cursor move to make sure it's positioned correctly this.client.editorView.dispatch({ @@ -281,13 +273,15 @@ export class LuaWidget extends WidgetType { const buttonBar = document.createElement("div"); buttonBar.className = "button-bar"; - const createButton = ( - { title, icon, listener }: { - title: string; - icon: string; - listener: (event: MouseEvent) => void; - }, - ) => { + const createButton = ({ + title, + icon, + listener, + }: { + title: string; + icon: string; + listener: (event: MouseEvent) => void; + }) => { const button = document.createElement("button"); button.setAttribute("data-button", title.toLowerCase()); button.setAttribute("title", title); @@ -297,64 +291,58 @@ export class LuaWidget extends WidgetType { return button; }; - buttonBar.appendChild(createButton( - { + buttonBar.appendChild( + createButton({ title: "Reload", - icon: - '', + icon: '', listener: (e) => { e.stopPropagation(); - this.client.clientSystem.localSyscall( - "system.invokeFunction", - ["index.refreshWidgets"], - ).catch(console.error); + this.client.clientSystem + .localSyscall("system.invokeFunction", ["index.refreshWidgets"]) + .catch(console.error); }, - }, - )); + }), + ); if (copyContent) { - buttonBar.appendChild(createButton( - { + buttonBar.appendChild( + createButton({ title: "Copy", - icon: - ``, + icon: ``, listener: (e) => { e.stopPropagation(); - this.client.clientSystem.localSyscall( - "editor.copyToClipboard", - [copyContent], - ).catch(console.error); + this.client.clientSystem + .localSyscall("editor.copyToClipboard", [copyContent]) + .catch(console.error); }, - }, - )); + }), + ); } if (this.inPage) { - buttonBar.appendChild(createButton( - { + buttonBar.appendChild( + createButton({ title: "Edit", - icon: - '', + icon: '', listener: (e) => { e.stopPropagation(); moveCursorIntoText(this.client, this.codeText); }, - }, - )); + }), + ); } if (this.openRef) { - buttonBar.appendChild(createButton( - { + buttonBar.appendChild( + createButton({ title: "Open", - icon: - '', + icon: '', listener: (e) => { e.stopPropagation(); void this.client.navigate(this.openRef!); }, - }, - )); + }), + ); } const content = document.createElement("div"); diff --git a/client/codemirror/smart_quotes.ts b/client/codemirror/smart_quotes.ts index e031fc8e..90856090 100644 --- a/client/codemirror/smart_quotes.ts +++ b/client/codemirror/smart_quotes.ts @@ -62,18 +62,15 @@ function keyBindingForQuote( ), }; } else { - const quote = (/\W/.exec(chBefore) && !/[!?,.\-=โ€œ]/.exec(chBefore)) - ? left - : right; + const quote = + /\W/.exec(chBefore) && !/[!?,.\-=โ€œ]/.exec(chBefore) ? left : right; return { changes: { insert: quote, from: cursorPos, }, - range: EditorSelection.cursor( - range.anchor + quote.length, - ), + range: EditorSelection.cursor(range.anchor + quote.length), }; } }); @@ -86,9 +83,7 @@ function keyBindingForQuote( export function createSmartQuoteKeyBindings(client: Client): KeyBinding[] { const smartQuotes = client.config.get("smartQuotes", {}); - if ( - smartQuotes.enabled === false - ) { + if (smartQuotes.enabled === false) { return []; } diff --git a/client/codemirror/table.ts b/client/codemirror/table.ts index 51e93ae5..bfe15fa7 100644 --- a/client/codemirror/table.ts +++ b/client/codemirror/table.ts @@ -34,9 +34,7 @@ class TableViewWidget extends WidgetType { } override get estimatedHeight(): number { - return this.client.getCachedWidgetHeight( - `table:${this.tableBodyText}`, - ); + return this.client.getCachedWidgetHeight(`table:${this.tableBodyText}`); } toDOM(): HTMLElement { @@ -119,7 +117,8 @@ export function tablePlugin(editor: Client) { fromIt += line.length + 1; } - const firstLine = lines[0], lastLine = lines[lines.length - 1]; + const firstLine = lines[0], + lastLine = lines[lines.length - 1]; // In case of doubt, back out if (!firstLine || !lastLine) return; diff --git a/client/codemirror/task.ts b/client/codemirror/task.ts index 69eb24bb..8bee6c3d 100644 --- a/client/codemirror/task.ts +++ b/client/codemirror/task.ts @@ -34,9 +34,11 @@ class CheckboxWidget extends WidgetType { } } -export function taskListPlugin( - { onCheckboxClick }: { onCheckboxClick: (pos: number) => void }, -) { +export function taskListPlugin({ + onCheckboxClick, +}: { + onCheckboxClick: (pos: number) => void; +}) { return decoratorStateField((state) => { const widgets: any[] = []; syntaxTree(state).iterate({ diff --git a/client/codemirror/top_bottom_panels.ts b/client/codemirror/top_bottom_panels.ts index 9a9293b6..e60e843c 100644 --- a/client/codemirror/top_bottom_panels.ts +++ b/client/codemirror/top_bottom_panels.ts @@ -11,9 +11,7 @@ class ArrayWidget extends WidgetType { constructor( readonly client: Client, readonly cacheKey: string, - readonly callback: ( - pageName: string, - ) => Promise, + readonly callback: (pageName: string) => Promise, readonly childClass: string, ) { super(); @@ -41,9 +39,7 @@ class ArrayWidget extends WidgetType { return div; } - async renderContent( - div: HTMLElement, - ) { + async renderContent(div: HTMLElement) { const content = await this.callback(this.client.currentName()); if (!content) return; @@ -58,7 +54,8 @@ class ArrayWidget extends WidgetType { (widgetContent instanceof Object && !widgetContent.markdown && !widgetContent.html) - ) continue; + ) + continue; const widget = new LuaWidget( this.client, @@ -109,9 +106,7 @@ class ArrayWidget extends WidgetType { } } -export function postScriptPrefacePlugin( - editor: Client, -) { +export function postScriptPrefacePlugin(editor: Client) { return decoratorStateField((state: EditorState) => { if (!editor.clientSystem.scriptsLoaded) { // console.info("System not yet ready, not rendering panel widgets."); @@ -124,10 +119,7 @@ export function postScriptPrefacePlugin( widget: new ArrayWidget( editor, `top:lua:${editor.currentPath()}`, - async () => - await client.dispatchAppEvent( - "hooks:renderTopWidgets", - ), + async () => await client.dispatchAppEvent("hooks:renderTopWidgets"), "sb-lua-top-widget", ), side: -1, @@ -141,9 +133,7 @@ export function postScriptPrefacePlugin( editor, `bottom:lua:${editor.currentPath()}`, async () => - await client.dispatchAppEvent( - "hooks:renderBottomWidgets", - ), + await client.dispatchAppEvent("hooks:renderBottomWidgets"), "sb-lua-bottom-widget", ), side: 1, diff --git a/client/codemirror/util.ts b/client/codemirror/util.ts index 9a3ad62d..9649e9cf 100644 --- a/client/codemirror/util.ts +++ b/client/codemirror/util.ts @@ -20,9 +20,7 @@ type LinkOptions = { }; export class LinkWidget extends WidgetType { - constructor( - readonly options: LinkOptions, - ) { + constructor(readonly options: LinkOptions) { super(); } @@ -64,11 +62,13 @@ export class LinkWidget extends WidgetType { } override eq(other: WidgetType): boolean { - return other instanceof LinkWidget && + return ( + other instanceof LinkWidget && this.options.from === other.options.from && this.options.text === other.options.text && this.options.href === other.options.href && - this.options.title === other.options.title; + this.options.title === other.options.title + ); } } @@ -170,7 +170,7 @@ export function checkRangeSubset( */ export function isCursorInRange(state: EditorState, range: [number, number]) { return state.selection.ranges.some((selection) => - checkRangeOverlap(range, [selection.from, selection.to]) + checkRangeOverlap(range, [selection.from, selection.to]), ); } @@ -180,6 +180,8 @@ export function isCursorInRange(state: EditorState, range: [number, number]) { export const invisibleDecoration = Decoration.replace({}); export function shouldRenderWidgets(client: Client) { - return client.systemReady && - client.currentPageMeta()?.pageDecoration?.renderWidgets !== false; + return ( + client.systemReady && + client.currentPageMeta()?.pageDecoration?.renderWidgets !== false + ); } diff --git a/client/codemirror/widget_util.ts b/client/codemirror/widget_util.ts index 403f07f1..df2d950f 100644 --- a/client/codemirror/widget_util.ts +++ b/client/codemirror/widget_util.ts @@ -67,13 +67,8 @@ export function attachWidgetEventHandlers( el.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); - console.info( - "Command link clicked in widget, running", - parsedOnclick, - ); - client.runCommandByName(command, parsedOnclick[2]).catch( - console.error, - ); + console.info("Command link clicked in widget, running", parsedOnclick); + client.runCommandByName(command, parsedOnclick[2]).catch(console.error); }); } }); @@ -83,30 +78,26 @@ export function attachWidgetEventHandlers( const taskRef = el.dataset.externalTaskRef; const input = el.querySelector("input[type=checkbox]"); if (input) { - input.addEventListener( - "click", - (e: any) => { - // Avoid triggering the click on the parent - e.stopPropagation(); - }, - ); - input.addEventListener( - "change", - (e: any) => { - e.stopPropagation(); - const oldState = e.target.dataset.state; - const newState = oldState === " " ? "x" : " "; - // Update state in DOM as well for future toggles - e.target.dataset.state = newState; - console.log("Toggling task", taskRef); - client.clientSystem.localSyscall( - "system.invokeFunction", - ["index.updateTaskState", taskRef, oldState, newState], - ).catch( - console.error, - ); - }, - ); + input.addEventListener("click", (e: any) => { + // Avoid triggering the click on the parent + e.stopPropagation(); + }); + input.addEventListener("change", (e: any) => { + e.stopPropagation(); + const oldState = e.target.dataset.state; + const newState = oldState === " " ? "x" : " "; + // Update state in DOM as well for future toggles + e.target.dataset.state = newState; + console.log("Toggling task", taskRef); + client.clientSystem + .localSyscall("system.invokeFunction", [ + "index.updateTaskState", + taskRef, + oldState, + newState, + ]) + .catch(console.error); + }); } }); diff --git a/client/codemirror/wiki_link.ts b/client/codemirror/wiki_link.ts index 755dbcd8..937cec51 100644 --- a/client/codemirror/wiki_link.ts +++ b/client/codemirror/wiki_link.ts @@ -63,9 +63,7 @@ export function cleanWikiLinkPlugin(client: Client) { altKey: e.altKey, pos: from, }; - client.dispatchClickEvent(clickEvent).catch( - console.error, - ); + client.dispatchClickEvent(clickEvent).catch(console.error); }, }); diff --git a/client/codemirror/wiki_link_processor.ts b/client/codemirror/wiki_link_processor.ts index fccd3ea5..eeddd42b 100644 --- a/client/codemirror/wiki_link_processor.ts +++ b/client/codemirror/wiki_link_processor.ts @@ -47,8 +47,8 @@ export function processWikiLink(options: WikiLinkProcessorOptions): any[] { } else if (ref.path === "" || isBuiltinPath(ref.path)) { linkStatus = "default"; } else if ( - Array.from(client.clientSystem.allKnownFiles).some((file) => - file === ref.path + Array.from(client.clientSystem.allKnownFiles).some( + (file) => file === ref.path, ) ) { linkStatus = "default"; @@ -58,8 +58,8 @@ export function processWikiLink(options: WikiLinkProcessorOptions): any[] { let css = { "file-missing": "sb-wiki-link-missing", - "invalid": "sb-wiki-link-invalid", - "default": "", + invalid: "sb-wiki-link-invalid", + default: "", }[linkStatus]; const renderingSyntax = client.ui.viewState.uiOptions.markdownSyntaxRendering; @@ -78,17 +78,17 @@ export function processWikiLink(options: WikiLinkProcessorOptions): any[] { const cleanedPath = ref ? getNameFromPath(ref.path) : stringRef; const helpText = { - "default": `Navigate to ${cleanedPath}`, + default: `Navigate to ${cleanedPath}`, "file-missing": `Create ${cleanedPath}`, - "invalid": `Cannot create invalid file ${cleanedPath}`, + invalid: `Cannot create invalid file ${cleanedPath}`, }[linkStatus]; let linkText = alias || stringRef; // The `&& ref` is only there to make typescript happy if (linkStatus === "default" && ref) { - const meta = client.ui.viewState.allPages.find((p) => - parseToRef(p.ref)?.path === ref.path + const meta = client.ui.viewState.allPages.find( + (p) => parseToRef(p.ref)?.path === ref.path, ); const renderedRef = structuredClone(ref); @@ -100,15 +100,16 @@ export function processWikiLink(options: WikiLinkProcessorOptions): any[] { ? fileName(renderedRef.path) : renderedRef.path; - const prefix = (ref.details?.type === "position" || - ref.details?.type === "linecolumn") - ? "" - : (meta?.pageDecoration?.prefix ?? ""); + const prefix = + ref.details?.type === "position" || ref.details?.type === "linecolumn" + ? "" + : (meta?.pageDecoration?.prefix ?? ""); - linkText = alias || (prefix + encodeRef(renderedRef)); + linkText = alias || prefix + encodeRef(renderedRef); if (meta?.pageDecoration?.cssClasses) { - css += " sb-decorated-object " + + css += + " sb-decorated-object " + meta.pageDecoration.cssClasses .join(" ") .replaceAll(/[^a-zA-Z0-9-_ ]/g, ""); diff --git a/client/components/anything_picker.tsx b/client/components/anything_picker.tsx index 7b182798..ac1f5e71 100644 --- a/client/components/anything_picker.tsx +++ b/client/components/anything_picker.tsx @@ -44,7 +44,7 @@ export function AnythingPicker({ let orderId = isViewable ? -new Date(documentMeta.lastModified).getTime() - : (Number.MAX_VALUE - new Date(documentMeta.lastModified).getTime()); + : Number.MAX_VALUE - new Date(documentMeta.lastModified).getTime(); if (currentPath === documentMeta.name) { orderId = Infinity; @@ -53,7 +53,8 @@ export function AnythingPicker({ // Can't really add tags to document as of right now, but maybe in the future let description: string | undefined; if (documentMeta.tags) { - description = (description || "") + + description = + (description || "") + documentMeta.tags.map((tag) => `#${tag}`).join(" "); } @@ -86,13 +87,12 @@ export function AnythingPicker({ orderId = -pageMeta.lastOpened; } // Or it's the currently open page - if ( - currentPath === `${pageMeta.name}.md` || pageMeta._isAspiring - ) { + if (currentPath === `${pageMeta.name}.md` || pageMeta._isAspiring) { // ... then we put it all the way to the end orderId = Infinity; } - const cssClass = (pageMeta.pageDecoration?.cssClasses || []).join(" ") + const cssClass = (pageMeta.pageDecoration?.cssClasses || []) + .join(" ") .replaceAll(/[^a-zA-Z0-9-_ ]/g, ""); if (mode === "page") { @@ -109,7 +109,8 @@ export function AnythingPicker({ description = `(a.k.a. ${aliases.join(", ")}) `; } if (pageMeta.tags) { - description = (description || "") + + description = + (description || "") + pageMeta.tags.map((tag) => `#${tag}`).join(" "); } options.push({ @@ -139,7 +140,8 @@ export function AnythingPicker({ orderId: orderId, cssClass, }); - } else { // all + } else { + // all // In mode "all" just show the full path and all tags let description: string | undefined; if (pageMeta.tags) { @@ -157,8 +159,7 @@ export function AnythingPicker({ } } - const completePrefix = - `${folderName(currentPath) || getNameFromPath(currentPath)}/`; + const completePrefix = `${folderName(currentPath) || getNameFromPath(currentPath)}/`; const allowNew = mode !== "document"; const creatablePageNoun = mode !== "all" ? mode : "page"; @@ -166,13 +167,15 @@ export function AnythingPicker({ return ( page.meta.tags.find((itemTag: string) => - itemTag.startsWith(tag) - ) + itemTag.startsWith(tag), + ), ); }); } @@ -229,17 +232,19 @@ export function AnythingPicker({ if (mode !== "all") { // Filter out hidden pages - options = options.filter((page) => - !(page.meta.pageDecoration?.hide === true) + options = options.filter( + (page) => !(page.meta.pageDecoration?.hide === true), ); } return options; }} allowNew={allowNew} - helpText={`Press Enter to open the selected ${openablePageNoun}` + + helpText={ + `Press Enter to open the selected ${openablePageNoun}` + (allowNew ? `, or Shift-Enter to create a new ${creatablePageNoun} with this exact name.` - : "")} + : "") + } newHint={`Create ${creatablePageNoun}`} completePrefix={completePrefix} onSelect={(opt) => { @@ -258,6 +263,8 @@ export function AnythingPicker({ } function isMetaPageOption(page: FilterOption) { - return page.meta.tags?.includes("template") || - page.meta.tags?.find((tag: string) => tag.startsWith("meta")); + return ( + page.meta.tags?.includes("template") || + page.meta.tags?.find((tag: string) => tag.startsWith("meta")) + ); } diff --git a/client/components/command_palette.tsx b/client/components/command_palette.tsx index 60483cc8..e10017b9 100644 --- a/client/components/command_palette.tsx +++ b/client/components/command_palette.tsx @@ -24,9 +24,8 @@ export function CommandPalette({ options.push({ name: name, hint: keyboardHint(def), - orderId: def.lastRun !== undefined - ? -def.lastRun - : def.priority || Infinity, + orderId: + def.lastRun !== undefined ? -def.lastRun : def.priority || Infinity, }); // console.log("Options", options); } diff --git a/client/components/filter.tsx b/client/components/filter.tsx index 0fc98291..43a3957f 100644 --- a/client/components/filter.tsx +++ b/client/components/filter.tsx @@ -44,10 +44,7 @@ export function FilterList({ }) { const [text, setText] = useState(""); const [matchingOptions, setMatchingOptions] = useState( - fuzzySearchAndSort( - preFilter ? preFilter(options, "") : options, - "", - ), + fuzzySearchAndSort(preFilter ? preFilter(options, "") : options, ""), ); const [selectedOption, setSelectionOption] = useState(0); @@ -61,8 +58,8 @@ export function FilterList({ originalPhrase = phrasePreprocessor(originalPhrase); } const results = fuzzySearchAndSort(prefilteredOptions, originalPhrase); - const foundExactMatch = !!results.find((result) => - result.name === originalPhrase + const foundExactMatch = !!results.find( + (result) => result.name === originalPhrase, ); if (allowNew && !foundExactMatch && originalPhrase) { results.splice(1, 0, { @@ -153,15 +150,9 @@ export function FilterList({ return false; }} onKeyDown={(view, e) => { - if ( - e.key === "ArrowUp" || - e.ctrlKey && e.key === "p" - ) { + if (e.key === "ArrowUp" || (e.ctrlKey && e.key === "p")) { setSelectionOption(Math.max(0, selectedOption - 1)); - } else if ( - e.key === "ArrowDown" || - e.ctrlKey && e.key === "n" - ) { + } else if (e.key === "ArrowDown" || (e.ctrlKey && e.key === "n")) { setSelectionOption( Math.min(matchingOptions.length - 1, selectedOption + 1), ); @@ -176,8 +167,9 @@ export function FilterList({ } else if (e.key === "End") { setSelectionOption(matchingOptions.length - 1); } else if ( - (e.key === " ") && completePrefix && - (view.state.sliceDoc() === "") + e.key === " " && + completePrefix && + view.state.sliceDoc() === "" ) { setText(completePrefix); } else { @@ -198,62 +190,65 @@ export function FilterList({
-
+ >
{matchingOptions && matchingOptions.length > 0 ? (() => { - let optionIndex = 0; + let optionIndex = 0; - return matchingOptions.map((option) => { - const currentOptionIndex = optionIndex; - optionIndex++; + return matchingOptions.map((option) => { + const currentOptionIndex = optionIndex; + optionIndex++; - return ( -
{ - if (selectedOption !== currentOptionIndex) { - setSelectionOption(currentOptionIndex); + return ( +
{ - e.stopPropagation(); - onSelect(option); - }} - > - {Icon && ( - - + className={ + (selectedOption === currentOptionIndex + ? "sb-option sb-selected-option" + : "sb-option") + + (option.cssClass + ? ` sb-decorated-object ${option.cssClass}` + : "") + } + onMouseMove={() => { + if (selectedOption !== currentOptionIndex) { + setSelectionOption(currentOptionIndex); + } + }} + onClick={(e) => { + e.stopPropagation(); + onSelect(option); + }} + > + {Icon && ( + + + + )} + + {(option.prefix ?? "") + option.name} - )} - - {(option.prefix ?? "") + option.name} - - {option.hint && ( - - {option.hint} - - )} -
- {option.description} + {option.hint && ( + + {option.hint} + + )} +
{option.description}
-
- ); - }); - })() + ); + }); + })() : null}
diff --git a/client/components/mini_editor.tsx b/client/components/mini_editor.tsx index 9ae447ef..644cb6be 100644 --- a/client/components/mini_editor.tsx +++ b/client/components/mini_editor.tsx @@ -104,7 +104,8 @@ export function MiniEditor({ } }, [text, vimMode]); - let onBlurred = false, onEntered = false; + let onBlurred = false, + onEntered = false; return (
{ - // Reset the state - view.setState(buildEditorState()); - }); + Promise.resolve( + callbacksRef.current!.onBlur(view.state.sliceDoc()), + ).catch(() => { + // Reset the state + view.setState(buildEditorState()); + }); } // Event may occur again in 500ms setTimeout(() => { diff --git a/client/components/panel.tsx b/client/components/panel.tsx index 0ac2d4df..39fe11dc 100644 --- a/client/components/panel.tsx +++ b/client/components/panel.tsx @@ -72,8 +72,9 @@ function IFramePanel({ switch (data.type) { case "syscall": { const { id, name, args } = data; - editor.clientSystem.localSyscall(name, args).then( - (result) => { + editor.clientSystem + .localSyscall(name, args) + .then((result) => { if (!iFrameRef.current?.contentWindow) { // iFrame already went away return; @@ -83,18 +84,18 @@ function IFramePanel({ id, result, }); - }, - ).catch((e: any) => { - if (!iFrameRef.current?.contentWindow) { - // iFrame already went away - return; - } - iFrameRef.current!.contentWindow!.postMessage({ - type: "syscall-response", - id, - error: e.message, + }) + .catch((e: any) => { + if (!iFrameRef.current?.contentWindow) { + // iFrame already went away + return; + } + iFrameRef.current!.contentWindow!.postMessage({ + type: "syscall-response", + id, + error: e.message, + }); }); - }); break; } } @@ -111,17 +112,13 @@ function IFramePanel({ srcDoc={html} ref={iFrameRef} style={{ visibility: "hidden" }} - onLoad={() => iFrameRef.current!.style.visibility = "visible"} + onLoad={() => (iFrameRef.current!.style.visibility = "visible")} />
); } -function ShadowPanel({ - config, -}: { - config: PanelConfig; -}) { +function ShadowPanel({ config }: { config: PanelConfig }) { const panelRef = useRef(null); const shadowRef = useRef(null); function updateContent() { @@ -159,10 +156,6 @@ function ShadowPanel({ }, [config.html, config.script]); return ( -
+
); } diff --git a/client/components/top_bar.tsx b/client/components/top_bar.tsx index 75479c4e..1998e8f6 100644 --- a/client/components/top_bar.tsx +++ b/client/components/top_bar.tsx @@ -61,12 +61,14 @@ export function TopBar({
{pageNamePrefix}
{ void onRename(newName); }} - editable={!client.ui.viewState.uiOptions.forcedROMode && - !client.bootConfig.readOnly} + editable={ + !client.ui.viewState.uiOptions.forcedROMode && + !client.bootConfig.readOnly + } /> {notifications.length > 0 && ( @@ -99,24 +103,24 @@ export function TopBar({
)}
- {progressPercentage !== undefined && - ( + {progressPercentage !== undefined && ( +
-
- {progressPercentage} -
+ {progressPercentage}
- )} +
+ )}
{actionButtons.map((actionButton) => { const button = ( @@ -130,7 +134,8 @@ export function TopBar({ onBlur={() => { // Close the hamburger menu in mobile mode if the action button loses focus after callback if (mobileMenuStyle === "hamburger") { - document.querySelector("#sb-top .sb-actions.hamburger") + document + .querySelector("#sb-top .sb-actions.hamburger") ?.classList.remove("open"); } }} @@ -141,13 +146,13 @@ export function TopBar({ ); - return actionButton.href - ? ( - - {button} - - ) - : button; + return actionButton.href ? ( + + {button} + + ) : ( + button + ); })}
diff --git a/client/components/widget_sandbox_iframe.ts b/client/components/widget_sandbox_iframe.ts index 1637981d..64984b28 100644 --- a/client/components/widget_sandbox_iframe.ts +++ b/client/components/widget_sandbox_iframe.ts @@ -29,7 +29,8 @@ function updatePool(exclude?: PreloadedIFrame) { } if ( // Is this iframe in use, but has it since been removed from the DOM? - preloadedIframe.used && !document.body.contains(preloadedIframe.iframe) + preloadedIframe.used && + !document.body.contains(preloadedIframe.iframe) ) { // Ditch it // console.log("Garbage collecting iframe", preloadedIframe); @@ -111,97 +112,99 @@ export function mountIFrame( ) { const iframe = preloadedIFrame.iframe; - preloadedIFrame.ready.then(async () => { - const messageListener = (evt: any) => { - (async () => { - if (evt.source !== iframe.contentWindow) { - return; - } - const data = evt.data; - if (!data) { - return; - } - switch (data.type) { - case "syscall": { - const { id, name, args } = data; - try { - const result = await client.clientSystem.localSyscall(name, args); - if (!iframe.contentWindow) { - // iFrame already went away - return; - } - iframe.contentWindow!.postMessage({ - type: "syscall-response", - id, - result, - }); - } catch (e: any) { - if (!iframe.contentWindow) { - // iFrame already went away - return; - } - iframe.contentWindow!.postMessage({ - type: "syscall-response", - id, - error: e.message, - }); - } - break; + preloadedIFrame.ready + .then(async () => { + const messageListener = (evt: any) => { + (async () => { + if (evt.source !== iframe.contentWindow) { + return; } - case "setHeight": - // iframe.height = data.height + "px"; - iframe.style.height = `${data.height}px`; + const data = evt.data; + if (!data) { + return; + } + switch (data.type) { + case "syscall": { + const { id, name, args } = data; + try { + const result = await client.clientSystem.localSyscall( + name, + args, + ); + if (!iframe.contentWindow) { + // iFrame already went away + return; + } + iframe.contentWindow!.postMessage({ + type: "syscall-response", + id, + result, + }); + } catch (e: any) { + if (!iframe.contentWindow) { + // iFrame already went away + return; + } + iframe.contentWindow!.postMessage({ + type: "syscall-response", + id, + error: e.message, + }); + } + break; + } + case "setHeight": + // iframe.height = data.height + "px"; + iframe.style.height = `${data.height}px`; + if (widgetHeightCacheKey) { + client.setCachedWidgetHeight(widgetHeightCacheKey, data.height); + } + break; + default: + if (onMessage) { + onMessage(data); + } + } + })().catch((e) => { + console.error("Message listener error", e); + }); + }; + + // Subscribe to message event on global object (to receive messages from iframe) + globalThis.addEventListener("message", messageListener); + // Only run this code once + iframe.onload = null; + const resolvedContent = await Promise.resolve(content); + if (!iframe.contentWindow) { + console.warn("Iframe went away or content was not loaded"); + return; + } + if (resolvedContent) { + if (resolvedContent.html) { + iframe.contentWindow!.postMessage({ + type: "html", + html: resolvedContent.html, + script: resolvedContent.script, + theme: document.getElementsByTagName("html")[0].dataset.theme, + }); + } else if (resolvedContent.url) { + iframe.contentWindow!.location.href = resolvedContent.url; + if (resolvedContent.height) { + iframe.style.height = `${resolvedContent.height}px`; if (widgetHeightCacheKey) { client.setCachedWidgetHeight( - widgetHeightCacheKey, - data.height, + widgetHeightCacheKey!, + resolvedContent.height, ); } - break; - default: - if (onMessage) { - onMessage(data); - } - } - })().catch((e) => { - console.error("Message listener error", e); - }); - }; - - // Subscribe to message event on global object (to receive messages from iframe) - globalThis.addEventListener("message", messageListener); - // Only run this code once - iframe.onload = null; - const resolvedContent = await Promise.resolve(content); - if (!iframe.contentWindow) { - console.warn("Iframe went away or content was not loaded"); - return; - } - if (resolvedContent) { - if (resolvedContent.html) { - iframe.contentWindow!.postMessage({ - type: "html", - html: resolvedContent.html, - script: resolvedContent.script, - theme: document.getElementsByTagName("html")[0].dataset.theme, - }); - } else if (resolvedContent.url) { - iframe.contentWindow!.location.href = resolvedContent.url; - if (resolvedContent.height) { - iframe.style.height = `${resolvedContent.height}px`; - if (widgetHeightCacheKey) { - client.setCachedWidgetHeight( - widgetHeightCacheKey!, - resolvedContent.height, - ); + } + if (resolvedContent.width) { + iframe.width = `${resolvedContent.width}px`; } } - if (resolvedContent.width) { - iframe.width = `${resolvedContent.width}px`; - } } - } - }).catch(console.error); + }) + .catch(console.error); } export function createWidgetSandboxIFrame( diff --git a/client/config.ts b/client/config.ts index 45ff826a..62ac218c 100644 --- a/client/config.ts +++ b/client/config.ts @@ -134,10 +134,7 @@ export class Config { } } - insert( - key: string | string[], - value?: T, - ): void { + insert(key: string | string[], value?: T): void { if (typeof key === "string") { key = key.split("."); } diff --git a/client/data/data_augmenter.test.ts b/client/data/data_augmenter.test.ts index dadb0fff..e6507568 100644 --- a/client/data/data_augmenter.test.ts +++ b/client/data/data_augmenter.test.ts @@ -15,13 +15,16 @@ test("Test data augmentation", async () => { name: "Mary", age: 5678, }; - await ds.batchSet([{ - key: ["john"], - value: john, - }, { - key: ["mary"], - value: mary, - }]); + await ds.batchSet([ + { + key: ["john"], + value: john, + }, + { + key: ["mary"], + value: mary, + }, + ]); const augm = new Augmenter(ds, ["aug"]); // Augment only john await augm.setAugmentation("john", { augmented: true }); diff --git a/client/data/data_augmenter.ts b/client/data/data_augmenter.ts index 8cd3776b..07e386c1 100644 --- a/client/data/data_augmenter.ts +++ b/client/data/data_augmenter.ts @@ -13,16 +13,12 @@ export class Augmenter { constructor( private ds: DataStore, private augmentationNamespace: KvKey, - ) { - } + ) {} /** * Augments objects additional attributes pulled from a data source */ - async augmentObjectArray( - objects: any[], - keyField: string, - ): Promise { + async augmentObjectArray(objects: any[], keyField: string): Promise { const objectMap = new Map(); // create a lookup map based on objects for (const obj of objects) { @@ -33,11 +29,9 @@ export class Augmenter { async augmentObjectMap(objectMap: Map) { // Now augment - for await ( - const augmentation of this.ds.query({ - prefix: this.augmentationNamespace, - }) - ) { + for await (const augmentation of this.ds.query({ + prefix: this.augmentationNamespace, + })) { const obj = objectMap.get( augmentation.key[this.augmentationNamespace.length], ); diff --git a/client/data/datastore.ts b/client/data/datastore.ts index 412de79b..be41cfe2 100644 --- a/client/data/datastore.ts +++ b/client/data/datastore.ts @@ -12,10 +12,7 @@ import type { KV, KvKey } from "../../plug-api/types/datastore.ts"; * in a more user-friendly way */ export class DataStore { - constructor( - readonly kv: KvPrimitives, - ) { - } + constructor(readonly kv: KvPrimitives) {} async get(key: KvKey): Promise { return (await this.batchGet([key]))[0]; diff --git a/client/data/encrypted_kv_primitives.ts b/client/data/encrypted_kv_primitives.ts index 1d6846d8..0f4573ce 100644 --- a/client/data/encrypted_kv_primitives.ts +++ b/client/data/encrypted_kv_primitives.ts @@ -62,9 +62,11 @@ export class EncryptedKvPrimitives implements KvPrimitives { } async batchGet(keys: KvKey[]): Promise { - const encryptedKeys: KvKey[] = await Promise.all(keys.map((key) => { - return this.encryptKey(key); - })); + const encryptedKeys: KvKey[] = await Promise.all( + keys.map((key) => { + return this.encryptKey(key); + }), + ); const encryptedValues = await this.wrapped.batchGet(encryptedKeys); return Promise.all( encryptedValues.map((value) => this.decryptValue(value)), @@ -92,11 +94,9 @@ export class EncryptedKvPrimitives implements KvPrimitives { async *query({ prefix }: KvQueryOptions): AsyncIterableIterator { const encryptedResults: KV[] = []; // Collect all results first - for await ( - const entry of this.wrapped.query({ - prefix: prefix ? await this.encryptKey(prefix) : undefined, - }) - ) { + for await (const entry of this.wrapped.query({ + prefix: prefix ? await this.encryptKey(prefix) : undefined, + })) { encryptedResults.push(entry); } diff --git a/client/data/indexeddb_kv_primitives.ts b/client/data/indexeddb_kv_primitives.ts index 14fb0218..dda11f26 100644 --- a/client/data/indexeddb_kv_primitives.ts +++ b/client/data/indexeddb_kv_primitives.ts @@ -10,10 +10,7 @@ const objectStoreName = "data"; export class IndexedDBKvPrimitives implements KvPrimitives { db!: IDBPDatabase; - constructor( - private dbName: string, - ) { - } + constructor(private dbName: string) {} async init() { this.db = await openDB(this.dbName, 1, { @@ -31,7 +28,7 @@ export class IndexedDBKvPrimitives implements KvPrimitives { // Clear each object store in parallel const clearPromises = Array.from(objectStoreNames).map((storeName) => - tx.objectStore(storeName).clear() + tx.objectStore(storeName).clear(), ); // Wait for all clears to complete @@ -50,7 +47,7 @@ export class IndexedDBKvPrimitives implements KvPrimitives { const tx = this.db.transaction(objectStoreName, "readwrite"); await Promise.all([ ...entries.map(({ key, value }) => - tx.store.put(value, this.buildKey(key)) + tx.store.put(value, this.buildKey(key)), ), tx.done, ]); @@ -69,12 +66,12 @@ export class IndexedDBKvPrimitives implements KvPrimitives { async *query({ prefix }: KvQueryOptions): AsyncIterableIterator { const tx = this.db.transaction(objectStoreName, "readonly"); prefix = prefix || []; - for await ( - const entry of tx.store.iterate(IDBKeyRange.bound( + for await (const entry of tx.store.iterate( + IDBKeyRange.bound( this.buildKey([...prefix, ""]), this.buildKey([...prefix, "\uffff"]), - )) - ) { + ), + )) { yield { key: this.extractKey(entry.key), value: entry.value }; } } @@ -82,10 +79,12 @@ export class IndexedDBKvPrimitives implements KvPrimitives { countQuery({ prefix }: KvQueryOptions): Promise { const tx = this.db.transaction(objectStoreName, "readonly"); prefix = prefix || []; - return tx.store.count(IDBKeyRange.bound( - this.buildKey([...prefix, ""]), - this.buildKey([...prefix, "\uffff"]), - )); + return tx.store.count( + IDBKeyRange.bound( + this.buildKey([...prefix, ""]), + this.buildKey([...prefix, "\uffff"]), + ), + ); } close() { diff --git a/client/data/kv_primitives.test.ts b/client/data/kv_primitives.test.ts index 0c23ae29..2bcf023b 100644 --- a/client/data/kv_primitives.test.ts +++ b/client/data/kv_primitives.test.ts @@ -9,10 +9,11 @@ export async function allTests(db: KvPrimitives) { { key: ["kv", "test1"], value: "Hello1" }, { key: ["other", "random"], value: "Hello3" }, ]); - const result = await db.batchGet([["kv", "test1"], ["kv", "test2"], [ - "kv", - "test3", - ]]); + const result = await db.batchGet([ + ["kv", "test1"], + ["kv", "test2"], + ["kv", "test3"], + ]); expect(result.length).toEqual(3); expect(result[0]).toEqual("Hello1"); expect(result[1]).toEqual("Hello2"); @@ -33,11 +34,15 @@ export async function allTests(db: KvPrimitives) { expect(counter).toEqual(2); // Delete a few keys - await db.batchDelete([["kv", "test1"], ["other", "random"]]); - const result2 = await db.batchGet([["kv", "test1"], ["kv", "test2"], [ - "other", - "random", - ]]); + await db.batchDelete([ + ["kv", "test1"], + ["other", "random"], + ]); + const result2 = await db.batchGet([ + ["kv", "test1"], + ["kv", "test2"], + ["other", "random"], + ]); expect(result2.length).toEqual(3); expect(result2[0]).toEqual(undefined); expect(result2[1]).toEqual("Hello2"); diff --git a/client/data/memory_kv_primitives.test.ts b/client/data/memory_kv_primitives.test.ts index 74ebac6d..70ff247b 100644 --- a/client/data/memory_kv_primitives.test.ts +++ b/client/data/memory_kv_primitives.test.ts @@ -8,7 +8,10 @@ import { allTests } from "./kv_primitives.test.ts"; import type { KV } from "../../plug-api/types/datastore.ts"; function tempFilePath(): string { - const path = join(tmpdir(), `test-${Date.now()}-${Math.random().toString(36)}.json`); + const path = join( + tmpdir(), + `test-${Date.now()}-${Math.random().toString(36)}.json`, + ); onTestFinished(() => rm(path, { force: true })); return path; } @@ -51,7 +54,10 @@ test("MemoryKvPrimitives persists and loads data", async () => { await store2.init(); // Check if data was loaded correctly - const results = await store2.batchGet([["test", "key1"], ["test", "key2"]]); + const results = await store2.batchGet([ + ["test", "key1"], + ["test", "key2"], + ]); expect(results).toEqual(["value1", "value2"]); }); @@ -91,7 +97,10 @@ test("MemoryKvPrimitives persists delete operations", async () => { await store2.init(); // Check if delete was persisted - const results = await store2.batchGet([["test", "key1"], ["test", "key2"]]); + const results = await store2.batchGet([ + ["test", "key1"], + ["test", "key2"], + ]); expect(results).toEqual([undefined, "value2"]); }); diff --git a/client/data/memory_kv_primitives.ts b/client/data/memory_kv_primitives.ts index b3c307ed..09d38db9 100644 --- a/client/data/memory_kv_primitives.ts +++ b/client/data/memory_kv_primitives.ts @@ -16,15 +16,14 @@ export class MemoryKvPrimitives implements KvPrimitives { ) { // Set up throttled persistence if throttleMs is provided or default to 1000ms if (this.filePath) { - const throttleMs = options.throttleMs !== undefined - ? options.throttleMs - : 1000; + const throttleMs = + options.throttleMs !== undefined ? options.throttleMs : 1000; // If throttleMs is 0, persistence will happen immediately without throttling if (throttleMs > 0) { this.throttledPersist = throttle(() => { this.persistToDisk().catch((err) => - console.error(`Error persisting to disk: ${err}`) + console.error(`Error persisting to disk: ${err}`), ); }, throttleMs); } diff --git a/client/data/mq.datastore.test.ts b/client/data/mq.datastore.test.ts index 248c8e60..6f34cff2 100644 --- a/client/data/mq.datastore.test.ts +++ b/client/data/mq.datastore.test.ts @@ -16,10 +16,7 @@ test("DataStore MQ", async () => { system.addHook(eventHook); try { - const mq = new DataStoreMQ( - new DataStore(db), - eventHook, - ); + const mq = new DataStoreMQ(new DataStore(db), eventHook); let messages: MQMessage[]; @@ -88,10 +85,7 @@ test("DataStore MQ - Scale test with multiple subscribers", async () => { system.addHook(eventHook); try { - const mq = new DataStoreMQ( - new DataStore(db), - eventHook, - ); + const mq = new DataStoreMQ(new DataStore(db), eventHook); const queueName = "scale-test"; const totalMessages = 1000; @@ -206,8 +200,9 @@ test("DataStore MQ - Scale test with multiple subscribers", async () => { expect(totalProcessedAcrossSubscribers).toEqual(totalMessages); // Verify at least one subscriber processed messages (relaxed requirement since MQ may favor one worker) - const activeSubscribers = - Array.from(subscriberStats.values()).filter((count) => count > 0).length; + const activeSubscribers = Array.from(subscriberStats.values()).filter( + (count) => count > 0, + ).length; expect(activeSubscribers >= 1).toBe(true); // Stop all workers diff --git a/client/data/mq.datastore.ts b/client/data/mq.datastore.ts index 703fe9d0..5a8c5eb5 100644 --- a/client/data/mq.datastore.ts +++ b/client/data/mq.datastore.ts @@ -28,8 +28,7 @@ export class QueueWorker { readonly queue: string, readonly options: MQSubscribeOptions, private callback: (messages: MQMessage[]) => Promise | void, - ) { - } + ) {} /** * This is the main loop of the worker, whenever it exits the loop it means the worker has stopped @@ -97,14 +96,13 @@ export class DataStoreMQ { queueWaiters = new Map< string, - ({ resolve: () => void; reject: (e: any) => void })[] + { resolve: () => void; reject: (e: any) => void }[] >(); constructor( private ds: DataStore, public eventHook: EventHook, - ) { - } + ) {} /// Worker management public queueWorker( @@ -307,11 +305,11 @@ export class DataStoreMQ { } async fetchDLQMessages(): Promise { - return (await this.ds.luaQuery(dlqPrefix, {})); + return await this.ds.luaQuery(dlqPrefix, {}); } async fetchProcessingMessages(): Promise { - return (await this.ds.luaQuery(processingPrefix, {})); + return await this.ds.luaQuery(processingPrefix, {}); } async flushDLQ(): Promise { @@ -328,28 +326,22 @@ export class DataStoreMQ { */ async flushQueue(queue: string): Promise { const ids: KvKey[] = []; - for ( - const item of await this.ds.luaQuery( - [...queuedPrefix, queue], - {}, - ) - ) { + for (const item of await this.ds.luaQuery( + [...queuedPrefix, queue], + {}, + )) { ids.push([...queuedPrefix, queue, item.id]); } - for ( - const item of await this.ds.luaQuery([ - ...processingPrefix, - queue, - ], {}) - ) { + for (const item of await this.ds.luaQuery( + [...processingPrefix, queue], + {}, + )) { ids.push([...processingPrefix, queue, item.id]); } - for ( - const item of await this.ds.luaQuery([ - ...dlqPrefix, - queue, - ], {}) - ) { + for (const item of await this.ds.luaQuery( + [...dlqPrefix, queue], + {}, + )) { ids.push([...dlqPrefix, queue, item.id]); } await this.ds.batchDelete(ids); diff --git a/client/data/object_index.ts b/client/data/object_index.ts index 148bbae9..836213cf 100644 --- a/client/data/object_index.ts +++ b/client/data/object_index.ts @@ -40,7 +40,10 @@ type TagDefinition = { }; export class ObjectValidationError extends Error { - constructor(message: string, readonly object: ObjectValue) { + constructor( + message: string, + readonly object: ObjectValue, + ) { super(message); } } @@ -74,9 +77,7 @@ export class ObjectIndex { // Theoretically we could get empty queue notifications before the file:listed event has been triggered, so let's account for this if (indexStarted) { // Indexing has just finished for the first time for this client - console.info( - "Initial index complete, reloading editor state", - ); + console.info("Initial index complete, reloading editor state"); await this.markFullIndexComplete(); // Unsubscribe yourself this.eventHook.removeLocalListener( @@ -146,7 +147,7 @@ export class ObjectIndex { // If the index version is less than the desired version currentIndexVersion < desiredIndexVersion && // And the index queue is empty (meaning no indexing is ongoing) - await this.mq.isQueueEmpty("indexQueue") + (await this.mq.isQueueEmpty("indexQueue")) ) { console.info( "[index]", @@ -172,7 +173,10 @@ export class ObjectIndex { console.log("Queing", files.length, "pages to be indexed."); // Queue all file names to be indexed const startTime = Date.now(); - await this.mq.batchSend("indexQueue", files.map((file) => file.name)); + await this.mq.batchSend( + "indexQueue", + files.map((file) => file.name), + ); await this.mq.awaitEmptyQueue("indexQueue"); await this.markFullIndexComplete(); console.log("Full index completed after", Date.now() - startTime, "ms"); @@ -208,9 +212,7 @@ export class ObjectIndex { query: LuaCollectionQuery, scopedVariables?: Record, ): Promise[]> { - const sf = LuaStackFrame.createWithGlobalEnv( - globalEnv, - ); + const sf = LuaStackFrame.createWithGlobalEnv(globalEnv); let env = globalEnv; if (scopedVariables) { env = new LuaEnv(globalEnv); @@ -224,13 +226,16 @@ export class ObjectIndex { batchSet(page: string, kvs: KV[]): Promise { const finalBatch: KV[] = []; for (const { key, value } of kvs) { - finalBatch.push({ - key: [indexKey, ...key, page], - value, - }, { - key: [pageKey, page, ...key], - value: true, - }); + finalBatch.push( + { + key: [indexKey, ...key, page], + value, + }, + { + key: [pageKey, page, ...key], + value: true, + }, + ); } return this.ds.batchSet(finalBatch); } @@ -253,11 +258,9 @@ export class ObjectIndex { } // console.log("Clearing index for", file); const allKeys: KvKey[] = []; - for await ( - const { key } of this.ds.query({ - prefix: [pageKey, file], - }) - ) { + for await (const { key } of this.ds.query({ + prefix: [pageKey, file], + })) { allKeys.push(key); allKeys.push([indexKey, ...key.slice(2), file]); } @@ -269,14 +272,10 @@ export class ObjectIndex { */ public async clearIndex(): Promise { const allKeys: KvKey[] = []; - for await ( - const { key } of this.ds.query({ prefix: [indexKey] }) - ) { + for await (const { key } of this.ds.query({ prefix: [indexKey] })) { allKeys.push(key); } - for await ( - const { key } of this.ds.query({ prefix: [pageKey] }) - ) { + for await (const { key } of this.ds.query({ prefix: [pageKey] })) { allKeys.push(key); } await this.ds.batchDelete(allKeys); @@ -326,7 +325,7 @@ export class ObjectIndex { continue; } // Index as all the tag + any additional tags specified - const allTags = [obj.tag, ...obj.tags || []]; + const allTags = [obj.tag, ...(obj.tags || [])]; for (const tag of allTags) { const tagDefinition = tagDefinitions[tag]; // Validate object based on schema if required @@ -431,11 +430,7 @@ export class ObjectIndex { return kvs; } - deleteObject( - page: string, - tag: string, - ref: string, - ): Promise { + deleteObject(page: string, tag: string, ref: string): Promise { return this.batchDelete(page, [[tag, this.cleanKey(ref, page)]]); } } diff --git a/client/document_editor.ts b/client/document_editor.ts index 600b134d..cfc7127e 100644 --- a/client/document_editor.ts +++ b/client/document_editor.ts @@ -16,15 +16,13 @@ export class DocumentEditor { readonly parent: HTMLElement, readonly client: Client, readonly saveMethod: (path: string, content: Uint8Array) => void, - ) { - } + ) {} async init(extension: string) { this.extension = extension; const entry = Array.from( - this.client.clientSystem.documentEditorHook.documentEditors - .entries(), + this.client.clientSystem.documentEditorHook.documentEditors.entries(), ).find(([_, { extensions }]) => extensions.includes(this.extension)); if (!entry) { @@ -59,11 +57,7 @@ export class DocumentEditor { this.sendMessage(message); } - openFile( - data: Uint8Array, - meta: DocumentMeta, - details: Ref["details"], - ) { + openFile(data: Uint8Array, meta: DocumentMeta, details: Ref["details"]) { this.sendMessage({ type: "file-open", data: { @@ -99,10 +93,7 @@ export class DocumentEditor { private async waitForSave() { if (this.savePromise) { try { - await Promise.race([ - this.savePromise.promise, - timeout(2500), - ]); + await Promise.race([this.savePromise.promise, timeout(2500)]); } catch { this.savePromise.resolve(); this.savePromise = null; @@ -114,9 +105,11 @@ export class DocumentEditor { } } - private sendMessage( - message: { type: string; internal?: boolean; data?: any }, - ) { + private sendMessage(message: { + type: string; + internal?: boolean; + data?: any; + }) { if (!this.iframe?.contentWindow) return; message.internal ??= false; this.iframe.contentWindow.postMessage(message); @@ -187,9 +180,10 @@ export class DocumentEditor { }); } - private static createIframe( - content: DocumentEditorContent, - ): { iframe: HTMLIFrameElement; ready: Promise } { + private static createIframe(content: DocumentEditorContent): { + iframe: HTMLIFrameElement; + ready: Promise; + } { const doc = new DOMParser().parseFromString(content.html, "text/html"); // This is only for legacy support diff --git a/client/editor_ui.tsx b/client/editor_ui.tsx index 82e319fc..1f330b4e 100644 --- a/client/editor_ui.tsx +++ b/client/editor_ui.tsx @@ -43,7 +43,8 @@ export class MainUI { return; } else if ( target.className === "cm-textfield" || - target.closest(".cm-content") || target.closest(".cm-vim-panel") + target.closest(".cm-content") || + target.closest(".cm-vim-panel") ) { // In some cm element, let's back out return; @@ -76,8 +77,7 @@ export class MainUI { }); } - viewDispatch: (action: Action) => void = () => { - }; + viewDispatch: (action: Action) => void = () => {}; ViewComponent() { const [viewState, dispatch] = useReducer(reducer, initialViewState); @@ -101,9 +101,10 @@ export class MainUI { useEffect(() => { const updateTheme = () => { - const darkMode = viewState.uiOptions.darkMode === undefined - ? globalThis.matchMedia("(prefers-color-scheme: dark)").matches - : viewState.uiOptions.darkMode; + const darkMode = + viewState.uiOptions.darkMode === undefined + ? globalThis.matchMedia("(prefers-color-scheme: dark)").matches + : viewState.uiOptions.darkMode; document.documentElement.dataset.theme = darkMode ? "dark" : "light"; @@ -127,8 +128,10 @@ export class MainUI { }, [viewState.uiOptions.darkMode]); useEffect(() => { - document.documentElement.dataset.markdownSyntaxRendering = - viewState.uiOptions.markdownSyntaxRendering ? "on" : "off"; + document.documentElement.dataset.markdownSyntaxRendering = viewState + .uiOptions.markdownSyntaxRendering + ? "on" + : "off"; }, [viewState.uiOptions.markdownSyntaxRendering]); useEffect(() => { @@ -145,11 +148,13 @@ export class MainUI { extensions), - )} + extensions={ + new Set( + Array.from( + client.clientSystem.documentEditorHook.documentEditors.values(), + ).flatMap(({ extensions }) => extensions), + ) + } currentPath={client.currentPath()} mode={viewState.pageNavigatorMode} vimMode={viewState.uiOptions.vimMode} @@ -209,10 +214,9 @@ export class MainUI { if ( !isMarkdownPath(ref.path) && !Array.from( - client.clientSystem.documentEditorHook.documentEditors - .values(), + client.clientSystem.documentEditorHook.documentEditors.values(), ).some(({ extensions }) => - extensions.includes(getPathExtension(ref.path)) + extensions.includes(getPathExtension(ref.path)), ) ) { await this.promptDocumentOperation( @@ -285,9 +289,9 @@ export class MainUI { /> )} ("mobileMenuStyle", "hamburger") - .includes( - "hamburger", - )) - ? [{ - icon: featherIcons.MoreVertical, - description: "Open Menu", - class: "expander", - callback: () => { - // Make the expander button open/close the menu via toggling the CSS class "open" - document.querySelector("#sb-top .sb-actions.hamburger") - ?.classList.toggle("open"); - }, - }] - : [], + client.config + .get("mobileMenuStyle", "hamburger") + .includes("hamburger") + ? [ + { + icon: featherIcons.MoreVertical, + description: "Open Menu", + class: "expander", + callback: () => { + // Make the expander button open/close the menu via toggling the CSS class "open" + document + .querySelector("#sb-top .sb-actions.hamburger") + ?.classList.toggle("open"); + }, + }, + ] + : []), // Custom action buttons - ...actionButtons.filter(( // Filter out buttons without icons (invalid) and mobile buttons when not in mobile mode - button, - ) => - button.icon && ( - (typeof button.mobile === "undefined") || - (button.mobile === viewState.isMobile) + ...actionButtons + .filter( + ( + // Filter out buttons without icons (invalid) and mobile buttons when not in mobile mode + button, + ) => + button.icon && + (typeof button.mobile === "undefined" || + button.mobile === viewState.isMobile), ) - ) // Then ensure all buttons have a priority set (by default based on array index) .map((button, index) => ({ ...button, @@ -362,55 +370,64 @@ export class MainUI { .sort((a, b) => b.priority - a.priority) .map((button) => { const mdiIcon = (mdi as any)[kebabToCamel(button.icon)]; - let featherIcon = - (featherIcons as any)[kebabToCamel(button.icon)]; + let featherIcon = (featherIcons as any)[ + kebabToCamel(button.icon) + ]; if (!featherIcon) { featherIcon = featherIcons.HelpCircle; } return { icon: mdiIcon ? mdiIcon : featherIcon, description: button.description || "", - callback: button.run || (() => { - client.flashNotification( - "actionButton did not specify a run() callback", - "error", - ); - }), + callback: + button.run || + (() => { + client.flashNotification( + "actionButton did not specify a run() callback", + "error", + ); + }), href: "", }; }), ]} - rhs={!!viewState.panels.rhs.mode && ( -
- )} - lhs={!!viewState.panels.lhs.mode && ( -
- )} - pageNamePrefix={client.currentPageMeta()?.pageDecoration?.prefix ?? - ""} + rhs={ + !!viewState.panels.rhs.mode && ( +
+ ) + } + lhs={ + !!viewState.panels.lhs.mode && ( +
+ ) + } + pageNamePrefix={ + client.currentPageMeta()?.pageDecoration?.prefix ?? "" + } cssClass={(client.currentPageMeta()?.pageDecoration?.cssClasses ?? []) - .join(" ").replaceAll(/[^a-zA-Z0-9-_ ]/g, "")} + .join(" ") + .replaceAll(/[^a-zA-Z0-9-_ ]/g, "")} mobileMenuStyle={client.config.get( "mobileMenuStyle", "hamburger", )} />
- {(viewState.panels.lhs.mode !== undefined) && ( + {viewState.panels.lhs.mode !== undefined && ( )}
- {(viewState.panels.rhs.mode !== undefined) && ( + {viewState.panels.rhs.mode !== undefined && ( )}
- {(viewState.panels.modal.mode !== undefined) && ( + {viewState.panels.modal.mode !== undefined && (
)} - {(viewState.panels.bhs.mode !== undefined) && ( + {viewState.panels.bhs.mode !== undefined && (
@@ -438,7 +455,7 @@ export class MainUI { const option = await client.filterBox( "Modify", - options.map((x) => ({ name: x } as FilterOption)), + options.map((x) => ({ name: x }) as FilterOption), msg, ); if (!option) return; @@ -491,8 +508,7 @@ type ActionButton = { }; function kebabToCamel(str: string) { - return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()).replace( - /^./, - (g) => g.toUpperCase(), - ); + return str + .replace(/-([a-z])/g, (g) => g[1].toUpperCase()) + .replace(/^./, (g) => g.toUpperCase()); } diff --git a/client/languages.ts b/client/languages.ts index ffdbe47f..78960dce 100644 --- a/client/languages.ts +++ b/client/languages.ts @@ -5,9 +5,7 @@ import { } from "@codemirror/legacy-modes/mode/sql"; import { rust as rustLanguage } from "@codemirror/legacy-modes/mode/rust"; import { python as pythonLanguage } from "@codemirror/legacy-modes/mode/python"; -import { - protobuf as protobufLanguage, -} from "@codemirror/legacy-modes/mode/protobuf"; +import { protobuf as protobufLanguage } from "@codemirror/legacy-modes/mode/protobuf"; import { shell as shellLanguage } from "@codemirror/legacy-modes/mode/shell"; import { swift as swiftLanguage } from "@codemirror/legacy-modes/mode/swift"; import { toml as tomlLanguage } from "@codemirror/legacy-modes/mode/toml"; @@ -17,19 +15,13 @@ import { r as rLanguage } from "@codemirror/legacy-modes/mode/r"; import { htmlLanguage } from "@codemirror/lang-html"; import { go as goLanguage } from "@codemirror/legacy-modes/mode/go"; import { diff as diffLanguage } from "@codemirror/legacy-modes/mode/diff"; -import { - powerShell as powerShellLanguage, -} from "@codemirror/legacy-modes/mode/powershell"; +import { powerShell as powerShellLanguage } from "@codemirror/legacy-modes/mode/powershell"; import { perl as perlLanguage } from "@codemirror/legacy-modes/mode/perl"; import { ruby as rubyLanguage } from "@codemirror/legacy-modes/mode/ruby"; import { tcl as tclLanguage } from "@codemirror/legacy-modes/mode/tcl"; -import { - verilog as verilogLanguage, -} from "@codemirror/legacy-modes/mode/verilog"; +import { verilog as verilogLanguage } from "@codemirror/legacy-modes/mode/verilog"; import { vhdl as vhdlLanguage } from "@codemirror/legacy-modes/mode/vhdl"; -import { - dockerFile as dockerfileLanguage, -} from "@codemirror/legacy-modes/mode/dockerfile"; +import { dockerFile as dockerfileLanguage } from "@codemirror/legacy-modes/mode/dockerfile"; import { cmake as cmakeLanguage } from "@codemirror/legacy-modes/mode/cmake"; import { erlang as erlangLanguage } from "@codemirror/legacy-modes/mode/erlang"; import { @@ -55,74 +47,74 @@ import { luaLanguage } from "./space_lua/parse.ts"; const yamlStreamLanguage = StreamLanguage.define(yamlLanguage); export const builtinLanguages: Record = { - "meta": yamlStreamLanguage, - "yaml": yamlStreamLanguage, - "include": yamlStreamLanguage, + meta: yamlStreamLanguage, + yaml: yamlStreamLanguage, + include: yamlStreamLanguage, "space-config": yamlStreamLanguage, - "data": yamlStreamLanguage, - "toc": yamlStreamLanguage, - "javascript": javascriptLanguage, + data: yamlStreamLanguage, + toc: yamlStreamLanguage, + javascript: javascriptLanguage, "space-script": javascriptLanguage, - "js": javascriptLanguage, - "typescript": typescriptLanguage, - "ts": typescriptLanguage, - "sql": StreamLanguage.define(sqlLanguage), - "postgresql": StreamLanguage.define(postgresqlLanguage), - "pgsql": StreamLanguage.define(postgresqlLanguage), - "postgres": StreamLanguage.define(postgresqlLanguage), - "rust": StreamLanguage.define(rustLanguage), - "rs": StreamLanguage.define(rustLanguage), - "r": StreamLanguage.define(rLanguage), - "css": cssLanguage, + js: javascriptLanguage, + typescript: typescriptLanguage, + ts: typescriptLanguage, + sql: StreamLanguage.define(sqlLanguage), + postgresql: StreamLanguage.define(postgresqlLanguage), + pgsql: StreamLanguage.define(postgresqlLanguage), + postgres: StreamLanguage.define(postgresqlLanguage), + rust: StreamLanguage.define(rustLanguage), + rs: StreamLanguage.define(rustLanguage), + r: StreamLanguage.define(rLanguage), + css: cssLanguage, "space-style": cssLanguage, - "html": htmlLanguage, - "python": StreamLanguage.define(pythonLanguage), - "py": StreamLanguage.define(pythonLanguage), - "protobuf": StreamLanguage.define(protobufLanguage), - "proto": StreamLanguage.define(protobufLanguage), - "shell": StreamLanguage.define(shellLanguage), - "sh": StreamLanguage.define(shellLanguage), - "bash": StreamLanguage.define(shellLanguage), - "zsh": StreamLanguage.define(shellLanguage), - "fish": StreamLanguage.define(shellLanguage), - "swift": StreamLanguage.define(swiftLanguage), - "toml": StreamLanguage.define(tomlLanguage), - "json": StreamLanguage.define(jsonLanguage), - "xml": StreamLanguage.define(xmlLanguage), - "c": StreamLanguage.define(cLanguage), - "cpp": StreamLanguage.define(cppLanguage), + html: htmlLanguage, + python: StreamLanguage.define(pythonLanguage), + py: StreamLanguage.define(pythonLanguage), + protobuf: StreamLanguage.define(protobufLanguage), + proto: StreamLanguage.define(protobufLanguage), + shell: StreamLanguage.define(shellLanguage), + sh: StreamLanguage.define(shellLanguage), + bash: StreamLanguage.define(shellLanguage), + zsh: StreamLanguage.define(shellLanguage), + fish: StreamLanguage.define(shellLanguage), + swift: StreamLanguage.define(swiftLanguage), + toml: StreamLanguage.define(tomlLanguage), + json: StreamLanguage.define(jsonLanguage), + xml: StreamLanguage.define(xmlLanguage), + c: StreamLanguage.define(cLanguage), + cpp: StreamLanguage.define(cppLanguage), "c++": StreamLanguage.define(cppLanguage), - "cxx": StreamLanguage.define(cppLanguage), - "java": StreamLanguage.define(javaLanguage), - "csharp": StreamLanguage.define(csharpLanguage), - "cs": StreamLanguage.define(csharpLanguage), + cxx: StreamLanguage.define(cppLanguage), + java: StreamLanguage.define(javaLanguage), + csharp: StreamLanguage.define(csharpLanguage), + cs: StreamLanguage.define(csharpLanguage), "c#": StreamLanguage.define(csharpLanguage), - "scala": StreamLanguage.define(scalaLanguage), - "kotlin": StreamLanguage.define(kotlinLanguage), - "objc": StreamLanguage.define(objectiveCLanguage), + scala: StreamLanguage.define(scalaLanguage), + kotlin: StreamLanguage.define(kotlinLanguage), + objc: StreamLanguage.define(objectiveCLanguage), "objective-c": StreamLanguage.define(objectiveCLanguage), - "objectivec": StreamLanguage.define(objectiveCLanguage), - "objcpp": StreamLanguage.define(objectiveCppLanguage), + objectivec: StreamLanguage.define(objectiveCLanguage), + objcpp: StreamLanguage.define(objectiveCppLanguage), "objective-cpp": StreamLanguage.define(objectiveCppLanguage), - "objectivecpp": StreamLanguage.define(objectiveCppLanguage), + objectivecpp: StreamLanguage.define(objectiveCppLanguage), "objective-c++": StreamLanguage.define(objectiveCppLanguage), "objectivec++": StreamLanguage.define(objectiveCppLanguage), - "dart": StreamLanguage.define(dartLanguage), - "go": StreamLanguage.define(goLanguage), - "golang": StreamLanguage.define(goLanguage), - "diff": StreamLanguage.define(diffLanguage), - "powershell": StreamLanguage.define(powerShellLanguage), - "perl": StreamLanguage.define(perlLanguage), - "ruby": StreamLanguage.define(rubyLanguage), - "tcl": StreamLanguage.define(tclLanguage), - "verilog": StreamLanguage.define(verilogLanguage), - "vhdl": StreamLanguage.define(vhdlLanguage), - "dockerfile": StreamLanguage.define(dockerfileLanguage), - "cmake": StreamLanguage.define(cmakeLanguage), - "erlang": StreamLanguage.define(erlangLanguage), - "nix": nixLanguage, + dart: StreamLanguage.define(dartLanguage), + go: StreamLanguage.define(goLanguage), + golang: StreamLanguage.define(goLanguage), + diff: StreamLanguage.define(diffLanguage), + powershell: StreamLanguage.define(powerShellLanguage), + perl: StreamLanguage.define(perlLanguage), + ruby: StreamLanguage.define(rubyLanguage), + tcl: StreamLanguage.define(tclLanguage), + verilog: StreamLanguage.define(verilogLanguage), + vhdl: StreamLanguage.define(vhdlLanguage), + dockerfile: StreamLanguage.define(dockerfileLanguage), + cmake: StreamLanguage.define(cmakeLanguage), + erlang: StreamLanguage.define(erlangLanguage), + nix: nixLanguage, "space-lua": luaLanguage, - "lua": luaLanguage, + lua: luaLanguage, }; export function languageFor(name: string): Language | null { diff --git a/client/lib/box_proxy.ts b/client/lib/box_proxy.ts index aa67cba5..0d41d19a 100644 --- a/client/lib/box_proxy.ts +++ b/client/lib/box_proxy.ts @@ -6,8 +6,7 @@ export class BoxProxy { * Initializes a new instance of the BoxProxy class. * @param target The initial target object */ - constructor(private target: any) { - } + constructor(private target: any) {} /** * Swaps out the target object :magic: diff --git a/client/lib/fuse_search.ts b/client/lib/fuse_search.ts index 0cf1ce08..f4c58eb4 100644 --- a/client/lib/fuse_search.ts +++ b/client/lib/fuse_search.ts @@ -38,22 +38,28 @@ export const fuzzySearchAndSort = ( }); const fuse = new Fuse(enrichedArr, { - keys: [{ - name: "name", - weight: 2, - }, { - name: "baseName", - weight: 3, - }, { - name: "displayName", - weight: 2, - }, { - name: "aliases", - weight: 2, - }, { - name: "description", - weight: 1, - }], + keys: [ + { + name: "name", + weight: 2, + }, + { + name: "baseName", + weight: 3, + }, + { + name: "displayName", + weight: 2, + }, + { + name: "aliases", + weight: 2, + }, + { + name: "description", + weight: 1, + }, + ], isCaseSensitive: false, ignoreDiacritics: true, shouldSort: true, diff --git a/client/lib/logger.test.ts b/client/lib/logger.test.ts index 15e94479..e14c1601 100644 --- a/client/lib/logger.test.ts +++ b/client/lib/logger.test.ts @@ -154,7 +154,8 @@ test("Logger handles complex objects", () => { expect(capturedLogs.length).toEqual(2); // First log should handle complex object properly - expect(capturedLogs[0].message).toEqual(`Complex object: {"name":"test","nested":{"value":42},"array":[1,2,3]}`, + expect(capturedLogs[0].message).toEqual( + `Complex object: {"name":"test","nested":{"value":42},"array":[1,2,3]}`, ); // Second log should handle circular reference gracefully diff --git a/client/lib/logger.ts b/client/lib/logger.ts index 3cf5ae31..1c1bbb83 100644 --- a/client/lib/logger.ts +++ b/client/lib/logger.ts @@ -58,17 +58,19 @@ export class Logger { const entry: LogEntry = { level, timestamp: Date.now(), - message: args.map((arg) => { - if (typeof arg === "string") { - return arg; - } - try { - return JSON.stringify(arg); - } catch { - // Handle circular references or other JSON.stringify failures - return String(arg); - } - }).join(" "), + message: args + .map((arg) => { + if (typeof arg === "string") { + return arg; + } + try { + return JSON.stringify(arg); + } catch { + // Handle circular references or other JSON.stringify failures + return String(arg); + } + }) + .join(" "), }; this.logBuffer.push(entry); @@ -109,7 +111,7 @@ export class Logger { } // Global logger instance -let globalLogger: Logger | undefined ; +let globalLogger: Logger | undefined; export function initLogger(prefix: string = ""): Logger { globalLogger = new Logger(prefix); diff --git a/client/lib/polyfills.ts b/client/lib/polyfills.ts index 9650dccf..086004d8 100644 --- a/client/lib/polyfills.ts +++ b/client/lib/polyfills.ts @@ -1,10 +1,11 @@ // Source: https://github.com/ungap/with-resolvers/blob/main/index.js // @ts-expect-error This is fine -Promise.withResolvers || (Promise.withResolvers = function withResolvers() { - let a, b; - const c = new this((resolve, reject) => { - a = resolve; - b = reject; +Promise.withResolvers || + (Promise.withResolvers = function withResolvers() { + let a, b; + const c = new this((resolve, reject) => { + a = resolve; + b = reject; + }); + return { resolve: a, reject: b, promise: c }; }); - return { resolve: a, reject: b, promise: c }; -}); diff --git a/client/lib/url_prefix.test.ts b/client/lib/url_prefix.test.ts index 9110b5ca..1ddbe2df 100644 --- a/client/lib/url_prefix.test.ts +++ b/client/lib/url_prefix.test.ts @@ -3,28 +3,28 @@ import { applyUrlPrefix, removeUrlPrefix } from "./url_prefix.ts"; test("url_prefix - removeUrlPrefix - with value", async () => { // Absolute URL, present, should be removed - expect( - removeUrlPrefix("http://myserver/sb/relevant", "/sb"), - ).toEqual("http://myserver/relevant"); - expect( - removeUrlPrefix("https://myserver/sb/relevant", "/sb"), - ).toEqual("https://myserver/relevant"); + expect(removeUrlPrefix("http://myserver/sb/relevant", "/sb")).toEqual( + "http://myserver/relevant", + ); + expect(removeUrlPrefix("https://myserver/sb/relevant", "/sb")).toEqual( + "https://myserver/relevant", + ); // Absolute URL, present, should only remove leading - expect( - removeUrlPrefix("http://myserver/sb/sb/relevant/sb", "/sb"), - ).toEqual("http://myserver/sb/relevant/sb"); - expect( - removeUrlPrefix("http://myserver/relevant/sb", "/sb"), - ).toEqual("http://myserver/relevant/sb"); + expect(removeUrlPrefix("http://myserver/sb/sb/relevant/sb", "/sb")).toEqual( + "http://myserver/sb/relevant/sb", + ); + expect(removeUrlPrefix("http://myserver/relevant/sb", "/sb")).toEqual( + "http://myserver/relevant/sb", + ); // Absolute URL, absent, should be untouched - expect( - removeUrlPrefix("http://myserver/other/relevant", "/sb"), - ).toEqual("http://myserver/other/relevant"); - expect( - removeUrlPrefix("https://myserver/other/relevant", "/sb"), - ).toEqual("https://myserver/other/relevant"); + expect(removeUrlPrefix("http://myserver/other/relevant", "/sb")).toEqual( + "http://myserver/other/relevant", + ); + expect(removeUrlPrefix("https://myserver/other/relevant", "/sb")).toEqual( + "https://myserver/other/relevant", + ); // Absolute URL, queryString, should be preserved expect( @@ -32,26 +32,26 @@ test("url_prefix - removeUrlPrefix - with value", async () => { ).toEqual("http://myserver/sb/relevant/sb?param=arg"); // Absolute URL, unsupported, should be untouched - expect( - removeUrlPrefix("ftp://myserver/sb/relevant", "/sb"), - ).toEqual("ftp://myserver/sb/relevant"); + expect(removeUrlPrefix("ftp://myserver/sb/relevant", "/sb")).toEqual( + "ftp://myserver/sb/relevant", + ); // Host-Relative URL, present, should be removed expect(removeUrlPrefix("/sb/relevant", "/sb")).toEqual("/relevant"); // Host-Relative URL, present, should only remove leading - expect( - removeUrlPrefix("/sb/sb/relevant/sb", "/sb"), - ).toEqual("/sb/relevant/sb"); + expect(removeUrlPrefix("/sb/sb/relevant/sb", "/sb")).toEqual( + "/sb/relevant/sb", + ); expect(removeUrlPrefix("/relevant/sb", "/sb")).toEqual("/relevant/sb"); // Host-Relative URL, queryString, should be preserved - expect( - removeUrlPrefix("/sb/sb/relevant/sb?param=arg", "/sb"), - ).toEqual("/sb/relevant/sb?param=arg"); - expect( - removeUrlPrefix("/relevant/sb?param=arg", "/sb"), - ).toEqual("/relevant/sb?param=arg"); + expect(removeUrlPrefix("/sb/sb/relevant/sb?param=arg", "/sb")).toEqual( + "/sb/relevant/sb?param=arg", + ); + expect(removeUrlPrefix("/relevant/sb?param=arg", "/sb")).toEqual( + "/relevant/sb?param=arg", + ); // Host-Relative URL, absent, should be untouched expect(removeUrlPrefix("/other/relevant", "/sb")).toEqual("/other/relevant"); @@ -62,12 +62,12 @@ test("url_prefix - removeUrlPrefix - with value", async () => { test("url_prefix - removeUrlPrefix - no value", async () => { // Absolute URL, should be untouched - expect( - removeUrlPrefix("http://myserver/sb/relevant", ""), - ).toEqual("http://myserver/sb/relevant"); - expect( - removeUrlPrefix("https://myserver/sb/relevant"), - ).toEqual("https://myserver/sb/relevant"); + expect(removeUrlPrefix("http://myserver/sb/relevant", "")).toEqual( + "http://myserver/sb/relevant", + ); + expect(removeUrlPrefix("https://myserver/sb/relevant")).toEqual( + "https://myserver/sb/relevant", + ); // Host-Relative URL, should be untouched expect(removeUrlPrefix("/sb/relevant", "")).toEqual("/sb/relevant"); @@ -80,17 +80,17 @@ test("url_prefix - removeUrlPrefix - no value", async () => { test("url_prefix - applyUrlPrefix - with value", async () => { // string, Absolute URL, should be prefixed - expect( - applyUrlPrefix("http://myserver/relevant", "/sb"), - ).toEqual("http://myserver/sb/relevant"); - expect( - applyUrlPrefix("https://myserver/relevant", "/sb"), - ).toEqual("https://myserver/sb/relevant"); + expect(applyUrlPrefix("http://myserver/relevant", "/sb")).toEqual( + "http://myserver/sb/relevant", + ); + expect(applyUrlPrefix("https://myserver/relevant", "/sb")).toEqual( + "https://myserver/sb/relevant", + ); // string, Absolute URL, should not care about dups - expect( - applyUrlPrefix("http://myserver/sb/relevant/sb", "/sb"), - ).toEqual("http://myserver/sb/sb/relevant/sb"); + expect(applyUrlPrefix("http://myserver/sb/relevant/sb", "/sb")).toEqual( + "http://myserver/sb/sb/relevant/sb", + ); // string, Absolute URL, queryString should be preserved expect( @@ -98,30 +98,30 @@ test("url_prefix - applyUrlPrefix - with value", async () => { ).toEqual("http://myserver/sb/sb/relevant/sb?param=arg"); // string, Absolute URL, unsupported, should be untouched - expect( - applyUrlPrefix("ftp://myserver/relevant", "/sb"), - ).toEqual("ftp://myserver/relevant"); + expect(applyUrlPrefix("ftp://myserver/relevant", "/sb")).toEqual( + "ftp://myserver/relevant", + ); // string, Host-Relative URL, should be prefixed expect(applyUrlPrefix("/relevant", "/sb")).toEqual("/sb/relevant"); // string, Host-Relative URL, should not care about dups - expect( - applyUrlPrefix("/sb/relevant/sb", "/sb"), - ).toEqual("/sb/sb/relevant/sb"); + expect(applyUrlPrefix("/sb/relevant/sb", "/sb")).toEqual( + "/sb/sb/relevant/sb", + ); // string, Host-Relative URL, queryString should be preserved - expect( - applyUrlPrefix("/sb/relevant/sb?param=arg", "/sb"), - ).toEqual("/sb/sb/relevant/sb?param=arg"); + expect(applyUrlPrefix("/sb/relevant/sb?param=arg", "/sb")).toEqual( + "/sb/sb/relevant/sb?param=arg", + ); // string, Page-Relative URL, should be untouched expect(applyUrlPrefix("relevant", "/sb")).toEqual("relevant"); // URL object, Absolute URL, should be prefixed - expect( - applyUrlPrefix(new URL("http://myserver/relevant"), "/sb"), - ).toEqual(new URL("http://myserver/sb/relevant")); + expect(applyUrlPrefix(new URL("http://myserver/relevant"), "/sb")).toEqual( + new URL("http://myserver/sb/relevant"), + ); // URL object, Absolute URL, queryString should be preserved expect( @@ -131,12 +131,12 @@ test("url_prefix - applyUrlPrefix - with value", async () => { test("url_prefix - applyUrlPrefix - no value", async () => { // Absolute URL, should be untouched - expect( - applyUrlPrefix("http://myserver/relevant", ""), - ).toEqual("http://myserver/relevant"); - expect( - applyUrlPrefix("https://myserver/relevant"), - ).toEqual("https://myserver/relevant"); + expect(applyUrlPrefix("http://myserver/relevant", "")).toEqual( + "http://myserver/relevant", + ); + expect(applyUrlPrefix("https://myserver/relevant")).toEqual( + "https://myserver/relevant", + ); // Host-Relative URL, should be untouched expect(applyUrlPrefix("/relevant", "")).toEqual("/relevant"); diff --git a/client/lib/url_prefix.ts b/client/lib/url_prefix.ts index 8f24e233..e420fc32 100644 --- a/client/lib/url_prefix.ts +++ b/client/lib/url_prefix.ts @@ -4,9 +4,7 @@ export function removeUrlPrefix(url: string, prefix?: string): string { if (url.startsWith("http://") || url.startsWith("https://")) { const parsedUrl = new URL(url); if (parsedUrl.pathname.startsWith(prefix)) { - parsedUrl.pathname = parsedUrl.pathname.substring( - prefix.length, - ); + parsedUrl.pathname = parsedUrl.pathname.substring(prefix.length); return parsedUrl.href; } else { return url; @@ -18,7 +16,7 @@ export function removeUrlPrefix(url: string, prefix?: string): string { } } -export function applyUrlPrefix( +export function applyUrlPrefix( url: T, prefix?: string, ): T { diff --git a/client/markdown_parser/constants.ts b/client/markdown_parser/constants.ts index 69c85d5a..47ac69a5 100644 --- a/client/markdown_parser/constants.ts +++ b/client/markdown_parser/constants.ts @@ -9,5 +9,5 @@ export const frontmatterQuotesRegex = /["'].*["']/g; export const frontmatterUrlRegex = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s"']+)/g; export const frontmatterWikiLinkRegex = /(?!?\[\[)(?.*?)(?:\|(?.*?))?(?\]\])/g; -export const frontmatterMailtoRegex = /(mailto:[^@\s]+@[^@\s"']+)/ig; +export const frontmatterMailtoRegex = /(mailto:[^@\s]+@[^@\s"']+)/gi; export const pWikiLinkRegex = new RegExp(`^${wikiLinkRegex.source}`); // Modified regex used only in parser diff --git a/client/markdown_parser/extended_task.ts b/client/markdown_parser/extended_task.ts index ed39a26d..b9748b51 100644 --- a/client/markdown_parser/extended_task.ts +++ b/client/markdown_parser/extended_task.ts @@ -11,8 +11,7 @@ import { TaskStateTag } from "./customtags.ts"; // Taken from https://github.com/lezer-parser/markdown/blob/main/src/extension.ts and adapted class MultiStatusTaskParser implements LeafBlockParser { - constructor(private status: string) { - } + constructor(private status: string) {} nextLine() { return false; @@ -46,16 +45,17 @@ export const TaskList: MarkdownConfig = { { name: "TaskMark", style: t.atom }, { name: "TaskState", style: TaskStateTag }, ], - parseBlock: [{ - name: "TaskList", - leaf(cx, leaf) { - // Note: task states cannot contain : to avoid ambiguity with attribute syntax - const match = /^\[([^\]:]+)\][ \t]/.exec(leaf.content); - return match && - cx.parentType().name === "ListItem" - ? new MultiStatusTaskParser(match[1]) - : null; + parseBlock: [ + { + name: "TaskList", + leaf(cx, leaf) { + // Note: task states cannot contain : to avoid ambiguity with attribute syntax + const match = /^\[([^\]:]+)\][ \t]/.exec(leaf.content); + return match && cx.parentType().name === "ListItem" + ? new MultiStatusTaskParser(match[1]) + : null; + }, + after: "SetextHeading", }, - after: "SetextHeading", - }], + ], }; diff --git a/client/markdown_parser/parser.test.ts b/client/markdown_parser/parser.test.ts index 568e4524..9a168830 100644 --- a/client/markdown_parser/parser.test.ts +++ b/client/markdown_parser/parser.test.ts @@ -148,7 +148,8 @@ test("Test hashtag helper functions", () => { expect(extractHashtag("#name")).toEqual("name"); expect(extractHashtag("#123-content")).toEqual("123-content"); expect(extractHashtag("#")).toEqual("escaped tag"); - expect(extractHashtag("#")).toEqual("allow < and # inside", + expect(extractHashtag("#")).toEqual( + "allow < and # inside", ); expect(renderHashtag("simple")).toEqual("#simple"); diff --git a/client/markdown_parser/parser.ts b/client/markdown_parser/parser.ts index c3220e93..5e4de8f4 100644 --- a/client/markdown_parser/parser.ts +++ b/client/markdown_parser/parser.ts @@ -90,18 +90,14 @@ const LuaDirectives: MarkdownConfig = { name: "LuaDirective", parse(cx, next, pos) { const textFromPos = cx.slice(pos, cx.end); - if ( - next !== 36 /* '$' */ || - cx.slice(pos, pos + 2) !== "${" - ) { + if (next !== 36 /* '$' */ || cx.slice(pos, pos + 2) !== "${") { return -1; } let bracketNestingDepth = 0; let valueLength = 0; // We need to ensure balanced { and } pairs - loopLabel: - for (; valueLength < textFromPos.length; valueLength++) { + loopLabel: for (; valueLength < textFromPos.length; valueLength++) { switch (textFromPos[valueLength]) { case "{": bracketNestingDepth++; @@ -134,12 +130,9 @@ const LuaDirectives: MarkdownConfig = { if (!node) { return -1; } - const bodyEl = cx.elt( - "LuaExpressionDirective", - pos + 2, - endPos - 1, - [cx.elt(node.toTree()!, pos + 2 + whiteSpaceOffset)], - ); + const bodyEl = cx.elt("LuaExpressionDirective", pos + 2, endPos - 1, [ + cx.elt(node.toTree()!, pos + 2 + whiteSpaceOffset), + ]); return cx.addElement( cx.elt("LuaDirective", pos, endPos, [ @@ -205,8 +198,7 @@ export const Attribute: MarkdownConfig = { const [fullMatch, attributeName, attributeColon] = match; let bracketNestingDepth = 1; let valueLength = fullMatch.length; - loopLabel: - for (; valueLength < textFromPos.length; valueLength++) { + loopLabel: for (; valueLength < textFromPos.length; valueLength++) { switch (textFromPos[valueLength]) { case "[": bracketNestingDepth++; @@ -285,13 +277,11 @@ function regexParser({ }; } -const NakedURL = regexParser( - { - firstCharCode: 104, // h - regex: new RegExp(`^${nakedUrlRegex.source}`), - nodeType: "NakedURL", - }, -); +const NakedURL = regexParser({ + firstCharCode: 104, // h + regex: new RegExp(`^${nakedUrlRegex.source}`), + nodeType: "NakedURL", +}); const Hashtag = regexParser({ firstCharCode: 35, // # @@ -309,57 +299,61 @@ export const FrontMatter: MarkdownConfig = { { name: "FrontMatterMarker" }, { name: "FrontMatterCode" }, ], - parseBlock: [{ - name: "FrontMatter", - parse: (cx, line: Line) => { - if (cx.parsedPos !== 0) { - return false; - } - if (line.text !== "---") { - return false; - } - const frontStart = cx.parsedPos; - const elts = [ - cx.elt( - "FrontMatterMarker", - cx.parsedPos, - cx.parsedPos + line.text.length + 1, - ), - ]; - cx.nextLine(); - const startPos = cx.parsedPos; - let endPos = startPos; - let text = ""; - let lastPos = cx.parsedPos; - do { - text += `${line.text}\n`; - endPos += line.text.length + 1; - cx.nextLine(); - if (cx.parsedPos === lastPos) { - // End of file, no progress made, there may be a better way to do this but :shrug: + parseBlock: [ + { + name: "FrontMatter", + parse: (cx, line: Line) => { + if (cx.parsedPos !== 0) { return false; } - lastPos = cx.parsedPos; - } while (line.text !== "---"); - const yamlTree = yamlLang.parser.parse(text); + if (line.text !== "---") { + return false; + } + const frontStart = cx.parsedPos; + const elts = [ + cx.elt( + "FrontMatterMarker", + cx.parsedPos, + cx.parsedPos + line.text.length + 1, + ), + ]; + cx.nextLine(); + const startPos = cx.parsedPos; + let endPos = startPos; + let text = ""; + let lastPos = cx.parsedPos; + do { + text += `${line.text}\n`; + endPos += line.text.length + 1; + cx.nextLine(); + if (cx.parsedPos === lastPos) { + // End of file, no progress made, there may be a better way to do this but :shrug: + return false; + } + lastPos = cx.parsedPos; + } while (line.text !== "---"); + const yamlTree = yamlLang.parser.parse(text); - elts.push( - cx.elt("FrontMatterCode", startPos, endPos, [ - cx.elt(yamlTree, startPos), - ]), - ); - endPos = cx.parsedPos + line.text.length; - elts.push(cx.elt( - "FrontMatterMarker", - cx.parsedPos, - cx.parsedPos + line.text.length, - )); - cx.nextLine(); - cx.addElement(cx.elt("FrontMatter", frontStart, endPos, elts)); - return true; + elts.push( + cx.elt("FrontMatterCode", startPos, endPos, [ + cx.elt(yamlTree, startPos), + ]), + ); + endPos = cx.parsedPos + line.text.length; + elts.push( + cx.elt( + "FrontMatterMarker", + cx.parsedPos, + cx.parsedPos + line.text.length, + ), + ); + cx.nextLine(); + cx.addElement(cx.elt("FrontMatter", frontStart, endPos, elts)); + return true; + }, + before: "HorizontalRule", }, - before: "HorizontalRule", - }], + ], }; export const extendedMarkdownLanguage = markdown({ diff --git a/client/markdown_parser/table_parser.ts b/client/markdown_parser/table_parser.ts index 99d33ae1..a8f93908 100644 --- a/client/markdown_parser/table_parser.ts +++ b/client/markdown_parser/table_parser.ts @@ -21,7 +21,11 @@ function parseRow( elts?: Element[], offset = 0, ) { - let count = 0, first = true, cellStart = -1, cellEnd = -1, esc = false; + let count = 0, + first = true, + cellStart = -1, + cellEnd = -1, + esc = false; const parseCell = () => { elts!.push( cx.elt( @@ -67,7 +71,7 @@ function parseRow( elts.push(cx.elt("TableDelimiter", i + offset, i + offset + 1)); } cellStart = cellEnd = -1; - } else if (esc || next !== 32 && next !== 9) { + } else if (esc || (next !== 32 && next !== 9)) { if (cellStart < 0) cellStart = i; cellEnd = i + 1; } @@ -98,12 +102,15 @@ class TableParser implements LeafBlockParser { rows: false | null | Element[] = null; nextLine(cx: BlockContext, line: Line, leaf: LeafBlock) { - if (this.rows == null) { // Second line + if (this.rows == null) { + // Second line this.rows = false; let lineText; if ( - (line.next === 45 || line.next === 58 || line.next === 124 /* '-:|' */) && - delimiterLine.test(lineText = line.text.slice(line.pos)) + (line.next === 45 || + line.next === 58 || + line.next === 124) /* '-:|' */ && + delimiterLine.test((lineText = line.text.slice(line.pos))) ) { const firstRow: Element[] = [], firstCount = parseRow(cx, leaf.content, 0, firstRow, leaf.start); @@ -123,7 +130,8 @@ class TableParser implements LeafBlockParser { ]; } } - } else if (this.rows) { // Line after the second + } else if (this.rows) { + // Line after the second const content: Element[] = []; parseRow(cx, line.text, line.pos, content, cx.lineStart); this.rows.push( @@ -170,22 +178,27 @@ export const Table: MarkdownConfig = { { name: "TableCell", style: t.content }, { name: "TableDelimiter", style: t.processingInstruction }, ], - parseBlock: [{ - name: "Table", - leaf(_, leaf) { - return hasPipe(leaf.content, 0) ? new TableParser() : null; + parseBlock: [ + { + name: "Table", + leaf(_, leaf) { + return hasPipe(leaf.content, 0) ? new TableParser() : null; + }, + endLeaf(cx, line, leaf) { + if ( + leaf.parsers.some((p) => p instanceof TableParser) || + !hasPipe(line.text, line.basePos) + ) + return false; + // @ts-expect-error: internal + const next = cx.scanLine(cx.absoluteLineEnd + 1).text; + return ( + delimiterLine.test(next) && + parseRow(cx, line.text, line.basePos) === + parseRow(cx, next, line.basePos) + ); + }, + before: "SetextHeading", }, - endLeaf(cx, line, leaf) { - if ( - leaf.parsers.some((p) => p instanceof TableParser) || - !hasPipe(line.text, line.basePos) - ) return false; - // @ts-expect-error: internal - const next = cx.scanLine(cx.absoluteLineEnd + 1).text; - return delimiterLine.test(next) && - parseRow(cx, line.text, line.basePos) === - parseRow(cx, next, line.basePos); - }, - before: "SetextHeading", - }], + ], }; diff --git a/client/markdown_renderer/html_render.test.ts b/client/markdown_renderer/html_render.test.ts index a254ec7b..ef4ff3b0 100644 --- a/client/markdown_renderer/html_render.test.ts +++ b/client/markdown_renderer/html_render.test.ts @@ -2,19 +2,25 @@ import { expect, test } from "vitest"; import { renderHtml } from "./html_render.ts"; test("HTML Render", () => { - expect(renderHtml({ - name: "b", - body: "hello", - })).toEqual(`hello`); - expect(renderHtml({ - name: "a", - attrs: { - href: "https://example.com", - }, - body: "hello", - })).toEqual(`hello`); - expect(renderHtml({ - name: "span", - body: "<>", - })).toEqual(`<>`); + expect( + renderHtml({ + name: "b", + body: "hello", + }), + ).toEqual(`hello`); + expect( + renderHtml({ + name: "a", + attrs: { + href: "https://example.com", + }, + body: "hello", + }), + ).toEqual(`hello`); + expect( + renderHtml({ + name: "span", + body: "<>", + }), + ).toEqual(`<>`); }); diff --git a/client/markdown_renderer/html_render.ts b/client/markdown_renderer/html_render.ts index ad00af46..293d133a 100644 --- a/client/markdown_renderer/html_render.ts +++ b/client/markdown_renderer/html_render.ts @@ -1,17 +1,20 @@ export const Fragment = "FRAGMENT"; -export type Tag = { - name: string; - attrs?: Record; - body: Tag[] | string; -} | string; +export type Tag = + | { + name: string; + attrs?: Record; + body: Tag[] | string; + } + | string; export function htmlEscape(s: string): string { if (typeof s !== "string") { return s; } - s = s.replace(/&/g, "&") + s = s + .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) @@ -34,14 +37,14 @@ export function renderHtml(t: Tag | null): string { } const attrs = t.attrs ? ` ${Object.entries(t.attrs) - .filter(([, value]) => value !== undefined) - .map(([k, v]) => `${k}="${htmlEscape(v!)}"`).join( - " ", - )}` + .filter(([, value]) => value !== undefined) + .map(([k, v]) => `${k}="${htmlEscape(v!)}"`) + .join(" ")}` : ""; - const body = typeof t.body === "string" - ? htmlEscape(t.body) - : t.body.map(renderHtml).join(""); + const body = + typeof t.body === "string" + ? htmlEscape(t.body) + : t.body.map(renderHtml).join(""); if (t.name === Fragment) { return body; } diff --git a/client/markdown_renderer/inline.ts b/client/markdown_renderer/inline.ts index 9dde45c7..3f9555f6 100644 --- a/client/markdown_renderer/inline.ts +++ b/client/markdown_renderer/inline.ts @@ -89,11 +89,13 @@ export async function expandMarkdown( return parseMarkdown(`**Error:** ${e.message}`); } } else if ( - n.type === "LuaDirective" && options.expandLuaDirectives !== false + n.type === "LuaDirective" && + options.expandLuaDirectives !== false ) { - const expr = findNodeOfType(n, "LuaExpressionDirective") as - | LuaExpression - | null; + const expr = findNodeOfType( + n, + "LuaExpressionDirective", + ) as LuaExpression | null; if (!expr) { return; } @@ -123,25 +125,30 @@ export async function expandMarkdown( // Add a task reference to this based on the current page name if there's not one already const existingLink = findNodeOfType(n, "WikiLink"); if (!existingLink) { - n.children!.splice(1, 0, { - "text": " ", - }, { - "type": "WikiLink", - "children": [ - { - "type": "WikiLinkMark", - "children": [{ "text": "[[" }], - }, - { - "type": "WikiLinkPage", - "children": [{ "text": `${pageName}@${n.parent!.from!}` }], - }, - { - "type": "WikiLinkMark", - "children": [{ "text": "]]" }], - }, - ], - }); + n.children!.splice( + 1, + 0, + { + text: " ", + }, + { + type: "WikiLink", + children: [ + { + type: "WikiLinkMark", + children: [{ text: "[[" }], + }, + { + type: "WikiLinkPage", + children: [{ text: `${pageName}@${n.parent!.from!}` }], + }, + { + type: "WikiLinkMark", + children: [{ text: "]]" }], + }, + ], + }, + ); } } }); @@ -198,7 +205,8 @@ export function inlineContentFromURL( throw Error(`Failed to determine mime type for ${transclusion.url}`); } - const style = `max-width: 100%;` + + const style = + `max-width: 100%;` + (transclusion.dimension?.width ? `width: ${transclusion.dimension.width}px;` : "") + diff --git a/client/markdown_renderer/justified_tables.ts b/client/markdown_renderer/justified_tables.ts index 724be098..65032013 100644 --- a/client/markdown_renderer/justified_tables.ts +++ b/client/markdown_renderer/justified_tables.ts @@ -16,17 +16,17 @@ export function justifiedTableRender( * @argument t ParseTree - A markdown table's `ParseTree` */ function getTableJustification(t: ParseTree): Justification[] { - const delimiter = t.children?.find((child) => - child.type === "TableDelimiter" + const delimiter = t.children?.find( + (child) => child.type === "TableDelimiter", ); const delimiterText = delimiter?.children?.at(0)?.text; if (!delimiterText) return []; // Split at "|" and filter out empty strings (from leading/trailing |) - const columnDelimiters = delimiterText.split("|").filter((part) => - part.trim() !== "" - ); + const columnDelimiters = delimiterText + .split("|") + .filter((part) => part.trim() !== ""); return columnDelimiters.map(parseColumnAlignment); } diff --git a/client/markdown_renderer/markdown_render.test.ts b/client/markdown_renderer/markdown_render.test.ts index f4005a55..8c2aca5d 100644 --- a/client/markdown_renderer/markdown_render.test.ts +++ b/client/markdown_renderer/markdown_render.test.ts @@ -82,7 +82,8 @@ test("Smart hard break test", () => { failOnUnknown: true, smartHardBreak: true, }); - expect(html).toEqual(`Hello
world!
`, + expect(html).toEqual( + `Hello
world!
`, ); const example2 = `This is going to be a text. With a new line. diff --git a/client/markdown_renderer/markdown_render.ts b/client/markdown_renderer/markdown_render.ts index 92e9cb79..7a686ce0 100644 --- a/client/markdown_renderer/markdown_render.ts +++ b/client/markdown_renderer/markdown_render.ts @@ -57,7 +57,8 @@ function preprocess(t: ParseTree) { const prevNodeIdx = node.parent!.children!.indexOf(node) - 1; const prevNodeType = node.parent!.children![prevNodeIdx]?.type; if ( - prevNodeType?.includes("Heading") || prevNodeType?.includes("Table") + prevNodeType?.includes("Heading") || + prevNodeType?.includes("Table") ) { node.text = node.text.slice(1); } @@ -90,10 +91,7 @@ function posPreservingRender( return tag; } -function render( - t: ParseTree, - options: MarkdownRenderOptions = {}, -): Tag | null { +function render(t: ParseTree, options: MarkdownRenderOptions = {}): Tag | null { if (t.type?.endsWith("Mark") || t.type?.endsWith("Delimiter")) { return null; } @@ -301,9 +299,8 @@ function render( // Custom stuff case "WikiLink": { const link = findNodeOfType(t, "WikiLinkPage")!.children![0].text!; - let linkText = options.shortWikiLinks === true - ? link.split("/").pop()! - : link; + let linkText = + options.shortWikiLinks === true ? link.split("/").pop()! : link; const aliasNode = findNodeOfType(t, "WikiLinkAlias"); if (aliasNode) { linkText = aliasNode.children![0].text!; @@ -358,7 +355,8 @@ function render( const ref = parseToRef(wikilink.children![0].text!); if ( - !externalTaskRef && ref && + !externalTaskRef && + ref && (ref.details?.type === "position" || ref.details?.type === "linecolumn") ) { @@ -370,8 +368,8 @@ function render( name: "span", attrs: externalTaskRef ? { - "data-external-task-ref": externalTaskRef, - } + "data-external-task-ref": externalTaskRef, + } : {}, body: cleanTags(mapRender(t.children!)), }; @@ -501,10 +499,7 @@ function render( } } -function traverseTag( - t: Tag, - fn: (t: Tag) => void, -) { +function traverseTag(t: Tag, fn: (t: Tag) => void) { fn(t); if (typeof t === "string") { return; @@ -542,21 +537,23 @@ export function renderMarkdownToHtml( return; } - const pageMeta = allPages.find((p) => - ref.path === parseToRef(p.ref)?.path + const pageMeta = allPages.find( + (p) => ref.path === parseToRef(p.ref)?.path, ); if ( pageMeta && - !(ref.details?.type === "position" || - ref.details?.type === "linecolumn") + !( + ref.details?.type === "position" || + ref.details?.type === "linecolumn" + ) ) { t.body = [(pageMeta.pageDecoration?.prefix ?? "") + t.body]; if (pageMeta.pageDecoration?.cssClasses) { - t.attrs!.class += " sb-decorated-object " + - pageMeta.pageDecoration.cssClasses.join(" ").replaceAll( - /[^a-zA-Z0-9-_ ]/g, - "", - ); + t.attrs!.class += + " sb-decorated-object " + + pageMeta.pageDecoration.cssClasses + .join(" ") + .replaceAll(/[^a-zA-Z0-9-_ ]/g, ""); } } } diff --git a/client/markdown_renderer/result_render.ts b/client/markdown_renderer/result_render.ts index f23e38f7..078512a6 100644 --- a/client/markdown_renderer/result_render.ts +++ b/client/markdown_renderer/result_render.ts @@ -55,7 +55,9 @@ function escapeRegularPipes(s: string) { if (s[i] === "{" && s[i + 1] === "[") { isInCommandButton = true; } else if ( - (s[i] === "]" || s[i] === ")") && s[i + 1] === "}" && isInCommandButton + (s[i] === "]" || s[i] === ")") && + s[i + 1] === "}" && + isInCommandButton ) { isInCommandButton = false; } else if (s[i] === "|" && !isInWikiLink && !isInCommandButton) { @@ -87,22 +89,8 @@ export async function jsonToMDTable( const headerList = [...headers]; const lines = []; - lines.push( - "|" + - headerList - .map( - (headerName) => headerName, - ) - .join("|") + - "|", - ); - lines.push( - "|" + - headerList - .map(() => "--") - .join("|") + - "|", - ); + lines.push("|" + headerList.map((headerName) => headerName).join("|") + "|"); + lines.push("|" + headerList.map(() => "--").join("|") + "|"); for (const val of jsonArray) { const el = []; for (const prop of headerList) { @@ -135,16 +123,18 @@ export function renderExpressionResult( return Promise.resolve(luaFormatNumber(result)); } if ( - Array.isArray(result) && result.length > 0 && typeof result[0] === "object" + Array.isArray(result) && + result.length > 0 && + typeof result[0] === "object" ) { // If result is an array of objects, render as a Markdown table try { return jsonToMDTable(result, cellTransformer); } catch (e: any) { console.error( - `Error rendering expression directive: ${e.message} for value ${ - JSON.stringify(result) - }`, + `Error rendering expression directive: ${e.message} for value ${JSON.stringify( + result, + )}`, ); return Promise.resolve(JSON.stringify(result)); } @@ -223,10 +213,7 @@ async function renderListItems( const md = await renderItemToMarkdown(item, cellTransformer, nested); if (md.includes("\n")) { const lines = md.split("\n"); - rendered.push( - "-\n" + - lines.map((l) => ` ${l}`).join("\n"), - ); + rendered.push("-\n" + lines.map((l) => ` ${l}`).join("\n")); } else { rendered.push(`- ${md}`); } @@ -275,8 +262,12 @@ function renderItemToMarkdown( } function isPlainObjectHelper(v: any): v is Record { - return typeof v === "object" && v !== null && !Array.isArray(v) && - v.constructor === Object; + return ( + typeof v === "object" && + v !== null && + !Array.isArray(v) && + v.constructor === Object + ); } // Array of record-like `LuaTables` to multi-row Markdown table diff --git a/client/navigator.ts b/client/navigator.ts index 10041102..14fe57ea 100644 --- a/client/navigator.ts +++ b/client/navigator.ts @@ -24,9 +24,7 @@ export class PathPageNavigator { openLocations = new Map(); - constructor( - private client: Client, - ) { + constructor(private client: Client) { this.indexRef = this.client.getIndexRef(); } @@ -36,10 +34,7 @@ export class PathPageNavigator { * @param replaceState whether to update the state in place (rather than to * push a new state) */ - async navigate( - ref: Ref, - replaceState = false, - ) { + async navigate(ref: Ref, replaceState = false) { // We are already navigating, let's wait if (this.navigationPromise) { await this.navigationPromise.promise; @@ -94,9 +89,8 @@ export class PathPageNavigator { } else { // This can e.g. happen on the first navigate. We obviously can't fall back to the same path, so fallback to the indexpage - const newState: LocationState = currentState.path === ref.path - ? this.indexRef - : currentState; + const newState: LocationState = + currentState.path === ref.path ? this.indexRef : currentState; globalThis.history.replaceState( newState, @@ -144,22 +138,16 @@ export class PathPageNavigator { return locationState; } - subscribe( - pageLoadCallback: ( - locationState: LocationState, - ) => Promise, - ) { + subscribe(pageLoadCallback: (locationState: LocationState) => Promise) { globalThis.addEventListener("popstate", async (event: PopStateEvent) => { const state: LocationState = event.state ? event.state - // The popstate event can be fired in a whole list of weird situations - // (see MDN). Try to do our best if it's fired without the state - : parseRefFromURI() || this.indexRef; + : // The popstate event can be fired in a whole list of weird situations + // (see MDN). Try to do our best if it's fired without the state + parseRefFromURI() || this.indexRef; // Try filling in the ref using the openLocation cache - if ( - !state.details && !state.selection && !state.scrollTop - ) { + if (!state.details && !state.selection && !state.scrollTop) { const openLocation = this.openLocations.get(state.path); if (openLocation) { state.selection = openLocation.selection; @@ -170,19 +158,20 @@ export class PathPageNavigator { // For some (propably smart) reason the reject() function on a // Promise.withResolvers, also throws. This is hugely annoying here, so // let's resolve for both cases - await pageLoadCallback(state) - .then( - () => this.navigationPromise?.resolve(null), - (e) => this.navigationPromise?.resolve(e.message), - ); + await pageLoadCallback(state).then( + () => this.navigationPromise?.resolve(null), + (e) => this.navigationPromise?.resolve(e.message), + ); }); } } export function parseRefFromURI(): Ref | null { - const locationRef = parseToRef(decodeURIComponent( - location.href.substring(document.baseURI.length), //this essentially returns location with prefix and leading slash removed (equivalent to location.pathname.substring(prefix.length).substring(1)), - )); + const locationRef = parseToRef( + decodeURIComponent( + location.href.substring(document.baseURI.length), //this essentially returns location with prefix and leading slash removed (equivalent to location.pathname.substring(prefix.length).substring(1)), + ), + ); if (locationRef && location.hash) { locationRef.details = { diff --git a/client/plugos/hooks/code_widget.ts b/client/plugos/hooks/code_widget.ts index 1ed3752e..1f094cdd 100644 --- a/client/plugos/hooks/code_widget.ts +++ b/client/plugos/hooks/code_widget.ts @@ -10,11 +10,9 @@ export class CodeWidgetHook implements Hook { collectAllCodeWidgets(system: System) { this.codeWidgetCallbacks.clear(); for (const plug of system.loadedPlugs.values()) { - for ( - const [name, functionDef] of Object.entries( - plug.manifest!.functions, - ) - ) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions, + )) { if (!functionDef.codeWidget) { continue; } diff --git a/client/plugos/hooks/command.ts b/client/plugos/hooks/command.ts index 569f3882..8a8d332e 100644 --- a/client/plugos/hooks/command.ts +++ b/client/plugos/hooks/command.ts @@ -5,8 +5,10 @@ import { throttle } from "@silverbulletmd/silverbullet/lib/async"; import type { Command, CommandHookEvents } from "../../types/command.ts"; import type { CommandHookT } from "@silverbulletmd/silverbullet/type/manifest"; -export class CommandHook extends EventEmitter - implements Hook { +export class CommandHook + extends EventEmitter + implements Hook +{ system?: System; public throttledBuildAllCommandsAndEmit = throttle(() => { this.buildAllCommandsAndEmit(); @@ -30,11 +32,9 @@ export class CommandHook extends EventEmitter return commands; } for (const plug of this.system.loadedPlugs.values()) { - for ( - const [name, functionDef] of Object.entries( - plug.manifest!.functions, - ) - ) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions, + )) { if (!functionDef.command) { continue; } @@ -46,7 +46,7 @@ export class CommandHook extends EventEmitter commands.set(cmd.name, { ...cmd, run: (args?: string[]) => { - return plug.invoke(name, [cmd, ...args ?? []]); + return plug.invoke(name, [cmd, ...(args ?? [])]); }, }); } diff --git a/client/plugos/hooks/document_editor.ts b/client/plugos/hooks/document_editor.ts index 82492fd4..28715e57 100644 --- a/client/plugos/hooks/document_editor.ts +++ b/client/plugos/hooks/document_editor.ts @@ -12,11 +12,9 @@ export class DocumentEditorHook implements Hook { collectAllDocumentEditors(system: System) { this.documentEditors.clear(); for (const plug of system.loadedPlugs.values()) { - for ( - const [name, functionDef] of Object.entries( - plug.manifest!.functions, - ) - ) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions, + )) { if (!functionDef.editor) { continue; } @@ -25,9 +23,9 @@ export class DocumentEditorHook implements Hook { ? functionDef.editor : [functionDef.editor]; - const conflict = Array.from(this.documentEditors.entries()).find(( - [_, { extensions }], - ) => keys.some((key) => extensions.includes(key))); + const conflict = Array.from(this.documentEditors.entries()).find( + ([_, { extensions }]) => keys.some((key) => extensions.includes(key)), + ); if (conflict) { console.log( @@ -37,10 +35,10 @@ export class DocumentEditorHook implements Hook { ); } - this.documentEditors.set( - name, - { extensions: keys, callback: () => plug.invoke(name, []) }, - ); + this.documentEditors.set(name, { + extensions: keys, + callback: () => plug.invoke(name, []), + }); } } } diff --git a/client/plugos/hooks/event.ts b/client/plugos/hooks/event.ts index f8290004..72fec67b 100644 --- a/client/plugos/hooks/event.ts +++ b/client/plugos/hooks/event.ts @@ -1,4 +1,3 @@ - import type { Manifest } from "../types.ts"; import type { System } from "../system.ts"; import type { EventHookI } from "../eventhook.ts"; @@ -12,8 +11,7 @@ export class EventHook implements EventHookI { private system?: System; private localListeners: Map any)[]> = new Map(); - constructor(readonly config?: Config) { - } + constructor(readonly config?: Config) {} addLocalListener(eventName: string, callback: (...args: any[]) => any) { if (!this.localListeners.has(eventName)) { @@ -74,29 +72,28 @@ export class EventHook implements EventHookI { const promises: Promise[] = []; for (const plug of this.system.loadedPlugs.values()) { const manifest = plug.manifest; - for ( - const [name, functionDef] of Object.entries( - manifest!.functions, - ) - ) { + for (const [name, functionDef] of Object.entries(manifest!.functions)) { if (functionDef.events) { for (const event of functionDef.events) { if ( - event === eventName || eventNameToRegex(event).test(eventName) + event === eventName || + eventNameToRegex(event).test(eventName) ) { // Only dispatch functions that can run in this environment if (plug.canInvoke(name)) { // Queue the promise - promises.push((async () => { - try { - return await plug.invoke(name, args); - } catch (e: any) { - console.error( - `Error dispatching event ${eventName} to ${plug.manifest.name}.${name}: ${e.message}`, - ); - throw e; - } - })()); + promises.push( + (async () => { + try { + return await plug.invoke(name, args); + } catch (e: any) { + console.error( + `Error dispatching event ${eventName} to ${plug.manifest.name}.${name}: ${e.message}`, + ); + throw e; + } + })(), + ); } } } @@ -109,9 +106,11 @@ export class EventHook implements EventHookI { if (eventNameToRegex(name).test(eventName)) { for (const localListener of localListeners) { // Queue the promise - promises.push((async () => { - return await Promise.resolve(localListener(...args)); - })()); + promises.push( + (async () => { + return await Promise.resolve(localListener(...args)); + })(), + ); } } } @@ -125,15 +124,17 @@ export class EventHook implements EventHookI { for (const [name, listeners] of Object.entries(configListeners)) { if (eventNameToRegex(name).test(eventName)) { for (const listener of listeners) { - promises.push((async () => { - return await Promise.resolve( - listener({ - name: eventName, - // Most events have a single argument, so let's optimize for that, otherwise pass all arguments as an array - data: args.length === 1 ? args[0] : args, - }), - ); - })()); + promises.push( + (async () => { + return await Promise.resolve( + listener({ + name: eventName, + // Most events have a single argument, so let's optimize for that, otherwise pass all arguments as an array + data: args.length === 1 ? args[0] : args, + }), + ); + })(), + ); } } } @@ -167,11 +168,7 @@ export class EventHook implements EventHookI { validateManifest(manifest: Manifest): string[] { const errors = []; - for ( - const [_, functionDef] of Object.entries( - manifest.functions || {}, - ) - ) { + for (const [_, functionDef] of Object.entries(manifest.functions || {})) { if (functionDef.events && !Array.isArray(functionDef.events)) { errors.push("'events' key must be an array of strings"); } diff --git a/client/plugos/hooks/mq.ts b/client/plugos/hooks/mq.ts index f27b698b..c60fc610 100644 --- a/client/plugos/hooks/mq.ts +++ b/client/plugos/hooks/mq.ts @@ -1,4 +1,3 @@ - import type { Hook, Manifest } from "../types.ts"; import type { System } from "../system.ts"; import { throttle } from "@silverbulletmd/silverbullet/lib/async"; @@ -10,13 +9,11 @@ import type { } from "@silverbulletmd/silverbullet/type/datastore"; import type { Config } from "../../config.ts"; -export type MQListenerSpec = - & MQSubscribeOptions - & { - queue: string; - autoAck?: boolean; - run: Function; - }; +export type MQListenerSpec = MQSubscribeOptions & { + queue: string; + autoAck?: boolean; + run: Function; +}; export class MQHook implements Hook { subscriptions: QueueWorker[] = []; @@ -28,8 +25,7 @@ export class MQHook implements Hook { private system: System, readonly mq: DataStoreMQ, readonly config: Config, - ) { - } + ) {} apply(system: System): void { this.system = system; @@ -57,11 +53,9 @@ export class MQHook implements Hook { if (!plug.manifest) { continue; } - for ( - const [name, functionDef] of Object.entries( - plug.manifest.functions, - ) - ) { + for (const [name, functionDef] of Object.entries( + plug.manifest.functions, + )) { if (!functionDef.mqSubscriptions) { continue; } @@ -79,7 +73,10 @@ export class MQHook implements Hook { try { await plug.invoke(name, [messages]); if (subscriptionDef.autoAck) { - await this.mq.batchAck(queue, messages.map((m) => m.id)); + await this.mq.batchAck( + queue, + messages.map((m) => m.id), + ); } } catch (e: any) { console.error( @@ -118,7 +115,10 @@ export class MQHook implements Hook { try { await listener.run(messages); if (listener.autoAck) { - await this.mq.batchAck(queue, messages.map((m) => m.id)); + await this.mq.batchAck( + queue, + messages.map((m) => m.id), + ); } } catch (e: any) { console.error( diff --git a/client/plugos/hooks/plug_namespace.ts b/client/plugos/hooks/plug_namespace.ts index a73969a1..513d0f97 100644 --- a/client/plugos/hooks/plug_namespace.ts +++ b/client/plugos/hooks/plug_namespace.ts @@ -30,11 +30,9 @@ export class PlugNamespaceHook implements Hook { this.spaceFunctions = []; for (const plug of system.loadedPlugs.values()) { if (plug.manifest?.functions) { - for ( - const [funcName, funcDef] of Object.entries( - plug.manifest.functions, - ) - ) { + for (const [funcName, funcDef] of Object.entries( + plug.manifest.functions, + )) { if (funcDef.pageNamespace) { this.spaceFunctions.push({ operation: funcDef.pageNamespace.operation, diff --git a/client/plugos/hooks/slash_command.ts b/client/plugos/hooks/slash_command.ts index 9713cdcf..ed322616 100644 --- a/client/plugos/hooks/slash_command.ts +++ b/client/plugos/hooks/slash_command.ts @@ -23,8 +23,7 @@ export class SlashCommandHook implements Hook { this.buildAllCommands(); }, 200); - constructor(private client: Client) { - } + constructor(private client: Client) {} buildAllCommands() { const clientSystem = this.client.clientSystem; @@ -32,11 +31,9 @@ export class SlashCommandHook implements Hook { this.slashCommands = []; for (const plug of system.loadedPlugs.values()) { - for ( - const [name, functionDef] of Object.entries( - plug.manifest!.functions, - ) - ) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions, + )) { if (!functionDef.slashCommand) { continue; } @@ -50,14 +47,9 @@ export class SlashCommandHook implements Hook { } } // Iterate over script defined slash commands - for ( - const command of Object.values( - this.client.config.get>( - "slashCommands", - {}, - ), - ) - ) { + for (const command of Object.values( + this.client.config.get>("slashCommands", {}), + )) { this.slashCommands.push(command); } } @@ -88,14 +80,14 @@ export class SlashCommandHook implements Hook { if ( def.onlyContexts && !def.onlyContexts.some((context) => - parentNodes.some((node) => node.startsWith(context)) + parentNodes.some((node) => node.startsWith(context)), ) ) { continue; } if ( - def.exceptContexts?.some( - (context) => parentNodes.some((node) => node.startsWith(context)), + def.exceptContexts?.some((context) => + parentNodes.some((node) => node.startsWith(context)), ) ) { continue; @@ -123,17 +115,10 @@ export class SlashCommandHook implements Hook { } const slashCompletions: CompletionResult | SlashCompletions | null = - await this.client - .completeWithEvent( - ctx, - "slash:complete", - ); + await this.client.completeWithEvent(ctx, "slash:complete"); if (slashCompletions) { - for ( - const slashCompletion of slashCompletions - .options as SlashCompletionOption[] - ) { + for (const slashCompletion of slashCompletions.options as SlashCompletionOption[]) { options.push({ label: slashCompletion.label, detail: slashCompletion.detail, diff --git a/client/plugos/hooks/syscall.ts b/client/plugos/hooks/syscall.ts index 925b7810..8975105d 100644 --- a/client/plugos/hooks/syscall.ts +++ b/client/plugos/hooks/syscall.ts @@ -17,9 +17,9 @@ export class SyscallHook implements Hook { for (const plug of system.loadedPlugs.values()) { const syscalls: SysCallMapping = {}; - for ( - const [name, functionDef] of Object.entries(plug.manifest!.functions) - ) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions, + )) { if (!functionDef.syscall) { continue; } diff --git a/client/plugos/manifest_cache.ts b/client/plugos/manifest_cache.ts index 9093c35d..d591214d 100644 --- a/client/plugos/manifest_cache.ts +++ b/client/plugos/manifest_cache.ts @@ -11,38 +11,45 @@ export interface ManifestCache { } export class KVPrimitivesManifestCache implements ManifestCache { - constructor(private kv: KvPrimitives, private manifestPrefix: string) { - } + constructor( + private kv: KvPrimitives, + private manifestPrefix: string, + ) {} async getManifest( plug: Plug, cacheKey: string, cacheHash: number, ): Promise> { - const [cached] = await this.kv.batchGet([[ - this.manifestPrefix, - cacheKey, - ]]); + const [cached] = await this.kv.batchGet([[this.manifestPrefix, cacheKey]]); if (cached && cached.hash === cacheHash) { // console.log("Using KV cached manifest for", plug.name); return cached.manifest; } await plug.sandbox.init(); const manifest = plug.sandbox.manifest!; - await this.kv.batchSet([{ - key: [this.manifestPrefix, cacheKey], - // Deliverately removing the assets from the manifest to preserve space, will be re-added upon load of actual worker - value: { manifest: { ...manifest, assets: undefined }, hash: cacheHash }, - }]); + await this.kv.batchSet([ + { + key: [this.manifestPrefix, cacheKey], + // Deliverately removing the assets from the manifest to preserve space, will be re-added upon load of actual worker + value: { + manifest: { ...manifest, assets: undefined }, + hash: cacheHash, + }, + }, + ]); return manifest; } } export class InMemoryManifestCache implements ManifestCache { - private cache = new Map; - hash: number; - }>(); + private cache = new Map< + string, + { + manifest: Manifest; + hash: number; + } + >(); async getManifest( plug: Plug, diff --git a/client/plugos/plug.ts b/client/plugos/plug.ts index 36984730..b4a40847 100644 --- a/client/plugos/plug.ts +++ b/client/plugos/plug.ts @@ -26,10 +26,7 @@ export class Plug { cacheHash: number, sandboxFactory: SandboxFactory, ): Promise> { - const plug = new Plug( - system, - sandboxFactory, - ); + const plug = new Plug(system, sandboxFactory); // Retrieve the manifest, which may either come from a cache or be loaded from the worker plug.manifest = await system.options.manifestCache!.getManifest( @@ -83,7 +80,7 @@ export class Plug { } return plug.invoke(name, args); } - if (!await this.canInvoke(name)) { + if (!(await this.canInvoke(name))) { throw new Error( `Function ${name} is not available in ${this.runtimeEnv}`, ); diff --git a/client/plugos/plug_compile.ts b/client/plugos/plug_compile.ts index 0ff707f5..56bd06bb 100644 --- a/client/plugos/plug_compile.ts +++ b/client/plugos/plug_compile.ts @@ -50,7 +50,7 @@ export async function compileManifest( // Assets const assetsBundle = await bundleAssets( path.resolve(rootPath), - manifest.assets as string[] || [], + (manifest.assets as string[]) || [], ); manifest.assets = assetsBundle.toJSON(); @@ -63,34 +63,32 @@ export async function compileManifest( import { setupMessageListener } from "worker-runtime"; // Imports -${ - Object.entries(manifest.functions).map(([funcName, def]) => { - if (!def.path) { - return ""; - } - let [filePath, jsFunctionName] = def.path.split(":"); - // Resolve path - filePath = path.join(rootPath, filePath); +${Object.entries(manifest.functions) + .map(([funcName, def]) => { + if (!def.path) { + return ""; + } + let [filePath, jsFunctionName] = def.path.split(":"); + // Resolve path + filePath = path.join(rootPath, filePath); - return `import {${jsFunctionName} as ${funcName}} from "${ - // Replacing \ with / for Windows - path.resolve(filePath).replaceAll( - "\\", - "\\\\", - )}";\n`; - }).join("") - } + return `import {${jsFunctionName} as ${funcName}} from "${ + // Replacing \ with / for Windows + path.resolve(filePath).replaceAll("\\", "\\\\") + }";\n`; + }) + .join("")} // Function mapping const functionMapping = { -${ - Object.entries(manifest.functions).map(([funcName, def]) => { - if (!def.path) { - return ""; - } - return ` ${funcName}: ${funcName},\n`; - }).join("") - } +${Object.entries(manifest.functions) + .map(([funcName, def]) => { + if (!def.path) { + return ""; + } + return ` ${funcName}: ${funcName},\n`; + }) + .join("")} }; // Manifest @@ -130,10 +128,10 @@ setupMessageListener(functionMapping, manifest, self.postMessage); let jsCode = await readFile(outFile, "utf-8"); jsCode = patchBundledJS(jsCode); await writeFile(outFile, jsCode, "utf-8"); - + // Clean up temp directory await rm(tempDir, { recursive: true, force: true }); - + console.log(`Plug ${manifest.name} written to ${outFile}.`); return outFile; } @@ -155,19 +153,17 @@ export async function compileManifests( await mkdir(dist, { recursive: true }); const startTime = Date.now(); // Build all plugs in parallel - await Promise.all(manifestFiles.map(async (plugManifestPath) => { - const manifestPath = plugManifestPath as string; - try { - await compileManifest( - manifestPath, - dist, - options, - ); - } catch (e: any) { - console.error(`Error building ${manifestPath}:`, e.message); - throw e; - } - })); + await Promise.all( + manifestFiles.map(async (plugManifestPath) => { + const manifestPath = plugManifestPath as string; + try { + await compileManifest(manifestPath, dist, options); + } catch (e: any) { + console.error(`Error building ${manifestPath}:`, e.message); + throw e; + } + }), + ); console.log(`Done building plugs in ${Date.now() - startTime}ms`); building = false; } @@ -181,21 +177,21 @@ export function patchBundledJS(code: string): string { } export async function plugCompileCommand( - { dist, debug, info }: { + { + dist, + debug, + info, + }: { dist: string; debug: boolean; info: boolean; }, ...manifestPaths: string[] ) { - await compileManifests( - manifestPaths, - dist, - { - debug: debug, - info: info, - }, - ); + await compileManifests(manifestPaths, dist, { + debug: debug, + info: info, + }); await esbuild.stop(); process.exit(0); } diff --git a/client/plugos/protocol.ts b/client/plugos/protocol.ts index aa059ff5..d95f8be9 100644 --- a/client/plugos/protocol.ts +++ b/client/plugos/protocol.ts @@ -3,38 +3,38 @@ import type { Manifest } from "@silverbulletmd/silverbullet/type/manifest"; // Messages received from the worker export type ControllerMessage = | { - // Parsed manifest when worker is initialized - type: "manifest"; - manifest: Manifest; - } + // Parsed manifest when worker is initialized + type: "manifest"; + manifest: Manifest; + } | { - // Function invocation result - type: "invr"; - id: number; - error?: string; - result?: any; - } + // Function invocation result + type: "invr"; + id: number; + error?: string; + result?: any; + } | { - // Syscall - type: "sys"; - id: number; - name: string; - args: any[]; - }; + // Syscall + type: "sys"; + id: number; + name: string; + args: any[]; + }; // Messages received inside the worker export type WorkerMessage = | { - // Function invocation - type: "inv"; - id: number; - name: string; - args: any[]; - } + // Function invocation + type: "inv"; + id: number; + name: string; + args: any[]; + } | { - // Syscall result - type: "sysr"; - id: number; - result?: any; - error?: any; - }; + // Syscall result + type: "sysr"; + id: number; + result?: any; + error?: any; + }; diff --git a/client/plugos/proxy_fetch.ts b/client/plugos/proxy_fetch.ts index a88adfef..52ba76b6 100644 --- a/client/plugos/proxy_fetch.ts +++ b/client/plugos/proxy_fetch.ts @@ -35,12 +35,13 @@ export async function performLocalFetch( ): Promise { const result = await fetch( url, - req && { - method: req.method, - headers: req.headers, - body: req.base64Body && base64Decode(req.base64Body), - // Casting to any due to TypeScript fetch type limitations - } as any, + req && + ({ + method: req.method, + headers: req.headers, + body: req.base64Body && base64Decode(req.base64Body), + // Casting to any due to TypeScript fetch type limitations + } as any), ); return { ok: result.ok, diff --git a/client/plugos/sandboxes/worker_sandbox.ts b/client/plugos/sandboxes/worker_sandbox.ts index f15cf3af..493ee6bd 100644 --- a/client/plugos/sandboxes/worker_sandbox.ts +++ b/client/plugos/sandboxes/worker_sandbox.ts @@ -22,8 +22,7 @@ export class WorkerSandbox implements Sandbox { readonly plug: Plug, public workerUrl: URL, private workerOptions = {}, - ) { - } + ) {} /** * Should only invoked lazily (either by invoke, or by a ManifestCache to load the manifest) @@ -43,7 +42,7 @@ export class WorkerSandbox implements Sandbox { return race([ // We're adding a timeout of 5s here to handle the case where a plug blows up during initialization timeout(5000).catch((_) => - Promise.reject(new Error("Plug timed out during creation")) + Promise.reject(new Error("Plug timed out during creation")), ), new Promise((resolve) => { this.worker!.onmessage = (ev) => { @@ -54,7 +53,7 @@ export class WorkerSandbox implements Sandbox { // Set assets in the plug this.plug.assets = new AssetBundle( - this.manifest?.assets ? this.manifest.assets as AssetJson : {}, + this.manifest?.assets ? (this.manifest.assets as AssetJson) : {}, ); return resolve(); @@ -76,18 +75,20 @@ export class WorkerSandbox implements Sandbox { try { const result = await this.plug.syscall(data.name!, data.args!); - this.worker && this.worker!.postMessage({ - type: "sysr", - id: data.id, - result: result, - } as WorkerMessage); + this.worker && + this.worker!.postMessage({ + type: "sysr", + id: data.id, + result: result, + } as WorkerMessage); } catch (e: any) { // console.error("Syscall fail", e); - this.worker && this.worker!.postMessage({ - type: "sysr", - id: data.id, - error: e.message, - } as WorkerMessage); + this.worker && + this.worker!.postMessage({ + type: "sysr", + id: data.id, + error: e.message, + } as WorkerMessage); } break; case "invr": { diff --git a/client/plugos/syscalls/asset.ts b/client/plugos/syscalls/asset.ts index 6208c856..86941265 100644 --- a/client/plugos/syscalls/asset.ts +++ b/client/plugos/syscalls/asset.ts @@ -4,9 +4,7 @@ import type { FileMeta } from "../../../plug-api/types/index.ts"; export default function assetSyscalls(system: System): SysCallMapping { return { "asset.readAsset": (_ctx, plugName: string, name: string): string => { - return system.loadedPlugs.get(plugName)!.assets!.readFileAsDataUrl( - name, - ); + return system.loadedPlugs.get(plugName)!.assets!.readFileAsDataUrl(name); }, "asset.listFiles": (_ctx, plugName: string): FileMeta[] => { const assets = system.loadedPlugs.get(plugName)!.assets!; diff --git a/client/plugos/syscalls/code_widget.ts b/client/plugos/syscalls/code_widget.ts index 7ccd3fca..aa3cefe6 100644 --- a/client/plugos/syscalls/code_widget.ts +++ b/client/plugos/syscalls/code_widget.ts @@ -12,9 +12,7 @@ export function codeWidgetSyscalls( body: string, pageName: string, ): Promise => { - const langCallback = codeWidgetHook.codeWidgetCallbacks.get( - lang, - ); + const langCallback = codeWidgetHook.codeWidgetCallbacks.get(lang); if (!langCallback) { throw new Error(`Code widget ${lang} not found`); } diff --git a/client/plugos/syscalls/config.ts b/client/plugos/syscalls/config.ts index f55737da..67b22e35 100644 --- a/client/plugos/syscalls/config.ts +++ b/client/plugos/syscalls/config.ts @@ -23,11 +23,7 @@ export function configSyscalls(config: Config): SysCallMapping { keyOrValues = await luaValueToJS(keyOrValues, LuaStackFrame.lostFrame); config.set(keyOrValues as any, value); }, - "config.insert": ( - _ctx, - key: string | string[], - value: any, - ) => { + "config.insert": (_ctx, key: string | string[], value: any) => { config.insert(key, value); }, "config.has": (_ctx, path: string) => { diff --git a/client/plugos/syscalls/datastore.ts b/client/plugos/syscalls/datastore.ts index 2b6eca39..fac8cef9 100644 --- a/client/plugos/syscalls/datastore.ts +++ b/client/plugos/syscalls/datastore.ts @@ -61,7 +61,7 @@ export function dataStoreReadSyscalls( } } return (await queryLua(ds.kv, prefix, query, env, sf)).map((item) => - luaValueToJS(item, sf) + luaValueToJS(item, sf), ); }, }; diff --git a/client/plugos/syscalls/editor.ts b/client/plugos/syscalls/editor.ts index acbecde9..d5f7520d 100644 --- a/client/plugos/syscalls/editor.ts +++ b/client/plugos/syscalls/editor.ts @@ -51,8 +51,8 @@ export function editorSyscalls(client: Client): SysCallMapping { return client.documentEditor?.name || "page"; }, "editor.getRecentlyOpenedPages": (): PageMeta[] => { - return client.ui.viewState.allPages.sort((a, b) => - (b.lastOpened || 0) - (a.lastOpened || 0) + return client.ui.viewState.allPages.sort( + (a, b) => (b.lastOpened || 0) - (a.lastOpened || 0), ); }, "editor.getText": () => { @@ -68,7 +68,9 @@ export function editorSyscalls(client: Client): SysCallMapping { const line = client.editorView.state.doc.lineAt(pos); return { ...line, - textWithCursor: line.text.slice(0, pos - line.from) + "|^|" + + textWithCursor: + line.text.slice(0, pos - line.from) + + "|^|" + line.text.slice(pos - line.from), }; }, @@ -127,7 +129,7 @@ export function editorSyscalls(client: Client): SysCallMapping { legacyRef.kind ??= "page"; - let details: Ref["details"] ; + let details: Ref["details"]; if (typeof legacyRef.pos === "number") { details = { @@ -160,13 +162,9 @@ export function editorSyscalls(client: Client): SysCallMapping { // some validation library. Didn't want to use jsonschemas here tho. // Ideally this would be moved into a function too if (!isValidPath(ref.path) && ref.path !== "") { - throw new Error( - "Path passed in ref to `editor.navigate` is invalid", - ); + throw new Error("Path passed in ref to `editor.navigate` is invalid"); } else if (typeof ref.meta !== "boolean" && ref.meta !== undefined) { - throw new Error( - "ref.meta has to be of type `boolean`", - ); + throw new Error("ref.meta has to be of type `boolean`"); } else if (ref.details !== undefined && typeof ref.details !== "object") { throw new Error( "ref.details has to be of type `object` or `undefined`", @@ -181,17 +179,15 @@ export function editorSyscalls(client: Client): SysCallMapping { } if ( - ref.details?.type === "position" && typeof ref.details.pos !== "number" + ref.details?.type === "position" && + typeof ref.details.pos !== "number" ) { - throw new Error( - "ref.details.pos has to be of type `number`", - ); + throw new Error("ref.details.pos has to be of type `number`"); } else if ( - ref.details?.type === "header" && typeof ref.details.header !== "string" + ref.details?.type === "header" && + typeof ref.details.header !== "string" ) { - throw new Error( - "ref.details.header has to be of type `string`", - ); + throw new Error("ref.details.header has to be of type `string`"); } else if ( ref.details?.type === "linecolumn" && typeof ref.details.line !== "number" && @@ -214,10 +210,7 @@ export function editorSyscalls(client: Client): SysCallMapping { client.rebuildEditorState(); }, "editor.reloadConfigAndCommands": async () => { - await client.clientSystem.system.localSyscall( - "system.loadScripts", - [], - ); + await client.clientSystem.system.localSyscall("system.loadScripts", []); await client.clientSystem.system.localSyscall( "system.loadSpaceStyles", [], @@ -351,7 +344,8 @@ export function editorSyscalls(client: Client): SysCallMapping { if (cursorPlaceHolder) { cursorPlaceholderPos = text.indexOf("|^|"); if (cursorPlaceholderPos !== -1) { - text = text.slice(0, cursorPlaceholderPos) + + text = + text.slice(0, cursorPlaceholderPos) + text.slice(cursorPlaceholderPos + 3); } else { cursorPlaceHolder = false; @@ -369,9 +363,7 @@ export function editorSyscalls(client: Client): SysCallMapping { selection: { anchor: cursorPos, }, - effects: [ - EditorView.scrollIntoView(cursorPos), - ], + effects: [EditorView.scrollIntoView(cursorPos)], }); } }, @@ -385,7 +377,8 @@ export function editorSyscalls(client: Client): SysCallMapping { let cursorPlaceholderPos = -1; if (cursorPlaceHolder) { cursorPlaceholderPos = text.indexOf("|^|"); - text = text.slice(0, cursorPlaceholderPos) + + text = + text.slice(0, cursorPlaceholderPos) + text.slice(cursorPlaceholderPos + 3); } client.editorView.dispatch({ @@ -401,9 +394,7 @@ export function editorSyscalls(client: Client): SysCallMapping { selection: { anchor: cursorPos, }, - effects: [ - EditorView.scrollIntoView(cursorPos), - ], + effects: [EditorView.scrollIntoView(cursorPos)], }); } }, @@ -416,12 +407,9 @@ export function editorSyscalls(client: Client): SysCallMapping { if (center) { client.editorView.dispatch({ effects: [ - EditorView.scrollIntoView( - pos, - { - y: "center", - }, - ), + EditorView.scrollIntoView(pos, { + y: "center", + }), ], }); } @@ -459,7 +447,8 @@ export function editorSyscalls(client: Client): SysCallMapping { const from = editorView.state.selection.main.from; const cursorPlaceholderPos = text.indexOf("|^|"); if (cursorPlaceHolder && cursorPlaceholderPos !== -1) { - text = text.slice(0, cursorPlaceholderPos) + + text = + text.slice(0, cursorPlaceholderPos) + text.slice(cursorPlaceholderPos + 3); } else { cursorPlaceHolder = false; @@ -566,15 +555,11 @@ export function editorSyscalls(client: Client): SysCallMapping { } }); config.map?.forEach(({ map, to, mode }) => { - console.log( - `Mapping ${map} to ${to} for ${mode ?? "normal"}`, - ); + console.log(`Mapping ${map} to ${to} for ${mode ?? "normal"}`); Vim.map(map, to, mode ?? "normal"); }); config.noremap?.forEach(({ map, to, mode }) => { - console.log( - `Noremapping ${map} to ${to} for ${mode ?? "normal"}`, - ); + console.log(`Noremapping ${map} to ${to} for ${mode ?? "normal"}`); Vim.noremap(map, to, mode ?? "normal"); }); config.commands?.forEach(({ ex, command }) => { diff --git a/client/plugos/syscalls/event.ts b/client/plugos/syscalls/event.ts index 9330ceda..f066eb06 100644 --- a/client/plugos/syscalls/event.ts +++ b/client/plugos/syscalls/event.ts @@ -18,19 +18,16 @@ export function eventSyscalls( /** * Define a Lua event listener */ - "event.listen": ( - _ctx, - def: EventSubscription, - ) => { + "event.listen": (_ctx, def: EventSubscription) => { // console.log("Registering Lua event listener: ", def.name); - client.config.insert([ - "eventListeners", - def.name, - ], async (...args: any[]) => { - // Convert return value to JS - const val = await def.run(...args); - return luaValueToJS(val, LuaStackFrame.lostFrame); - }); + client.config.insert( + ["eventListeners", def.name], + async (...args: any[]) => { + // Convert return value to JS + const val = await def.run(...args); + return luaValueToJS(val, LuaStackFrame.lostFrame); + }, + ); }, }; } diff --git a/client/plugos/syscalls/fetch.ts b/client/plugos/syscalls/fetch.ts index c1d9a8b7..65629029 100644 --- a/client/plugos/syscalls/fetch.ts +++ b/client/plugos/syscalls/fetch.ts @@ -12,9 +12,7 @@ import { } from "@silverbulletmd/silverbullet/lib/crypto"; import { fsEndpoint } from "../../spaces/constants.ts"; -export function sandboxFetchSyscalls( - client: Client, -): SysCallMapping { +export function sandboxFetchSyscalls(client: Client): SysCallMapping { return { // For use in Lua "http.request": async ( @@ -25,17 +23,18 @@ export function sandboxFetchSyscalls( console.warn("Deprecated: use net.proxyFetch() instead"); // JSONify any non-serializable body if ( - options?.body && typeof options.body !== "string" && + options?.body && + typeof options.body !== "string" && !(options.body instanceof Uint8Array) ) { options.body = JSON.stringify(options.body); } const fetchOptions = options ? { - method: options.method, - headers: {} as Record, - body: options.body, - } + method: options.method, + headers: {} as Record, + body: options.body, + } : {}; fetchOptions.headers = buildProxyHeaders(options?.headers); @@ -46,7 +45,8 @@ export function sandboxFetchSyscalls( ); // Do sensible things with the body based on the content type let body: any; - const contentTypeHeader = options.responseEncoding || + const contentTypeHeader = + options.responseEncoding || resp.headers.get("x-proxy-header-content-type"); const statusCode = +(resp.headers.get("x-proxy-status-code") || "200"); if (contentTypeHeader?.startsWith("application/json")) { @@ -74,10 +74,10 @@ export function sandboxFetchSyscalls( // console.log("Got sandbox fetch ", url, op); const fetchOptions = options ? { - method: options.method, - headers: options.headers, - body: options.base64Body && base64Decode(options.base64Body), - } + method: options.method, + headers: options.headers, + body: options.base64Body && base64Decode(options.base64Body), + } : {}; fetchOptions.headers = buildProxyHeaders(options?.headers); const resp = await client.httpSpacePrimitives.authenticatedFetch( @@ -100,8 +100,11 @@ export function sandboxFetchSyscalls( function buildProxyUrl(client: Client, url: string) { url = url.replace(/^https?:\/\//, ""); // Strip off the /.fs and replace with /.proxy - return client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) + - "/.proxy/" + url; + return ( + client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) + + "/.proxy/" + + url + ); } function buildProxyHeaders(headers?: Record): Record { @@ -115,9 +118,7 @@ function buildProxyHeaders(headers?: Record): Record { return newHeaders; } -function extractProxyHeaders( - headers: Headers, -): Record { +function extractProxyHeaders(headers: Headers): Record { const newHeaders: Record = {}; for (const [key, value] of headers.entries()) { if (key.toLowerCase().startsWith("x-proxy-header-")) { diff --git a/client/plugos/syscalls/jsonschema.ts b/client/plugos/syscalls/jsonschema.ts index ab205970..636312b2 100644 --- a/client/plugos/syscalls/jsonschema.ts +++ b/client/plugos/syscalls/jsonschema.ts @@ -59,10 +59,7 @@ export function jsonschemaSyscalls(): SysCallMapping { ): undefined | string => { return validateObject(schema, object); }, - "jsonschema.validateSchema": ( - _ctx, - schema: any, - ): undefined | string => { + "jsonschema.validateSchema": (_ctx, schema: any): undefined | string => { return validateSchema(schema); }, }; diff --git a/client/plugos/syscalls/markdown.ts b/client/plugos/syscalls/markdown.ts index 1de83641..b9543870 100644 --- a/client/plugos/syscalls/markdown.ts +++ b/client/plugos/syscalls/markdown.ts @@ -61,8 +61,9 @@ export function markdownSyscalls(client: Client): SysCallMapping { "markdown.objectsToTable": ( _ctx, data: any[], - options: { renderCell?: (val: any, key: string) => Promise | any } = - {}, + options: { + renderCell?: (val: any, key: string) => Promise | any; + } = {}, ) => { return jsonToMDTable(data, options.renderCell || refCellTransformer); }, diff --git a/client/plugos/syscalls/mq.ts b/client/plugos/syscalls/mq.ts index a6ae2cef..fd4876cd 100644 --- a/client/plugos/syscalls/mq.ts +++ b/client/plugos/syscalls/mq.ts @@ -6,23 +6,15 @@ export type EventSubscription = MQListenerSpec & { run: (...args: any[]) => Promise; }; -export function mqSyscalls( - mq: DataStoreMQ, -): SysCallMapping { +export function mqSyscalls(mq: DataStoreMQ): SysCallMapping { return { /** * Define a Lua event listener */ - "mq.subscribe": ( - _ctx, - def: MQListenerSpec, - ) => { + "mq.subscribe": (_ctx, def: MQListenerSpec) => { def.autoAck = def.autoAck !== false; // console.log("Registering Lua event listener: ", def.name); - client.config.insert([ - "mqSubscriptions", - def.queue, - ], def); + client.config.insert(["mqSubscriptions", def.queue], def); }, "mq.send": (_ctx, queue: string, body: any) => { return mq.send(queue, body); diff --git a/client/plugos/syscalls/service_registry.ts b/client/plugos/syscalls/service_registry.ts index b92b5d0a..08323709 100644 --- a/client/plugos/syscalls/service_registry.ts +++ b/client/plugos/syscalls/service_registry.ts @@ -12,10 +12,7 @@ export function serviceRegistrySyscalls( /** * Define a Lua event listener */ - "service.define": ( - _ctx, - def: ServiceSpec, - ) => { + "service.define": (_ctx, def: ServiceSpec) => { return serviceRegistry.define(def); }, "service.discover": ( diff --git a/client/plugos/syscalls/shell.ts b/client/plugos/syscalls/shell.ts index f9841a25..9e61cdc6 100644 --- a/client/plugos/syscalls/shell.ts +++ b/client/plugos/syscalls/shell.ts @@ -2,9 +2,7 @@ import type { SysCallMapping } from "../system.ts"; import type { Client } from "../../client.ts"; import { fsEndpoint } from "../../spaces/constants.ts"; -export function shellSyscalls( - client: Client, -): SysCallMapping { +export function shellSyscalls(client: Client): SysCallMapping { return { "shell.run": async ( _ctx, @@ -34,6 +32,7 @@ export function shellSyscalls( function buildShellUrl(client: Client) { // Strip off the /.fs and replace with /.shell - return client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) + - "/.shell"; + return ( + client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) + "/.shell" + ); } diff --git a/client/plugos/syscalls/system.ts b/client/plugos/syscalls/system.ts index 186f9f20..3093e7ef 100644 --- a/client/plugos/syscalls/system.ts +++ b/client/plugos/syscalls/system.ts @@ -66,9 +66,8 @@ export function systemSyscalls( }, "system.listSyscalls": (): SyscallMeta[] => { const syscalls: SyscallMeta[] = []; - for ( - const [name, info] of client.clientSystem.system.registeredSyscalls - ) { + for (const [name, info] of client.clientSystem.system + .registeredSyscalls) { syscalls.push({ name, requiredPermissions: info.requiredPermissions, diff --git a/client/plugos/system.ts b/client/plugos/system.ts index ba98fb02..3bac546c 100644 --- a/client/plugos/system.ts +++ b/client/plugos/system.ts @@ -24,9 +24,7 @@ export type SyscallContext = { plug?: string; }; -type SyscallSignature = ( - ...args: any[] -) => Promise | any; +type SyscallSignature = (...args: any[]) => Promise | any; type Syscall = { requiredPermissions: string[]; @@ -112,11 +110,7 @@ export class System extends EventEmitter> { return this.syscall({}, name, args); } - syscall( - ctx: SyscallContext, - name: string, - args: any[], - ): Promise { + syscall(ctx: SyscallContext, name: string, args: any[]): Promise { const syscall = this.registeredSyscalls.get(name); if (!syscall) { throw Error(`Unregistered syscall ${name}`); diff --git a/client/plugos/worker_runtime.ts b/client/plugos/worker_runtime.ts index b44c72a8..d099c725 100644 --- a/client/plugos/worker_runtime.ts +++ b/client/plugos/worker_runtime.ts @@ -19,7 +19,8 @@ let workerPostMessage = (_msg: ControllerMessage): void => { // - in a browser's main thread, typeof window is "object" // - in a browser's worker threads, typeof window === "undefined" // - in Cloudflare workers typeof window === "undefined", but typeof globalThis.WebSocketPair is defined -const runningAsWebWorker = typeof window === "undefined" && +const runningAsWebWorker = + typeof window === "undefined" && // @ts-expect-error: globalThis typeof globalThis.WebSocketPair === "undefined"; @@ -147,8 +148,8 @@ export function monkeyPatchFetch() { ): Promise => { const encodedBody = init?.body ? base64Encode( - new Uint8Array(await (new Response(init.body)).arrayBuffer()), - ) + new Uint8Array(await new Response(init.body).arrayBuffer()), + ) : undefined; const r = await sandboxFetch( reqInfo, diff --git a/client/reducer.ts b/client/reducer.ts index 0217cf30..1a778584 100644 --- a/client/reducer.ts +++ b/client/reducer.ts @@ -49,7 +49,7 @@ export default function reducer( state.allPages = state.allPages.map((pageMeta) => pageMeta.name === action.meta.name ? { ...action.meta, lastOpened: Date.now() } - : pageMeta + : pageMeta, ); // Can't update page meta if not on a page if (!state.current || !isMarkdownPath(state.current.path)) { @@ -177,8 +177,7 @@ export default function reducer( showCommandPalette: false, showPageNavigator: false, showFilterBox: false, - filterBoxOnSelect: () => { - }, + filterBoxOnSelect: () => {}, filterBoxPlaceHolder: "", filterBoxOptions: [], filterBoxHelpText: "", diff --git a/client/service_registry.test.ts b/client/service_registry.test.ts index d9157224..3b612d89 100644 --- a/client/service_registry.test.ts +++ b/client/service_registry.test.ts @@ -66,7 +66,11 @@ test("Test services", async () => { expect(matchesMulti[1].priority).toEqual(1); expect(await registry.invoke(matches[0], "Pete")).toEqual("Hello Pete!"); - expect(await registry.invokeBestMatch("greeter", "Pete")).toEqual("Hello Pete!"); + expect(await registry.invokeBestMatch("greeter", "Pete")).toEqual( + "Hello Pete!", + ); - expect(await registry.invokeBestMatch("greeter-multi", "Pete")).toEqual("Hello 2 Pete!"); + expect(await registry.invokeBestMatch("greeter-multi", "Pete")).toEqual( + "Hello 2 Pete!", + ); }); diff --git a/client/service_registry.ts b/client/service_registry.ts index 21a33601..373e5466 100644 --- a/client/service_registry.ts +++ b/client/service_registry.ts @@ -15,26 +15,29 @@ export type ServiceSpec = { }; export class ServiceRegistry { - constructor(private eventHook: EventHook, private config: Config) { - } + constructor( + private eventHook: EventHook, + private config: Config, + ) {} public define(spec: ServiceSpec): void { const id = globalThis.crypto.randomUUID(); // Register with discover:* event - this.config.insert([ - "eventListeners", - `discover:${spec.selector}`, - ], async (e: any) => { - const matchResult = typeof spec.match === "function" - ? await spec.match(e.data) - : spec.match; - if (matchResult) { - return { - ...matchResult, - id, - }; - } - }); + this.config.insert( + ["eventListeners", `discover:${spec.selector}`], + async (e: any) => { + const matchResult = + typeof spec.match === "function" + ? await spec.match(e.data) + : spec.match; + if (matchResult) { + return { + ...matchResult, + id, + }; + } + }, + ); // Register callback when invoked this.config.insert(["eventListeners", `service:${id}`], (e: any) => { return spec.run(e.data); diff --git a/client/service_worker.ts b/client/service_worker.ts index b48b9f28..678b013b 100644 --- a/client/service_worker.ts +++ b/client/service_worker.ts @@ -39,27 +39,25 @@ const basePathName = location.pathname.substring( location.pathname.length - workerFilename.length - 1, ); -const precacheFiles = Object.fromEntries([ - "/", - "/.client/client.js", - "/.client/favicon.png", - "/.client/iAWriterMonoS-Bold.woff2", - "/.client/iAWriterMonoS-BoldItalic.woff2", - "/.client/iAWriterMonoS-Italic.woff2", - "/.client/iAWriterMonoS-Regular.woff2", - "/.client/logo.png", - "/.client/logo-dock.png", - "/.client/main.css", - "/.client/manifest.json", -].map((path) => [path, `${baseURI}${path}?v=${CACHE_NAME}`, path])); // Cache busting +const precacheFiles = Object.fromEntries( + [ + "/", + "/.client/client.js", + "/.client/favicon.png", + "/.client/iAWriterMonoS-Bold.woff2", + "/.client/iAWriterMonoS-BoldItalic.woff2", + "/.client/iAWriterMonoS-Italic.woff2", + "/.client/iAWriterMonoS-Regular.woff2", + "/.client/logo.png", + "/.client/logo-dock.png", + "/.client/main.css", + "/.client/manifest.json", + ].map((path) => [path, `${baseURI}${path}?v=${CACHE_NAME}`, path]), +); // Cache busting // Initially set to undefined, resulting in all "fetch" being proxied. // Once the service worker is configured, this will be set and the proxy will handle fetches. -const proxyRouter = new ProxyRouter( - basePathName, - baseURI, - precacheFiles, -); +const proxyRouter = new ProxyRouter(basePathName, baseURI, precacheFiles); // Configuration mutex let configuring = false; @@ -123,9 +121,7 @@ self.addEventListener("message", async (event: any) => { } case "perform-file-sync": { if (proxyRouter.syncEngine) { - await proxyRouter.syncEngine.syncSingleFile( - message.path, - ); + await proxyRouter.syncEngine.syncSingleFile(message.path); } else { console.warn( "Ignoring perform-file-sync request, proxy not configured yet", @@ -147,8 +143,9 @@ self.addEventListener("message", async (event: any) => { case "get-encryption-key": { event.source.postMessage({ type: "encryption-key", - key: encryptionKeyMemoryStore && - await exportKey(encryptionKeyMemoryStore), + key: + encryptionKeyMemoryStore && + (await exportKey(encryptionKeyMemoryStore)), } as ServiceWorkerSourceMessage); break; } @@ -320,19 +317,21 @@ function broadcastMessage(message: ServiceWorkerSourceMessage) { // @ts-expect-error: service worker API const clients: any = self.clients; // Find all windows attached to this service worker - clients.matchAll({ - type: "window", - }).then((clients: any[]) => { - clients.forEach((client) => { - client.postMessage(message); + clients + .matchAll({ + type: "window", + }) + .then((clients: any[]) => { + clients.forEach((client) => { + client.postMessage(message); + }); + if (clients.length === 0) { + console.info( + "No clients are listening for messages, dropping message", + message, + ); + } }); - if (clients.length === 0) { - console.info( - "No clients are listening for messages, dropping message", - message, - ); - } - }); } const throttledServiceWorkerStarted = throttleImmediately(() => { @@ -356,14 +355,9 @@ self.addEventListener("install", (event: any) => { event.waitUntil( (async () => { const cache = await caches.open(CACHE_NAME); - console.log( - "Now pre-caching client files", - ); + console.log("Now pre-caching client files"); await cache.addAll(Object.values(precacheFiles)); - console.log( - Object.keys(precacheFiles).length, - "client files cached", - ); + console.log(Object.keys(precacheFiles).length, "client files cached"); // @ts-expect-error: Force the waiting service worker to become the active service worker await self.skipWaiting(); })(), diff --git a/client/service_worker/proxy_router.ts b/client/service_worker/proxy_router.ts index a95d9ebf..b53f573a 100644 --- a/client/service_worker/proxy_router.ts +++ b/client/service_worker/proxy_router.ts @@ -132,7 +132,8 @@ export class ProxyRouter extends EventEmitter { if ( // Not yet configured -> Proxy - !this.localSpacePrimitives || !this.syncEngine || + !this.localSpacePrimitives || + !this.syncEngine || // Not fully synced but online -> Proxy (!this.fullSyncConfirmed && this.online) || // A path we always need to proxy -> Proxy @@ -168,8 +169,9 @@ export class ProxyRouter extends EventEmitter { return this.handleRequest(pathname, request); } else { // Fallback to the app shell for all other requests (SPA) - return (await caches.match(this.precacheFiles["/"])) || - fetch(request); + return ( + (await caches.match(this.precacheFiles["/"])) || fetch(request) + ); } } catch (e: any) { console.warn("Fetch failed for", request.url, "error:", e.message); @@ -182,16 +184,15 @@ export class ProxyRouter extends EventEmitter { ); } - handleRequest( - pathname: string, - request: Request, - ): Promise { + handleRequest(pathname: string, request: Request): Promise { const path = decodePageURI(pathname.slice(fsEndpoint.length + 1)); switch (request.method) { case "GET": { - if (!path) { // .fs GET + if (!path) { + // .fs GET return this.handleFileListing(); - } else { // .fs/* GET + } else { + // .fs/* GET return this.handleGet(path, request); } } @@ -222,25 +223,19 @@ export class ProxyRouter extends EventEmitter { const files = await this.localSpacePrimitives.fetchFileList(); // Now augment this with non-synced file metadata - for ( - const nonSyncedFile of this.nonSyncedFiles - .values() - ) { - const existingFile = files.find((file) => - file.name === nonSyncedFile.name + for (const nonSyncedFile of this.nonSyncedFiles.values()) { + const existingFile = files.find( + (file) => file.name === nonSyncedFile.name, ); if (!existingFile) { files.push(nonSyncedFile); } } - return new Response( - JSON.stringify(files), - { - headers: { - "Content-Type": "application/json", - }, + return new Response(JSON.stringify(files), { + headers: { + "Content-Type": "application/json", }, - ); + }); } async handleGet(path: string, request: Request): Promise { diff --git a/client/service_worker/sync_engine.ts b/client/service_worker/sync_engine.ts index a691595d..12f30fa3 100644 --- a/client/service_worker/sync_engine.ts +++ b/client/service_worker/sync_engine.ts @@ -100,10 +100,7 @@ export class SyncEngine extends EventEmitter { this.syncAccepts = config.syncIgnore ? gitIgnoreCompiler(config.syncIgnore).accepts : () => true; - console.log( - "[sync] Updated sync config:", - this.syncConfig, - ); + console.log("[sync] Updated sync config:", this.syncConfig); } isSyncCandidate(path: string): boolean { @@ -161,10 +158,12 @@ export class SyncEngine extends EventEmitter { * @param snapshot A map of sync status items. */ saveSnapshot(snapshot: SyncSnapshot) { - return this.kv.batchSet([{ - key: syncSnapshotKey, - value: snapshot.toJSON(), - }]); + return this.kv.batchSet([ + { + key: syncSnapshotKey, + value: snapshot.toJSON(), + }, + ]); } async wipe() { @@ -205,20 +204,11 @@ export class SyncEngine extends EventEmitter { "will pick the version from secondary and be done with it.", ); // Read file from secondary - const { data, meta } = await secondary.readFile( - name, - ); + const { data, meta } = await secondary.readFile(name); // Write file to primary - const newMeta = await primary.writeFile( - name, - data, - meta, - ); + const newMeta = await primary.writeFile(name, data, meta); // Update snapshot - snapshot.files.set(name, [ - newMeta.lastModified, - meta.lastModified, - ]); + snapshot.files.set(name, [newMeta.lastModified, meta.lastModified]); return 1; } diff --git a/client/service_worker/util.ts b/client/service_worker/util.ts index e1d7ec4e..88e42a9d 100644 --- a/client/service_worker/util.ts +++ b/client/service_worker/util.ts @@ -1,7 +1,6 @@ export async function unregisterServiceWorkers() { if (navigator.serviceWorker) { - const registrations = await navigator.serviceWorker - .getRegistrations(); + const registrations = await navigator.serviceWorker.getRegistrations(); for (const registration of registrations) { await registration.unregister(); console.log("Service worker unregistered"); diff --git a/client/space.test.ts b/client/space.test.ts index 671edbf9..1ad624ac 100644 --- a/client/space.test.ts +++ b/client/space.test.ts @@ -46,32 +46,32 @@ test("readRef checks", async () => { // Reference to a header expect(await space.readRef(parseToRef("test#Header 1")!)).toEqual({ - text: "# Header 1\nSome text\n\n", - offset: testPage.indexOf("# Header 1"), - }, - ); + text: "# Header 1\nSome text\n\n", + offset: testPage.indexOf("# Header 1"), + }); expect(await space.readRef(parseToRef("test#Header 2")!)).toEqual({ - text: - "# Header 2\n* Item 1\n * Sub item\n* [ ] Task 1\n * Sub item 2\n * Sub-sub item", - offset: testPage.indexOf("# Header 2"), - }, - ); + text: "# Header 2\n* Item 1\n * Sub item\n* [ ] Task 1\n * Sub item 2\n * Sub-sub item", + offset: testPage.indexOf("# Header 2"), + }); // Reference to an item should get item and children const itemPos = testPage.indexOf("* Item 1"); - expect(await space.readRef(parseToRef(`test@${itemPos}`)!)).toEqual({ text: "* Item 1\n * Sub item", offset: itemPos }, - ); + expect(await space.readRef(parseToRef(`test@${itemPos}`)!)).toEqual({ + text: "* Item 1\n * Sub item", + offset: itemPos, + }); // Reference to a task should get item and children const taskPos = testPage.indexOf("* [ ] Task 1"); expect(await space.readRef(parseToRef(`test@${taskPos}`)!)).toEqual({ - text: "* [ ] Task 1\n * Sub item 2\n * Sub-sub item", - offset: taskPos, - }, - ); + text: "* [ ] Task 1\n * Sub item 2\n * Sub-sub item", + offset: taskPos, + }); // Check left shift in case of jumping into nested item const subItemPos = testPage.indexOf("* Sub item 2"); - expect(await space.readRef(parseToRef(`test@${subItemPos}`)!)).toEqual({ text: "* Sub item 2\n * Sub-sub item", offset: subItemPos }, - ); + expect(await space.readRef(parseToRef(`test@${subItemPos}`)!)).toEqual({ + text: "* Sub item 2\n * Sub-sub item", + offset: subItemPos, + }); }); diff --git a/client/space.ts b/client/space.ts index 0efb0771..77a500fa 100644 --- a/client/space.ts +++ b/client/space.ts @@ -67,8 +67,7 @@ export class Space { async listPlugs(): Promise { const files = await this.deduplicatedFileList(); - return files - .filter((fileMeta) => fileMeta.name.endsWith(".plug.js")); + return files.filter((fileMeta) => fileMeta.name.endsWith(".plug.js")); } async readPage(name: string): Promise<{ text: string; meta: PageMeta }> { @@ -153,10 +152,7 @@ export class Space { return { text: "", offset: 0 }; } - async writePage( - name: string, - text: string, - ): Promise { + async writePage(name: string, text: string): Promise { try { this.saving = true; const pageMeta = fileMetaToPageMeta( @@ -181,8 +177,11 @@ export class Space { // - it's not a markdown file // - it's not a javascript of javascript source map (.map) isListedDocument(fileMeta: FileMeta): boolean { - return !this.isListedPage(fileMeta) && !fileMeta.name.endsWith(".js") && - !fileMeta.name.endsWith(".map"); + return ( + !this.isListedPage(fileMeta) && + !fileMeta.name.endsWith(".js") && + !fileMeta.name.endsWith(".map") + ); } async fetchPageList(): Promise { @@ -193,7 +192,7 @@ export class Space { async fetchDocumentList(): Promise { return (await this.deduplicatedFileList()).flatMap((fileMeta) => - this.isListedDocument(fileMeta) ? [fileMetaToDocumentMeta(fileMeta)] : [] + this.isListedDocument(fileMeta) ? [fileMetaToDocumentMeta(fileMeta)] : [], ); } @@ -226,15 +225,10 @@ export class Space { } async getDocumentMeta(name: string): Promise { - return fileMetaToDocumentMeta( - await this.spacePrimitives.getFileMeta(name), - ); + return fileMetaToDocumentMeta(await this.spacePrimitives.getFileMeta(name)); } - async writeDocument( - name: string, - data: Uint8Array, - ): Promise { + async writeDocument(name: string, data: Uint8Array): Promise { return fileMetaToDocumentMeta( await this.spacePrimitives.writeFile(name, data), ); @@ -297,9 +291,7 @@ export function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta { } } -export function fileMetaToDocumentMeta( - fileMeta: FileMeta, -): DocumentMeta { +export function fileMetaToDocumentMeta(fileMeta: FileMeta): DocumentMeta { try { return { ...fileMeta, diff --git a/client/space_lua.ts b/client/space_lua.ts index 86747dd5..33d117fb 100644 --- a/client/space_lua.ts +++ b/client/space_lua.ts @@ -16,7 +16,10 @@ import type { ObjectIndex } from "./data/object_index.ts"; export class SpaceLuaEnvironment { env: LuaEnv; - constructor(private system: System, private objectIndex: ObjectIndex) { + constructor( + private system: System, + private objectIndex: ObjectIndex, + ) { this.env = buildLuaEnv(system); } @@ -30,14 +33,17 @@ export class SpaceLuaEnvironment { "space-lua", { objectVariable: "script", - orderBy: [{ - expr: parseExpressionString("script.priority or 0"), - desc: true, - nulls: "first", - }, { - expr: parseExpressionString("script.ref"), - desc: false, - }], + orderBy: [ + { + expr: parseExpressionString("script.priority or 0"), + desc: true, + nulls: "first", + }, + { + expr: parseExpressionString("script.ref"), + desc: false, + }, + ], } as LuaCollectionQuery, ); try { @@ -57,9 +63,9 @@ export class SpaceLuaEnvironment { const origin = resolveASTReference(e.sf.astCtx!); if (origin) { console.error( - `Error evaluating script: ${e.message} at [[${ - encodeRef(origin) - }]]`, + `Error evaluating script: ${e.message} at [[${encodeRef( + origin, + )}]]`, ); continue; } @@ -88,9 +94,8 @@ export function resolveASTReference(ctx?: ASTCtx): Ref | null { } if (ref.details?.type === "position") { - ref.details.pos = (ref.details.pos as number) + - "```space-lua\n".length + - ctx.from!; + ref.details.pos = + (ref.details.pos as number) + "```space-lua\n".length + ctx.from!; } return ref; diff --git a/client/space_lua/aggregates.test.ts b/client/space_lua/aggregates.test.ts index da9cad79..3d85b81d 100644 --- a/client/space_lua/aggregates.test.ts +++ b/client/space_lua/aggregates.test.ts @@ -174,10 +174,26 @@ test("aggregate: min/max on empty group", async () => { const env = new LuaEnv(); const expr = parseExpressionString("_.v"); expect( - await executeAggregate(minSpec, items, expr, undefined, env, sf, evalExpression), + await executeAggregate( + minSpec, + items, + expr, + undefined, + env, + sf, + evalExpression, + ), ).toBeNull(); expect( - await executeAggregate(maxSpec, items, expr, undefined, env, sf, evalExpression), + await executeAggregate( + maxSpec, + items, + expr, + undefined, + env, + sf, + evalExpression, + ), ).toBeNull(); }); @@ -381,7 +397,10 @@ test("evalExpressionWithAggregates: binary comparison (count > N)", async () => test("evalExpressionWithAggregates: binary arithmetic (sum + sum)", async () => { const cleanup = installFakeConfig(); try { - const groupItems = luaArray([{ a: 10, b: 5 }, { a: 20, b: 15 }]); + const groupItems = luaArray([ + { a: 10, b: 5 }, + { a: 20, b: 15 }, + ]); const env = new LuaEnv(); const result = await evalExpressionWithAggregates( parseExpressionString("sum(_.a) + sum(_.b)"), @@ -733,8 +752,8 @@ test("applyQuery: select with aggregate division (float result)", async () => { test("aggregate: custom concat with finish", async () => { const concatSpec: AggregateSpec = { name: "concat", - initialize: new LuaBuiltinFunction((_sf) => - new LuaTable({ first: true, s: "" }) + initialize: new LuaBuiltinFunction( + (_sf) => new LuaTable({ first: true, s: "" }), ), iterate: new LuaBuiltinFunction((_sf, state: any, value: any) => { if (value === null || value === undefined) return state; diff --git a/client/space_lua/ast.ts b/client/space_lua/ast.ts index dcbea714..d5f046f9 100644 --- a/client/space_lua/ast.ts +++ b/client/space_lua/ast.ts @@ -255,10 +255,7 @@ export type LuaTableConstructor = { fields: LuaTableField[]; } & ASTContext; -export type LuaTableField = - | LuaDynamicField - | LuaPropField - | LuaExpressionField; +export type LuaTableField = LuaDynamicField | LuaPropField | LuaExpressionField; export type LuaDynamicField = { type: "DynamicField"; diff --git a/client/space_lua/ast_narrow.ts b/client/space_lua/ast_narrow.ts index 336330aa..18f69037 100644 --- a/client/space_lua/ast_narrow.ts +++ b/client/space_lua/ast_narrow.ts @@ -3,8 +3,10 @@ import type { ASTCtx, LuaExpression, LuaLValue, LuaStatement } from "./ast.ts"; // Extract by `type` discriminant -type NarrowByType = - Extract; +type NarrowByType< + U, + K extends U extends { type: infer T } ? T : never, +> = Extract; // Expressions export const asStringExpr = (e: LuaExpression) => diff --git a/client/space_lua/break_async.test.ts b/client/space_lua/break_async.test.ts index ce706c88..2979512b 100644 --- a/client/space_lua/break_async.test.ts +++ b/client/space_lua/break_async.test.ts @@ -54,10 +54,7 @@ test("Nested for loops: inner break (sync body)", async () => { test("Nested for loops: inner break with async call in body (Promise path)", async () => { const env = new LuaEnv(luaBuildStandardEnv()); - env.set( - "asyncOne", - new LuaNativeJSFunction(() => Promise.resolve(1)), - ); + env.set("asyncOne", new LuaNativeJSFunction(() => Promise.resolve(1))); await evalBlock( ` local t = {1,2,3} @@ -79,10 +76,7 @@ test("Nested for loops: inner break with async call in body (Promise path)", asy test("While: Promise condition, break inside body", async () => { const env = new LuaEnv(luaBuildStandardEnv()); // asyncTrue returns a truthy value via Promise to trigger Promise-based while path - env.set( - "asyncTrue", - new LuaNativeJSFunction(() => Promise.resolve(1)), - ); + env.set("asyncTrue", new LuaNativeJSFunction(() => Promise.resolve(1))); await evalBlock( ` cnt = 0 @@ -107,10 +101,7 @@ test("While: Promise condition, break inside body", async () => { test("Repeat-until: body goes Promise once, break handled locally", async () => { const env = new LuaEnv(luaBuildStandardEnv()); - env.set( - "asyncOne", - new LuaNativeJSFunction(() => Promise.resolve(1)), - ); + env.set("asyncOne", new LuaNativeJSFunction(() => Promise.resolve(1))); await evalBlock( ` n = 0 @@ -128,10 +119,7 @@ test("Repeat-until: body goes Promise once, break handled locally", async () => test("For-in: custom iterator, Promise in body before break", async () => { const env = new LuaEnv(luaBuildStandardEnv()); - env.set( - "asyncOne", - new LuaNativeJSFunction(() => Promise.resolve(1)), - ); + env.set("asyncOne", new LuaNativeJSFunction(() => Promise.resolve(1))); await evalBlock( ` function gen(n) @@ -157,10 +145,7 @@ test("For-in: custom iterator, Promise in body before break", async () => { test("Jaro-like inner-window matching: inner break with occasional Promise", async () => { const env = new LuaEnv(luaBuildStandardEnv()); - env.set( - "tick", - new LuaNativeJSFunction(() => Promise.resolve(true)), - ); + env.set("tick", new LuaNativeJSFunction(() => Promise.resolve(true))); await evalBlock( ` local s1 = "#aa" diff --git a/client/space_lua/close_attribute.test.ts b/client/space_lua/close_attribute.test.ts index 3c3f7020..232109c9 100644 --- a/client/space_lua/close_attribute.test.ts +++ b/client/space_lua/close_attribute.test.ts @@ -311,7 +311,9 @@ test("close: reassign after implicit nil errors", async () => { ); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("close: reassign after explicit nil errors", async () => { @@ -326,7 +328,9 @@ test("close: reassign after explicit nil errors", async () => { ); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("close: reassign after false is non-closable", async () => { @@ -359,7 +363,9 @@ test("close: reassign after closable value errors", async () => { ); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("close: reassignment through closure is non-closable", async () => { diff --git a/client/space_lua/const_attribute.test.ts b/client/space_lua/const_attribute.test.ts index 069c79d5..a71bfee1 100644 --- a/client/space_lua/const_attribute.test.ts +++ b/client/space_lua/const_attribute.test.ts @@ -79,7 +79,9 @@ test("const: Initialization ok, reassignment error", async () => { x = 2 `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'x'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'x'", + ); }); test("const: Initialization to nil ok, reassignment error", async () => { @@ -88,7 +90,9 @@ test("const: Initialization to nil ok, reassignment error", async () => { x = 2 `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'x'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'x'", + ); }); test("const: Multi-declaration and missing RHS", async () => { @@ -117,7 +121,9 @@ test("const: Multi-assignment after declaration fails", async () => { a, b = 3, 4 `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("const: Second position", async () => { @@ -126,7 +132,9 @@ test("const: Second position", async () => { b = 3 `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'b'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'b'", + ); }); test("const: Initialization from function returns", async () => { @@ -152,7 +160,9 @@ test("const: Initialization from function returns", async () => { a = 2 `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("const: Shadowing", async () => { @@ -166,7 +176,9 @@ test("const: Shadowing", async () => { end `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); } { @@ -226,7 +238,9 @@ test("const: Rebinding table fails", async () => { "const_table_rebind.lua", ); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 't'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 't'", + ); }); test("const: Inner block reassignment", async () => { @@ -237,7 +251,9 @@ test("const: Inner block reassignment", async () => { end `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("const: Closure reassignment", async () => { @@ -251,7 +267,9 @@ test("const: Closure reassignment", async () => { f() `); expect(e).toBeInstanceOf(LuaRuntimeError); - expect((e as LuaRuntimeError).message).toContain("attempt to assign to const variable 'a'",); + expect((e as LuaRuntimeError).message).toContain( + "attempt to assign to const variable 'a'", + ); }); test("const: Closure mutates table", async () => { diff --git a/client/space_lua/context_errors.test.ts b/client/space_lua/context_errors.test.ts index d8bacd1a..cd1371de 100644 --- a/client/space_lua/context_errors.test.ts +++ b/client/space_lua/context_errors.test.ts @@ -45,7 +45,6 @@ test("Context error: indexing nil value includes message and ref", async () => { assertCtxErrorContains(e, code, ref, "attempt to index a nil value"); }); - test("Context error: calling nil includes message and ref", async () => { const { e, code, ref } = await runAndCatch( ` @@ -94,12 +93,7 @@ test("Context error: __index metamethod must be function or table", async () => `, "meta_index_wrong.lua", ); - assertCtxErrorContains( - e, - code, - ref, - "attempt to index a number value", - ); + assertCtxErrorContains(e, code, ref, "attempt to index a number value"); }); test("Context error: __call metamethod must be a function", async () => { @@ -111,12 +105,7 @@ test("Context error: __call metamethod must be a function", async () => { `, "meta_call_wrong.lua", ); - assertCtxErrorContains( - e, - code, - ref, - "attempt to call a number value", - ); + assertCtxErrorContains(e, code, ref, "attempt to call a number value"); }); test("Context error: length operator wrong type (number)", async () => { diff --git a/client/space_lua/eval.test.ts b/client/space_lua/eval.test.ts index e989122c..d3ef6902 100644 --- a/client/space_lua/eval.test.ts +++ b/client/space_lua/eval.test.ts @@ -17,11 +17,7 @@ const sf = LuaStackFrame.lostFrame; function evalExpr(s: string, e = new LuaEnv(), sf?: LuaStackFrame): any { const node = parse(`e(${s})`).statements[0] as LuaFunctionCallStatement; sf = sf || new LuaStackFrame(e, node.ctx); - return evalExpression( - node.call.args[0], - e, - sf, - ); + return evalExpression(node.call.args[0], e, sf); } async function evalBlock(s: string, e = new LuaEnv()): Promise { @@ -107,14 +103,12 @@ test("Evaluator test", async () => { }); test("Parser rejects unary plus - parenthesized", () => { - for ( - const src of [ - "return +(1)", - "return +((1))", - "return +(1 + 2)", - "return +({})", - ] - ) { + for (const src of [ + "return +(1)", + "return +((1))", + "return +(1 + 2)", + "return +({})", + ]) { let err: any = null; try { parse(src); @@ -129,15 +123,13 @@ test("Parser rejects unary plus - parenthesized", () => { }); test("Parser rejects unary plus - variables and calls", () => { - for ( - const src of [ - "return +(a)", - "return +(a.b)", - "return +(a[1])", - "return +f()", - "return +(f())", - ] - ) { + for (const src of [ + "return +(a)", + "return +(a.b)", + "return +(a[1])", + "return +f()", + "return +(f())", + ]) { let err: any = null; try { parse(src); @@ -152,14 +144,12 @@ test("Parser rejects unary plus - variables and calls", () => { }); test("Parser rejects unary plus - whitespace/newlines", () => { - for ( - const src of [ - "return + 1", - "return +\n1", - "return +\t(1)", - "return \n+\n(1)", - ] - ) { + for (const src of [ + "return + 1", + "return +\n1", + "return +\t(1)", + "return \n+\n(1)", + ]) { let err: any = null; try { parse(src); diff --git a/client/space_lua/eval.ts b/client/space_lua/eval.ts index 03da82d5..0725a5c5 100644 --- a/client/space_lua/eval.ts +++ b/client/space_lua/eval.ts @@ -105,8 +105,14 @@ function astNumberKind(e: LuaExpression | undefined): NumericType | undefined { result = unwrapped.numericType === "int" ? "int" : "float"; } else if (unwrapped.type === "Binary") { const op = unwrapped.operator; - const numericOp = op === "+" || op === "-" || op === "*" || op === "/" || - op === "//" || op === "%" || op === "^"; + const numericOp = + op === "+" || + op === "-" || + op === "*" || + op === "/" || + op === "//" || + op === "%" || + op === "^"; if (numericOp) { const lk = astNumberKind(unwrapped.left); @@ -176,7 +182,6 @@ function blockMetaOrThrow( } } - function arithVerbFromOperator(op: string): string | null { switch (op) { case "+": @@ -254,13 +259,11 @@ export function luaOp( case "^": { const ar = numericArith[op as NumericArithOp]; try { - const { left: l, right: r, resultType } = coerceNumericPair( - left, - right, - leftType, - rightType, - op, - ); + const { + left: l, + right: r, + resultType, + } = coerceNumericPair(left, right, leftType, rightType, op); let result = ar.f(l, r); @@ -394,11 +397,14 @@ export function luaOp( type NumericArithOp = "+" | "-" | "*" | "/" | "^"; -const numericArith: Record number; - special?: "sub"; -}> = { +const numericArith: Record< + NumericArithOp, + { + metaMethod: "__add" | "__sub" | "__mul" | "__div" | "__pow"; + f: (l: number, r: number) => number; + special?: "sub"; + } +> = { "+": { metaMethod: "__add", f: (l, r) => l + r }, "-": { metaMethod: "__sub", f: (l, r) => l - r, special: "sub" }, "*": { metaMethod: "__mul", f: (l, r) => l * r }, @@ -448,10 +454,7 @@ function luaFloorDiv( ); if (resultType === "int" && right === 0) { - throw new LuaRuntimeError( - `attempt to divide by zero`, - sf.withCtx(ctx), - ); + throw new LuaRuntimeError(`attempt to divide by zero`, sf.withCtx(ctx)); } const result = Math.floor(left / right); @@ -482,10 +485,7 @@ function luaMod( ); if (resultType === "int" && right === 0) { - throw new LuaRuntimeError( - `attempt to perform 'n%0'`, - sf.withCtx(ctx), - ); + throw new LuaRuntimeError(`attempt to perform 'n%0'`, sf.withCtx(ctx)); } const q = Math.floor(left / right); @@ -506,10 +506,7 @@ function luaMod( return normalized; } -function luaUnaryMinus( - v: number, - numType: NumericType | undefined, -): number { +function luaUnaryMinus(v: number, numType: NumericType | undefined): number { const vType = numType ?? inferNumericType(v); if (v === 0 && vType === "int") { @@ -523,17 +520,20 @@ function luaUnaryMinus( return -v; } -const operatorsMetaMethods: Record LuaValue; -}> = { +const operatorsMetaMethods: Record< + string, + { + metaMethod?: string; + nativeImplementation: ( + a: LuaValue, + b: LuaValue, + leftType: NumericType | undefined, + rightType: NumericType | undefined, + ctx: ASTCtx, + sf: LuaStackFrame, + ) => LuaValue; + } +> = { "//": { metaMethod: "__idiv", nativeImplementation: (a, b, lt, rt, ctx, sf) => @@ -629,14 +629,7 @@ export function evalExpression( if (b.operator === "and") { return evalLogical("and", b.left, b.right, env, sf); } - return evalBinaryWithLR( - b.operator, - b.left, - b.right, - b.ctx, - env, - sf, - ); + return evalBinaryWithLR(b.operator, b.left, b.right, b.ctx, env, sf); } case "Unary": { const u = asUnary(e); @@ -660,59 +653,53 @@ export function evalExpression( const applyTyped = (typed: TypedValue) => { const arg = singleResult(typed.value); - return unaryWithMeta( - arg, - "__unm", - u.ctx, - sf, - () => { - // Numeric-string coercion for unary minus - if (typeof arg === "string") { - const n = coerceToNumber(arg); - if (n === null) { - throw new LuaRuntimeError( - "attempt to unm a 'string' with a 'string'", - sf.withCtx(u.ctx), - ); - } - if (n === 0) { - return 0; - } - return -n; - } - - const plain = untagNumber(arg); - if (typeof plain !== "number") { + return unaryWithMeta(arg, "__unm", u.ctx, sf, () => { + // Numeric-string coercion for unary minus + if (typeof arg === "string") { + const n = coerceToNumber(arg); + if (n === null) { throw new LuaRuntimeError( - "attempt to perform arithmetic on a table value", + "attempt to unm a 'string' with a 'string'", sf.withCtx(u.ctx), ); } - - const argType = isTaggedFloat(arg) - ? "float" - : astNumberKind(u.argument); - - const out = luaUnaryMinus(plain, argType); - - // If the operand is a float-tagged boxed number, unary - // minus must keep the result float-typed. - if (isTaggedFloat(arg)) { - if (out === 0) { - return makeLuaZero(out, "float"); - } - return makeLuaFloat(out); + if (n === 0) { + return 0; } + return -n; + } - // Preserve numeric kind for zero results + const plain = untagNumber(arg); + if (typeof plain !== "number") { + throw new LuaRuntimeError( + "attempt to perform arithmetic on a table value", + sf.withCtx(u.ctx), + ); + } + + const argType = isTaggedFloat(arg) + ? "float" + : astNumberKind(u.argument); + + const out = luaUnaryMinus(plain, argType); + + // If the operand is a float-tagged boxed number, unary + // minus must keep the result float-typed. + if (isTaggedFloat(arg)) { if (out === 0) { - const outType = argType ?? inferNumericType(plain); - return makeLuaZero(out, outType); + return makeLuaZero(out, "float"); } + return makeLuaFloat(out); + } - return out; - }, - ); + // Preserve numeric kind for zero results + if (out === 0) { + const outType = argType ?? inferNumericType(plain); + return makeLuaZero(out, outType); + } + + return out; + }); }; return rpThen(tv as any, applyTyped); @@ -727,35 +714,29 @@ export function evalExpression( } case "~": { const arg = singleResult(value); - return unaryWithMeta( - arg, - "__bnot", - u.ctx, - sf, - () => { - const intVal = toInteger(arg); - if (intVal === null) { - if (typeof arg === "string") { - throw new LuaRuntimeError( - `attempt to perform bitwise operation on a string value (constant '${arg}')`, - sf.withCtx(u.ctx), - ); - } - const t = luaTypeName(arg); - if (t === "number") { - throw new LuaRuntimeError( - `number has no integer representation`, - sf.withCtx(u.ctx), - ); - } + return unaryWithMeta(arg, "__bnot", u.ctx, sf, () => { + const intVal = toInteger(arg); + if (intVal === null) { + if (typeof arg === "string") { throw new LuaRuntimeError( - `attempt to perform bitwise operation on a ${t} value`, + `attempt to perform bitwise operation on a string value (constant '${arg}')`, sf.withCtx(u.ctx), ); } - return ~intVal; - }, - ); + const t = luaTypeName(arg); + if (t === "number") { + throw new LuaRuntimeError( + `number has no integer representation`, + sf.withCtx(u.ctx), + ); + } + throw new LuaRuntimeError( + `attempt to perform bitwise operation on a ${t} value`, + sf.withCtx(u.ctx), + ); + } + return ~intVal; + }); } case "#": { return luaLengthOp(singleResult(value), u.ctx, sf); @@ -825,20 +806,14 @@ export function evalExpression( const q = asQueryExpr(e); const findFromClause = q.clauses.find((c) => c.type === "From"); if (!findFromClause) { - throw new LuaRuntimeError( - "No from clause found", - sf.withCtx(q.ctx), - ); + throw new LuaRuntimeError("No from clause found", sf.withCtx(q.ctx)); } const objectVariable = findFromClause.name; const objectExpression = findFromClause.expression; return Promise.resolve(evalExpression(objectExpression, env, sf)).then( async (collection: LuaValue) => { if (!collection) { - throw new LuaRuntimeError( - "Collection is nil", - sf.withCtx(q.ctx), - ); + throw new LuaRuntimeError("Collection is nil", sf.withCtx(q.ctx)); } // If already a queryable collection (e.g. DataStoreQueryCollection), @@ -973,13 +948,10 @@ function evalPrefixExpression( return luaGet(table, key, ta.ctx, sf); } - return rpThen( - objV, - (obj) => - rpThen( - keyV, - (key) => luaGet(singleResult(obj), singleResult(key), ta.ctx, sf), - ), + return rpThen(objV, (obj) => + rpThen(keyV, (key) => + luaGet(singleResult(obj), singleResult(key), ta.ctx, sf), + ), ); } @@ -998,15 +970,13 @@ function evalPrefixExpression( const fc = asFunctionCall(e); const prefixValue = evalPrefixExpression(fc.prefix, env, sf); if (prefixValue === null || prefixValue === undefined) { - const nilMsg = fc.prefix.type === "Variable" - ? `attempt to call a nil value (global '${ - asVariable(fc.prefix).name - }')` - : `attempt to call a nil value`; - throw new LuaRuntimeError( - nilMsg, - sf.withCtx(fc.prefix.ctx), - ); + const nilMsg = + fc.prefix.type === "Variable" + ? `attempt to call a nil value (global '${ + asVariable(fc.prefix).name + }')` + : `attempt to call a nil value`; + throw new LuaRuntimeError(nilMsg, sf.withCtx(fc.prefix.ctx)); } let selfArgs: LuaValue[] = []; @@ -1280,10 +1250,7 @@ function luaEqWithMetamethod( } const ty = luaTypeName(mm); - throw new LuaRuntimeError( - `attempt to call a ${ty} value`, - sf.withCtx(ctx), - ); + throw new LuaRuntimeError(`attempt to call a ${ty} value`, sf.withCtx(ctx)); }; // Try left __eq first, then right. @@ -1340,11 +1307,7 @@ function luaRelWithMetamethod( * - for JavaScript arrays return length, * - throw error otherwise. */ -function luaLengthOp( - val: any, - ctx: ASTCtx, - sf: LuaStackFrame, -): LuaValue { +function luaLengthOp(val: any, ctx: ASTCtx, sf: LuaStackFrame): LuaValue { // Strings: ignore `__len` if (typeof val === "string") { return val.length; @@ -1434,12 +1397,7 @@ function runStatementsNoGoto( idx: number, ): undefined | ControlSignal | Promise => { for (let i = idx; i < stmts.length; i++) { - const result = evalStatement( - stmts[i], - execEnv, - sf, - returnOnReturn, - ); + const result = evalStatement(stmts[i], execEnv, sf, returnOnReturn); if (isPromise(result)) { return (result as Promise).then((res) => { if (res !== undefined) { @@ -1491,9 +1449,8 @@ function withCloseBoundary( }; const onRejected = (e: any) => { - const errObj: LuaValue = e instanceof LuaRuntimeError - ? e.message - : (e?.message ?? String(e)); + const errObj: LuaValue = + e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e)); const r = luaCloseFromMark(sf, mark, errObj); if (isPromise(r)) { return (r as Promise).then(() => { @@ -1536,9 +1493,7 @@ function evalBlockNoClose( if (fnHasGotos === true && !hasLabelHere && !hasGotoFlag) { const execEnv = b.needsEnv === true ? new LuaEnv(env) : env; const stmts = b.statements; - const runFrom = ( - i: number, - ): EvalBlockResult => { + const runFrom = (i: number): EvalBlockResult => { for (; i < stmts.length; i++) { const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn); if (isPromise(r)) { @@ -1583,9 +1538,7 @@ function evalBlockNoClose( const execEnv = b.needsEnv === true ? new LuaEnv(env) : env; const stmts = b.statements; - const runFrom = ( - i: number, - ): EvalBlockResult => { + const runFrom = (i: number): EvalBlockResult => { for (; i < stmts.length; i++) { const r = evalStatement(stmts[i], execEnv, sf, returnOnReturn); if (isPromise(r)) { @@ -1635,8 +1588,9 @@ export function evalStatement( case "Assignment": { const a = asAssignment(s); const valuesRP = evalExpressions(a.expressions, env, sf); - const lvaluesRP = evalPromiseValues(a.variables - .map((lval) => evalLValue(lval, env, sf))); + const lvaluesRP = evalPromiseValues( + a.variables.map((lval) => evalLValue(lval, env, sf)), + ); const apply = (values: LuaValue[], lvalues: { env: any; key: any }[]) => { const ps: Promise[] = []; @@ -1659,29 +1613,22 @@ export function evalStatement( }; if (!isPromise(valuesRP) && !isPromise(lvaluesRP)) { - return apply( - valuesRP as LuaValue[], - lvaluesRP as LuaLValueContainer[], - ); + return apply(valuesRP as LuaValue[], lvaluesRP as LuaLValueContainer[]); } - if ( - isPromise(valuesRP) && !isPromise(lvaluesRP) - ) { + if (isPromise(valuesRP) && !isPromise(lvaluesRP)) { return (valuesRP as Promise).then((values: LuaValue[]) => - apply(values, lvaluesRP as LuaLValueContainer[]) + apply(values, lvaluesRP as LuaLValueContainer[]), ); } - if ( - !isPromise(valuesRP) && isPromise(lvaluesRP) - ) { + if (!isPromise(valuesRP) && isPromise(lvaluesRP)) { return (lvaluesRP as Promise).then((lvalues: any[]) => - apply(valuesRP as LuaValue[], lvalues) + apply(valuesRP as LuaValue[], lvalues), ); } return (valuesRP as Promise).then((values: LuaValue[]) => (lvaluesRP as Promise).then((lvalues: any[]) => - apply(values, lvalues) - ) + apply(values, lvalues), + ), ); } case "Local": { @@ -1730,10 +1677,7 @@ export function evalStatement( const bindAvailable = () => { while (boundCount < l.names.length && boundCount < out.length) { - bindOne( - l.names[boundCount], - out[boundCount] ?? null, - ); + bindOne(l.names[boundCount], out[boundCount] ?? null); boundCount++; } }; @@ -1813,9 +1757,8 @@ export function evalStatement( try { out = evalBlockNoClose(b, env, sf, returnOnReturn); } catch (e: any) { - const errObj: LuaValue = e instanceof LuaRuntimeError - ? e.message - : (e?.message ?? String(e)); + const errObj: LuaValue = + e instanceof LuaRuntimeError ? e.message : (e?.message ?? String(e)); const r = luaCloseFromMark(sf, mark, errObj); if (isPromise(r)) { return (r as Promise).then(() => { @@ -1834,10 +1777,7 @@ export function evalStatement( const runFrom = ( i: number, - ): - | undefined - | ControlSignal - | Promise => { + ): undefined | ControlSignal | Promise => { if (i >= conds.length) { if (iff.elseBlock) { return evalStatement(iff.elseBlock, env, sf, returnOnReturn); @@ -1889,25 +1829,25 @@ export function evalStatement( if (!luaTruthy(cv)) { return; } - const r = evalStatement(w.block, env, sf, returnOnReturn); - if (isPromise(r)) { - return (r as Promise).then((res) => { - if (res !== undefined) { - if (isBreakSignal(res)) { - return; - } - return res; + const r = evalStatement(w.block, env, sf, returnOnReturn); + if (isPromise(r)) { + return (r as Promise).then((res) => { + if (res !== undefined) { + if (isBreakSignal(res)) { + return; } - return runAsync(); - }); - } - if (r !== undefined) { - if (isBreakSignal(r)) { - return; + return res; } - return r; + return runAsync(); + }); + } + if (r !== undefined) { + if (isBreakSignal(r)) { + return; } - return runAsync(); + return r; + } + return runAsync(); }); } if (!luaTruthy(c)) { @@ -1978,7 +1918,7 @@ export function evalStatement( const c = evalExpression(r.condition, env, sf); if (isPromise(c)) { return (c as Promise).then((cv) => - luaTruthy(cv) ? undefined : runAsync() + luaTruthy(cv) ? undefined : runAsync(), ); } if (luaTruthy(c)) { @@ -2005,7 +1945,7 @@ export function evalStatement( if (fn.name.colonName) { // function hello:there() -> function hello.there(self) transformation body = { - ...(fn.body), + ...fn.body, parameters: ["self", ...fn.body.parameters], }; propNames = [...fn.name.propNames, fn.name.colonName]; @@ -2028,17 +1968,14 @@ export function evalStatement( } case "LocalFunction": { const lf = asLocalFunction(s); - env.setLocal( - lf.name, - new LuaFunction(lf.body, env), - ); + env.setLocal(lf.name, new LuaFunction(lf.body, env)); return; } case "Return": { const ret = asReturn(s); const parts = ret.expressions.map((value: LuaExpression) => - evalExpression(value, env, sf) + evalExpression(value, env, sf), ); const valuesRP = rpAll(parts); @@ -2086,9 +2023,7 @@ export function evalStatement( const determineLoopType = (): NumericType => { const startType = astNumberKind(fr.start); const stepType = fr.step ? astNumberKind(fr.step) : "int"; - return (startType === "float" || stepType === "float") - ? "float" - : "int"; + return startType === "float" || stepType === "float" ? "float" : "int"; }; const wrapLoopVar = (i: number, loopType: NumericType) => { @@ -2098,27 +2033,27 @@ export function evalStatement( return i; }; - const canReuseEnv = !fr.block.hasFunctionDef || - fr.capturesLoopVar === false; + const canReuseEnv = + !fr.block.hasFunctionDef || fr.capturesLoopVar === false; const executeIteration = canReuseEnv ? ( - loopEnv: LuaEnv, - i: number, - loopType: NumericType, - ): undefined | ControlSignal | Promise => { - loopEnv.setLocal(fr.name, wrapLoopVar(i, loopType)); - return evalStatement(fr.block, loopEnv, sf, returnOnReturn); - } + loopEnv: LuaEnv, + i: number, + loopType: NumericType, + ): undefined | ControlSignal | Promise => { + loopEnv.setLocal(fr.name, wrapLoopVar(i, loopType)); + return evalStatement(fr.block, loopEnv, sf, returnOnReturn); + } : ( - _loopEnv: LuaEnv, - i: number, - loopType: NumericType, - ): undefined | ControlSignal | Promise => { - const localEnv = new LuaEnv(env); - localEnv.setLocal(fr.name, wrapLoopVar(i, loopType)); - return evalStatement(fr.block, localEnv, sf, returnOnReturn); - }; + _loopEnv: LuaEnv, + i: number, + loopType: NumericType, + ): undefined | ControlSignal | Promise => { + const localEnv = new LuaEnv(env); + localEnv.setLocal(fr.name, wrapLoopVar(i, loopType)); + return evalStatement(fr.block, localEnv, sf, returnOnReturn); + }; const runAsync = async ( loopEnv: LuaEnv, @@ -2131,9 +2066,8 @@ export function evalStatement( throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx)); } - const shouldContinue = step > 0 - ? (i: number) => i <= end - : (i: number) => i >= end; + const shouldContinue = + step > 0 ? (i: number) => i <= end : (i: number) => i >= end; for (let i = startIndex; shouldContinue(i); i += step) { const r = executeIteration(loopEnv, i, loopType); @@ -2152,17 +2086,13 @@ export function evalStatement( end: number, step: number, loopType: NumericType, - ): - | undefined - | ControlSignal - | Promise => { + ): undefined | ControlSignal | Promise => { if (step === 0) { throw new LuaRuntimeError("'for' step is zero", sf.withCtx(fr.ctx)); } - const shouldContinue = step > 0 - ? (i: number) => i <= end - : (i: number) => i >= end; + const shouldContinue = + step > 0 ? (i: number) => i <= end : (i: number) => i >= end; const loopEnv = new LuaEnv(env); @@ -2191,11 +2121,7 @@ export function evalStatement( const loopType = determineLoopType(); - if ( - !isPromise(startV) && - !isPromise(endV) && - !isPromise(stepV) - ) { + if (!isPromise(startV) && !isPromise(endV) && !isPromise(stepV)) { return runSyncFirst( untagNumber(startV) as number, untagNumber(endV) as number, @@ -2222,8 +2148,8 @@ export function evalStatement( fi.expressions.map((e: LuaExpression) => evalExpression(e, env, sf)), ); - const canReuseEnv = !fi.block.hasFunctionDef || - fi.capturesLoopVar === false; + const canReuseEnv = + !fi.block.hasFunctionDef || fi.capturesLoopVar === false; const setIterVars = ( localEnv: LuaEnv, names: string[], @@ -2335,44 +2261,47 @@ export function evalStatement( sf, ); if (isPromise(iterCall)) { - return (iterCall as Promise).then((itv) => { - const iterResult = new LuaMultiRes(itv).flatten(); - const nextControl = iterResult.values[0]; - if (nextControl === null || nextControl === undefined) { - const r = finish(undefined); - if (isPromise(r)) return (r as Promise).then(() => {}); - return; - } - control = nextControl; - - const localEnv = makeIterEnv(); - setIterVars(localEnv, fi.names, iterResult.values); - - const r = evalStatement( - fi.block, - localEnv, - sf, - returnOnReturn, - ); - if (isPromise(r)) { - return (r as Promise).then((res) => { - if (res !== undefined) { - if (isBreakSignal(res)) { - return finish(undefined); - } - return rpThen(finish(undefined), () => res); - } - return runAsync(); - }); - } - if (r !== undefined) { - if (isBreakSignal(r)) { - return finish(undefined); + return (iterCall as Promise) + .then((itv) => { + const iterResult = new LuaMultiRes(itv).flatten(); + const nextControl = iterResult.values[0]; + if (nextControl === null || nextControl === undefined) { + const r = finish(undefined); + if (isPromise(r)) + return (r as Promise).then(() => {}); + return; } - return rpThen(finish(undefined), () => r); - } - return runAsync(); - }).catch((e: any) => finishErr(e)); + control = nextControl; + + const localEnv = makeIterEnv(); + setIterVars(localEnv, fi.names, iterResult.values); + + const r = evalStatement( + fi.block, + localEnv, + sf, + returnOnReturn, + ); + if (isPromise(r)) { + return (r as Promise).then((res) => { + if (res !== undefined) { + if (isBreakSignal(res)) { + return finish(undefined); + } + return rpThen(finish(undefined), () => res); + } + return runAsync(); + }); + } + if (r !== undefined) { + if (isBreakSignal(r)) { + return finish(undefined); + } + return rpThen(finish(undefined), () => r); + } + return runAsync(); + }) + .catch((e: any) => finishErr(e)); } const iterResult = new LuaMultiRes(iterCall).flatten(); @@ -2380,7 +2309,7 @@ export function evalStatement( if (nextControl === null || nextControl === undefined) { const r = finish(undefined); if (isPromise(r)) { - return (r as Promise); + return r as Promise; } return; } @@ -2391,15 +2320,17 @@ export function evalStatement( const r = evalStatement(fi.block, localEnv, sf, returnOnReturn); if (isPromise(r)) { - return (r as Promise).then((res) => { - if (res !== undefined) { - if (isBreakSignal(res)) { - return finish(undefined); + return (r as Promise) + .then((res) => { + if (res !== undefined) { + if (isBreakSignal(res)) { + return finish(undefined); + } + return rpThen(finish(undefined), () => res); } - return rpThen(finish(undefined), () => res); - } - return runAsync(); - }).catch((e: any) => finishErr(e)); + return runAsync(); + }) + .catch((e: any) => finishErr(e)); } if (r !== undefined) { if (isBreakSignal(r)) { @@ -2436,16 +2367,9 @@ function evalLValue( } case "TableAccess": { const ta = asLValueTableAccess(lval); - const objValue = evalExpression( - ta.object, - env, - sf, - ); + const objValue = evalExpression(ta.object, env, sf); const keyValue = evalExpression(ta.key, env, sf); - if ( - isPromise(objValue) || - isPromise(keyValue) - ) { + if (isPromise(objValue) || isPromise(keyValue)) { return Promise.all([ isPromise(objValue) ? objValue : Promise.resolve(objValue), isPromise(keyValue) ? keyValue : Promise.resolve(keyValue), @@ -2461,11 +2385,7 @@ function evalLValue( } case "PropertyAccess": { const pa = asLValuePropertyAccess(lval); - const objValue = evalExpression( - pa.object, - env, - sf, - ); + const objValue = evalExpression(pa.object, env, sf); if (isPromise(objValue)) { return (objValue as Promise).then((ov) => { return { diff --git a/client/space_lua/labels.ts b/client/space_lua/labels.ts index d4368b2e..10987783 100644 --- a/client/space_lua/labels.ts +++ b/client/space_lua/labels.ts @@ -27,7 +27,10 @@ type FunctionMeta = { }; class LabelResolveError extends Error { - constructor(msg: string, public astCtx: ASTCtx) { + constructor( + msg: string, + public astCtx: ASTCtx, + ) { super(msg); } } @@ -70,14 +73,7 @@ type ValidationCtx = { closeLocals: Set; }; -type BlockRole = - | "Root" - | "Do" - | "If" - | "While" - | "Repeat" - | "For" - | "ForIn"; +type BlockRole = "Root" | "Do" | "If" | "While" | "Repeat" | "For" | "ForIn"; function resolveFunction(root: LuaBlock): FunctionMeta { const existing = functionMetaByRoot.get(root); @@ -282,8 +278,7 @@ function processBlock( curActive.add(id); const isClose = - l.names[j].attributes?.includes(LuaAttribute.Close) === - true; + l.names[j].attributes?.includes(LuaAttribute.Close) === true; if (isClose) { vctx.closeLocals.add(id); } diff --git a/client/space_lua/lua.test.ts b/client/space_lua/lua.test.ts index 2f6e511f..27ce2810 100644 --- a/client/space_lua/lua.test.ts +++ b/client/space_lua/lua.test.ts @@ -3,9 +3,9 @@ import { parse } from "./parse.ts"; import { luaBuildStandardEnv } from "./stdlib.ts"; import { LuaEnv, LuaRuntimeError, LuaStackFrame } from "./runtime.ts"; import { evalStatement } from "./eval.ts"; -import { readFile, } from "node:fs/promises"; +import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; -import { dirname, } from "node:path"; +import { dirname } from "node:path"; import { readdirSync } from "node:fs"; const __dirname = dirname(fileURLToPath(import.meta.url)); diff --git a/client/space_lua/numeric.ts b/client/space_lua/numeric.ts index 0891e447..5d7d0244 100644 --- a/client/space_lua/numeric.ts +++ b/client/space_lua/numeric.ts @@ -31,10 +31,7 @@ function makeFloat(n: number): LuaTaggedFloat { } // Box a zero with a given kind tag. -export function makeLuaZero( - n: number, - numericType: NumericType, -): any { +export function makeLuaZero(n: number, numericType: NumericType): any { if (n !== 0) { return n; } @@ -107,9 +104,7 @@ export function combineNumericTypes( return "int"; } -export function getNumericKind( - n: unknown, -): NumericType | undefined { +export function getNumericKind(n: unknown): NumericType | undefined { if (typeof n === "number") { return inferNumericType(n); } @@ -174,7 +169,9 @@ export function coerceNumericPair( right: b, resultType: forceFloat ? "float" - : ((lt === "float" || rt === "float") ? "float" : "int"), + : lt === "float" || rt === "float" + ? "float" + : "int", }; } diff --git a/client/space_lua/parse-lua.js b/client/space_lua/parse-lua.js index bd44f2ce..c7994882 100644 --- a/client/space_lua/parse-lua.js +++ b/client/space_lua/parse-lua.js @@ -1,22 +1,89 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. -import {LRParser} from "@lezer/lr" -const spec_identifier = {__proto__:null,break:16, goto:20, do:24, end:26, while:30, nil:32, true:34, false:36, or:80, and:82, not:104, function:114, query:122, from:129, where:133, order:137, by:139, desc:143, asc:145, using:146, nulls:149, first:151, last:153, select:157, limit:161, group:165, having:169, filter:173, repeat:176, until:178, if:182, then:184, elseif:186, else:188, for:192, in:200, local:210, return:226} +import { LRParser } from "@lezer/lr"; +const spec_identifier = { + __proto__: null, + break: 16, + goto: 20, + do: 24, + end: 26, + while: 30, + nil: 32, + true: 34, + false: 36, + or: 80, + and: 82, + not: 104, + function: 114, + query: 122, + from: 129, + where: 133, + order: 137, + by: 139, + desc: 143, + asc: 145, + using: 146, + nulls: 149, + first: 151, + last: 153, + select: 157, + limit: 161, + group: 165, + having: 169, + filter: 173, + repeat: 176, + until: 178, + if: 182, + then: 184, + elseif: 186, + else: 188, + for: 192, + in: 200, + local: 210, + return: 226, +}; export const parser = LRParser.deserialize({ version: 14, - states: "HzO!ZQPOOOOQO'#Cc'#CcO!UQPO'#CaO!bQPOOOOQO'#FW'#FWO!vQQO'#CwO$]QPO'#FVOOQO'#FV'#FVO$gQPO'#FVOOQO'#Ek'#EkO%vQPO'#EjOOQO'#FR'#FROOQO'#Er'#ErO%{QPO'#C_OOQO'#C_'#C_QOQPOOO!UQPO'#CeO&`QPO'#CgO!vQQO'#CjO&gQPO'#EVO!vQQO'#EYO!UQPO'#E_O!UQPO'#EfO&nQPO'#ElO&vQQO'#EpO'^QPO,58{OOQO'#Cq'#CqO!UQPO,59^O!vQQO,59`O(gQQO'#C|O(nQQO'#F]OOQO'#FX'#FXOOQO,59f,59fO!UQPO,59fO(uQPO'#FTO,eQPO,59cO,lQPO'#FVOOQO'#Dc'#DcOOQO'#Dd'#DdOOQO'#De'#DeO!vQQO'#DaOOQO'#FT'#FTO/ZQPO'#DfO/`QSO'#DjO/eQPO'#E{O/mQPO,5<]O!vQQO,5;UOOQO-E8p-E8pOOQO,58y,58yOOQO,59P,59PO/uQPO,59RO/zQPO,59UO0RQPO,5:qO0WQPO,5:tO0_QPO'#FpOOQO'#Ec'#EcO0jQPO'#EbO0oQPO,5:yO0tQPO'#EgO/ZQPO,5;QO1PQQO'#EnO2gQPO'#FrOOQO'#Em'#EmO!UQPO,5;SO3zQPO,5;WO5[QPO'#F^OOQO'#Ee'#EeOOQO,5;[,5;[O6qQPO,5;[OOQO1G.g1G.gOOQO1G.x1G.xO7VQPO1G.zO7^QWO'#FTO:jQWO'#FWO!vQQO'#DOO;{QSO'#DQOOQO'#FY'#FYOOQO,59h,59hOWQPO,5;wO>]QPO1G/QOOQO1G.}1G.}OOQO'#DX'#DXOOQO'#DY'#DYOOQO'#DZ'#DZOOQO'#D['#D[OOQO'#D^'#D^OOQO'#D_'#D_OOQO'#D`'#D`O!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO>hQPO,5:oO?tQPO,59{OA^QQO'#DhOOQO,5:Q,5:QOBTQQO,5:UOB[QPO,5;gOOQO-E8y-E8yOOQO1G0p1G0pOOQO1G.m1G.mO&`QPO1G.pO!vQQO1G0]OBfQPO1G0`O!vQQO,5:{O!UQPO'#EyOBsQPO,5<[O!vQQO,5:|O&`QPO1G0eO!UQPO'#EzOB{QPO,5;RO!UQPO,5;ROOQO1G0l1G0lO!UQPO'#EoOOQO,5;Y,5;YO!UQPO'#E|OCWQPO,5<^O/ZQPO1G0nO!vQQO1G0rO!vQQO'#EtODkQPO,5;xOOQO1G0v1G0vOOQO7+$f7+$fOFOQQO,59kOGPQPO,59jOGWQQO1G/SOG_QSO1G/SOOQO1G/S1G/SOITQSO,59{OOQO1G1c1G1cOOQO7+$l7+$lOKwQPO1G/ZOLOQPO1G/ZONoQPO1G/ZONvQPO1G/ZO!#aQPO1G/ZO!#nQPO1G/ZO!%oQPO1G/ZO!)UQPO1G/ZO!)]QPO1G/ZO!)dQPO1G0ZO!)iQPO'#FVO!)sQPO'#FlOOQO'#Di'#DiO!){QPO,5:SOOQO'#Dl'#DlOOQO'#Ev'#EvO!*QQQO1G/pOFOQQO'#DmOFOQQO'#DoO!*XQPO'#DqOFOQQO'#D{O!*^QQO'#D}O!+_QPO'#EPOFOQQO'#EROOQO1G/p1G/pO!+dQPO7+$[O!+iQPO7+%wO!,xQPO7+%zO!-TQPO1G0gOOQO,5;e,5;eOOQO-E8w-E8wOOQO1G0h1G0hO!-[QPO7+&POOQO,5;f,5;fOOQO-E8x-E8xO!UQPO1G0mOOQO1G0m1G0mO!-aQQO,5;ZOOQO,5;h,5;hOOQO-E8z-E8zOOQO7+&Y7+&YOOQO7+&^7+&^O!-fQPO,5;`OOQO-E8r-E8rO!.{QSO1G/VO!/VQPO1G/UO!0{QSO1G/ZO!1SQSO1G/ZO!2zQSO1G/ZO!3RQSO1G/ZO!4sQSO1G/ZO!5QQSO1G/ZO!6{QSO1G/ZO!8vQSO1G/ZO!8}QSO1G/ZOOQO,5;_,5;_OOQO7+$n7+$nO!9UQQO7+$nOOQO-E8q-E8qO!vQQO7+%uO!9]QQO'#EuO!9hQPO,5WQ`O'#FVO!A^Q`O,5:iO!*^QQO,5:kO!A}QSO,5:mOOQO<{AN>{OOQO<lAN>lOFOQQO'#DaO!*^QQO'#DaOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oO!*^QQO'#Et", - stateData: "#(_~O#sOS#tOSPOS~OSZOUQOWZOY`O[aO_bOlTO!ZfO!zcO!}dO#SeO#]gO#ehO#vPO~O#qRP~P]OgkOilOlnOoqOqmO#xjO~O`yOayObyOcyOdyOlTOqmO!UxO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Og#yXi#yXl#yXo#yXq#yX#x#yX~Ov|O#}$eX~P#wOS#uXU#uXW#uXY#uX[#uX_#uXl#uX!Z#uX!z#uX!}#uX#S#uX#]#uX#e#uX#q#uX#v#uX]#uX!{#uX#P#uX#Q#uX~P#wO#}!OO~O#qRX]RX!{RX#PRX#QRX~P]O]RP~P]O!{RP~P]O!Z!aO#vPO~OS!eO#q#dX]#dX!{#dX#P#dX#Q#dX~P!vOU!gO~O`yOayObyOcyOdyOi!lOlTOqmO!U'XO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Ou!oO~P'cOm!rO~P!vOm#wXx#wXy#wXz#wX!P#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX[#wX#O#wXS#wXv#wX#q#wXj#wXU#wXW#wXY#wX_#wX!Z#wX!z#wX!}#wX#S#wX#]#wX#e#wX#v#wX]#wX!{#wX#P#wX#Q#wX~P!bOx#UOy#VOz!}O!P#RO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~Om!uO~P+aO!x#WOm#yXx#yXy#yXz#yX!P#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX[#yX#O#yXS#yXv#yX#q#yXj#yXU#yXW#yXY#yX_#yX!Z#yX!z#yX!}#yX#S#yX#]#yX#e#yX#v#yX]#yX!{#yX#P#yX#Q#yX~P#wOl#YO~O$a#[O~OlTO#vPO~Ov|O#}$ea~O]#`O~O[#aO~P+aO!{#bO~O#O#cO~P+aOv#eO#}#dO#W$dX~O#W#gO~O[#hO~Og#iOo#kOl#ZX~O$g#mOS#cPU#cPW#cPY#cP[#cP_#cPl#cPv#cP!Z#cP!z#cP!}#cP#S#cP#]#cP#e#cP#q#cP#v#cP#}#cP]#cP!{#cP#P#cP#Q#cP~Ov#oOS$fXU$fXW$fXY$fX[$fX_$fXl$fX!Z$fX!z$fX!}$fX#S$fX#]$fX#e$fX#q$fX#v$fX#}$fX]$fX!{$fX#P$fX#Q$fX~O#}#rOS#`aU#`aW#`aY#`a[#`a_#`al#`a!Z#`a!z#`a!}#`a#S#`a#]#`a#e#`a#q#`a#v#`a]#`a!{#`a#P#`a#Q#`a~Ov#sOS$QX#q$QXm$QXU$QXW$QXY$QX[$QX_$QXl$QX!Z$QX!z$QX!}$QX#S$QX#]$QX#e$QX#v$QX]$QX!{$QX#P$QX#Q$QX~P+aOS#uO#q#da]#da!{#da#P#da#Q#da~Oj#vO~P+aOu#wXx#wXy#wXz#wX!P#wX$O#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX!b#wX!d#wX!f#wX!p#wX!r#wX!t#wX!v#wX$c#wX~P!bOg#zXi#zXl#zXo#zXq#zXx#zXy#zXz#zX!P#zX#x#zX$R#zX$S#zX$T#zX$U#zX$V#zX$W#zX$X#zX$Y#zX$Z#zX$[#zX$]#zX$^#zX~O#}#wOu#zX$O#zX~P9SOx'iOy'kOz'ZO!P'cO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~OutX$OtX~P:wOu#{O$O#yO~O!x#WOu#yXx#yXy#yXz#yX!P#yX$O#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX!b#yX!d#yX!f#yX!p#yX!r#yX!t#yX!v#yX$c#yX~P#wOm#}O~OlnOqmO#xjO~Ol$YO~O$^!|Ox!Tay!Taz!Ta!P!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta$]!Tav!Ta~Om!Ta[!Ta#O!TaS!Ta#q!Taj!TaU!TaW!TaY!Ta_!Tal!Ta!Z!Ta!z!Ta!}!Ta#S!Ta#]!Ta#e!Ta#v!Ta]!Ta!{!Ta#P!Ta#Q!Ta~P>mOc$[OlTO#vPOm!]P~O!b$bO!d$cO!f$dO!p$eO!r$fO!t$gO!v$hO~O$c$iO~PAlOv#oa#}#oa~P#wO]RP#PRP#QRP~P]Ov#eO#W$da~Og#iOo$tOl#Za~Ov#oOS$faU$faW$faY$fa[$fa_$fal$fa!Z$fa!z$fa!}$fa#S$fa#]$fa#e$fa#q$fa#v$fa#}$fa]$fa!{$fa#P$fa#Q$fa~Ov#sOS$Qa#q$Qam$QaU$QaW$QaY$Qa[$Qa_$Qal$Qa!Z$Qa!z$Qa!}$Qa#S$Qa#]$Qa#e$Qa#v$Qa]$Qa!{$Qa#P$Qa#Q$Qa~O`yOayObyOcyOdyOlTOqmO!U'XO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Oj%OO~P+aOu%ZO~P'cOu%ZO$O%[O~O$^!|Ox!Tay!Taz!Ta!P!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta$]!Ta!b!Ta!d!Ta!f!Ta!p!Ta!r!Ta!t!Ta!v!Ta$c!Ta~Ou!Ta$O!Ta~PGgO!P#RO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O$R!vO~PI_O$Rwi~PI_O!P#RO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi$Rwi$Twi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O$Swi~PLVO$S!wO~PLVO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi$Rwi$Swi$Twi$Uwi$Vwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O!P#RO$W!zO$X!zO~PN}O!Pwi$Wwi$Xwi~PN}O$^!|Omwixwiywi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~Ozwi!Pwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi$]wi~P!#{Oz!}O!P#RO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~Oy#VO~P!&lOywi~P!&lO!d%^O~Ov%_Om$`X~P#wOv%_Om$`X~Om%aO~O$c%cO~PAlO!g%gO~O`yOayObyOcyOdyOlTOqmO!U'YO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~O!g%lO~O]%nO~OS!yqU!yqW!yqY!yq[!yq_!yql!yq!Z!yq!z!yq!}!yq#S!yq#]!yq#e!yq#q!yq#v!yq]!yq!{!yq#P!yq#Q!yq~P+aO]%qO#P%pO#Q%rO~Ov%sO~P+aO]%tO~O$h%vO~OS#hav#ha#q#ham#haU#haW#haY#ha[#ha_#hal#ha!Z#ha!z#ha!}#ha#S#ha#]#ha#e#ha#v#ha]#ha!{#ha#P#ha#Q#ha~P+aOusi$Osi~P:wO#}%wO~O!P'cO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O$R!vO~P!/[O$Rwi~P!/[O!P'cO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi$Rwi$Twi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O$Swi~P!1ZO$S!wO~P!1ZO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi$Rwi$Swi$Twi$Uwi$Vwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O!P'cO$W!zO$X!zO~P!3YO!Pwi$Wwi$Xwi~P!3YO$^!|Oxwiywizwi!Pwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi$]wi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~Ouwi$Owi~P!5_Oz'ZO!P'cO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwi$Owi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~Oy'kO~P!7VOywi~P!7VOu%xO~P'cOc%|OlTO#vPO~Ov%_Om$`a~O#}&PO!b#zX!d#zX!f#zX!p#zX!r#zX!t#zX!v#zX$c#zX~P9SO!b!aa!d!aa!f!aa!p!aa!r!aa!t!aa!v!aa$c!aa~P:wO!b!ca!d!ca!f!ca!p!ca!r!ca!t!ca!v!ca$c!ca~P:wO!b!oa!d!oa!f!oa!p!oa!r!oa!t!oa!v!oa$c!oa~P:wOv#wXx#wXy#wXz#wX!P#wX!b#wX!d#wX!f#wX!p#wX!r#wX!t#wX!v#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX$c#wX!i#wX!j#wX!k#wX!l#wX~P!bO!x#WOv#yXx#yXy#yXz#yX!P#yX!b#yX!d#yX!f#yX!p#yX!r#yX!t#yX!v#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX$c#yX!i#yX!j#yX!k#yX!l#yX~P#wOx'jOy'lOz'[O!P'dO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~Ov&TO!b!qa!d!qa!f!qa!p!qa!r!qa!t!qa!v!qa$c!qa~P!@YO!b!ua!d!ua!f!ua!p!ua!r!ua!t!ua!v!ua$c!ua~P:wO]&YO#P%pO#Q&ZO~Om&`O~P+aOm#iav#ia~P#wO]&aO~O!i&dO!j&dO!k&eO!l&cOv!hX!b!hX!d!hX!f!hX!p!hX!r!hX!t!hX!v!hX$c!hX~P!@YOv&fO!b$bX!d$bX!f$bX!p$bX!r$bX!t$bX!v$bX$c$bX~Ov!Ta!i!Ta!j!Ta!k!Ta!l!Ta~PGgOv'mO!b$QX!d$QX!f$QX!p$QX!r$QX!t$QX!v$QX$c$QX~P!@YO#O&tO~P+aO]&uO~Ov&vO[#Ty~P+aOury$Ory~P:wO!b!aq!d!aq!f!aq!p!aq!r!aq!t!aq!v!aq$c!aq~P:wO!m&wO!n&wO~O!l&xOv!ha!b!ha!d!ha!f!ha!p!ha!r!ha!t!ha!v!ha$c!ha~O!Z&zO#vPO~Ov&fO!b$ba!d$ba!f$ba!p$ba!r$ba!t$ba!v$ba$c$ba~O!P'dO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi!iwi!jwi!kwi!lwi~O$R!vO~P!H{O$Rwi~P!H{O!P'dO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$Rwi$Twi$cwi!iwi!jwi!kwi!lwi~O$Swi~P!KTO$S!wO~P!KTO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$Rwi$Swi$Twi$Uwi$Vwi$cwi!iwi!jwi!kwi!lwi~O!P'dO$W!zO$X!zO~P!M]O!Pwi$Wwi$Xwi~P!M]Ovwi!iwi!jwi!kwi!lwi~P!5_Oz'[O!P'dO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi!iwi!jwi!kwi!lwi~Oy'lO~P#!OOywi~P#!OO!b!qq!d!qq!f!qq!p!qq!r!qq!t!qq!v!qq$c!qq~P:wOv'mO!b$Qa!d$Qa!f$Qa!p$Qa!r$Qa!t$Qa!v$Qa$c$Qa~O]'OO~O!m'RO!n'RO~O!l'SOv!hi!b!hi!d!hi!f!hi!p!hi!r!hi!t!hi!v!hi$c!hi~Ov#ha!b#ha!d#ha!f#ha!p#ha!r#ha!t#ha!v#ha$c#ha~P!@YO[#T!Z~P+aO!m'UO!n'UO~O!l'VOv!hq!b!hq!d!hq!f!hq!p!hq!r!hq!t!hq!v!hq$c!hq~O!m'WO!n'WO~OP$X~", + states: + "HzO!ZQPOOOOQO'#Cc'#CcO!UQPO'#CaO!bQPOOOOQO'#FW'#FWO!vQQO'#CwO$]QPO'#FVOOQO'#FV'#FVO$gQPO'#FVOOQO'#Ek'#EkO%vQPO'#EjOOQO'#FR'#FROOQO'#Er'#ErO%{QPO'#C_OOQO'#C_'#C_QOQPOOO!UQPO'#CeO&`QPO'#CgO!vQQO'#CjO&gQPO'#EVO!vQQO'#EYO!UQPO'#E_O!UQPO'#EfO&nQPO'#ElO&vQQO'#EpO'^QPO,58{OOQO'#Cq'#CqO!UQPO,59^O!vQQO,59`O(gQQO'#C|O(nQQO'#F]OOQO'#FX'#FXOOQO,59f,59fO!UQPO,59fO(uQPO'#FTO,eQPO,59cO,lQPO'#FVOOQO'#Dc'#DcOOQO'#Dd'#DdOOQO'#De'#DeO!vQQO'#DaOOQO'#FT'#FTO/ZQPO'#DfO/`QSO'#DjO/eQPO'#E{O/mQPO,5<]O!vQQO,5;UOOQO-E8p-E8pOOQO,58y,58yOOQO,59P,59PO/uQPO,59RO/zQPO,59UO0RQPO,5:qO0WQPO,5:tO0_QPO'#FpOOQO'#Ec'#EcO0jQPO'#EbO0oQPO,5:yO0tQPO'#EgO/ZQPO,5;QO1PQQO'#EnO2gQPO'#FrOOQO'#Em'#EmO!UQPO,5;SO3zQPO,5;WO5[QPO'#F^OOQO'#Ee'#EeOOQO,5;[,5;[O6qQPO,5;[OOQO1G.g1G.gOOQO1G.x1G.xO7VQPO1G.zO7^QWO'#FTO:jQWO'#FWO!vQQO'#DOO;{QSO'#DQOOQO'#FY'#FYOOQO,59h,59hOWQPO,5;wO>]QPO1G/QOOQO1G.}1G.}OOQO'#DX'#DXOOQO'#DY'#DYOOQO'#DZ'#DZOOQO'#D['#D[OOQO'#D^'#D^OOQO'#D_'#D_OOQO'#D`'#D`O!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO!vQQO,59oO>hQPO,5:oO?tQPO,59{OA^QQO'#DhOOQO,5:Q,5:QOBTQQO,5:UOB[QPO,5;gOOQO-E8y-E8yOOQO1G0p1G0pOOQO1G.m1G.mO&`QPO1G.pO!vQQO1G0]OBfQPO1G0`O!vQQO,5:{O!UQPO'#EyOBsQPO,5<[O!vQQO,5:|O&`QPO1G0eO!UQPO'#EzOB{QPO,5;RO!UQPO,5;ROOQO1G0l1G0lO!UQPO'#EoOOQO,5;Y,5;YO!UQPO'#E|OCWQPO,5<^O/ZQPO1G0nO!vQQO1G0rO!vQQO'#EtODkQPO,5;xOOQO1G0v1G0vOOQO7+$f7+$fOFOQQO,59kOGPQPO,59jOGWQQO1G/SOG_QSO1G/SOOQO1G/S1G/SOITQSO,59{OOQO1G1c1G1cOOQO7+$l7+$lOKwQPO1G/ZOLOQPO1G/ZONoQPO1G/ZONvQPO1G/ZO!#aQPO1G/ZO!#nQPO1G/ZO!%oQPO1G/ZO!)UQPO1G/ZO!)]QPO1G/ZO!)dQPO1G0ZO!)iQPO'#FVO!)sQPO'#FlOOQO'#Di'#DiO!){QPO,5:SOOQO'#Dl'#DlOOQO'#Ev'#EvO!*QQQO1G/pOFOQQO'#DmOFOQQO'#DoO!*XQPO'#DqOFOQQO'#D{O!*^QQO'#D}O!+_QPO'#EPOFOQQO'#EROOQO1G/p1G/pO!+dQPO7+$[O!+iQPO7+%wO!,xQPO7+%zO!-TQPO1G0gOOQO,5;e,5;eOOQO-E8w-E8wOOQO1G0h1G0hO!-[QPO7+&POOQO,5;f,5;fOOQO-E8x-E8xO!UQPO1G0mOOQO1G0m1G0mO!-aQQO,5;ZOOQO,5;h,5;hOOQO-E8z-E8zOOQO7+&Y7+&YOOQO7+&^7+&^O!-fQPO,5;`OOQO-E8r-E8rO!.{QSO1G/VO!/VQPO1G/UO!0{QSO1G/ZO!1SQSO1G/ZO!2zQSO1G/ZO!3RQSO1G/ZO!4sQSO1G/ZO!5QQSO1G/ZO!6{QSO1G/ZO!8vQSO1G/ZO!8}QSO1G/ZOOQO,5;_,5;_OOQO7+$n7+$nO!9UQQO7+$nOOQO-E8q-E8qO!vQQO7+%uO!9]QQO'#EuO!9hQPO,5WQ`O'#FVO!A^Q`O,5:iO!*^QQO,5:kO!A}QSO,5:mOOQO<{AN>{OOQO<lAN>lOFOQQO'#DaO!*^QQO'#DaOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oOFOQQO,59oO!*^QQO,59oO!*^QQO'#Et", + stateData: + "#(_~O#sOS#tOSPOS~OSZOUQOWZOY`O[aO_bOlTO!ZfO!zcO!}dO#SeO#]gO#ehO#vPO~O#qRP~P]OgkOilOlnOoqOqmO#xjO~O`yOayObyOcyOdyOlTOqmO!UxO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Og#yXi#yXl#yXo#yXq#yX#x#yX~Ov|O#}$eX~P#wOS#uXU#uXW#uXY#uX[#uX_#uXl#uX!Z#uX!z#uX!}#uX#S#uX#]#uX#e#uX#q#uX#v#uX]#uX!{#uX#P#uX#Q#uX~P#wO#}!OO~O#qRX]RX!{RX#PRX#QRX~P]O]RP~P]O!{RP~P]O!Z!aO#vPO~OS!eO#q#dX]#dX!{#dX#P#dX#Q#dX~P!vOU!gO~O`yOayObyOcyOdyOi!lOlTOqmO!U'XO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Ou!oO~P'cOm!rO~P!vOm#wXx#wXy#wXz#wX!P#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX[#wX#O#wXS#wXv#wX#q#wXj#wXU#wXW#wXY#wX_#wX!Z#wX!z#wX!}#wX#S#wX#]#wX#e#wX#v#wX]#wX!{#wX#P#wX#Q#wX~P!bOx#UOy#VOz!}O!P#RO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~Om!uO~P+aO!x#WOm#yXx#yXy#yXz#yX!P#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX[#yX#O#yXS#yXv#yX#q#yXj#yXU#yXW#yXY#yX_#yX!Z#yX!z#yX!}#yX#S#yX#]#yX#e#yX#v#yX]#yX!{#yX#P#yX#Q#yX~P#wOl#YO~O$a#[O~OlTO#vPO~Ov|O#}$ea~O]#`O~O[#aO~P+aO!{#bO~O#O#cO~P+aOv#eO#}#dO#W$dX~O#W#gO~O[#hO~Og#iOo#kOl#ZX~O$g#mOS#cPU#cPW#cPY#cP[#cP_#cPl#cPv#cP!Z#cP!z#cP!}#cP#S#cP#]#cP#e#cP#q#cP#v#cP#}#cP]#cP!{#cP#P#cP#Q#cP~Ov#oOS$fXU$fXW$fXY$fX[$fX_$fXl$fX!Z$fX!z$fX!}$fX#S$fX#]$fX#e$fX#q$fX#v$fX#}$fX]$fX!{$fX#P$fX#Q$fX~O#}#rOS#`aU#`aW#`aY#`a[#`a_#`al#`a!Z#`a!z#`a!}#`a#S#`a#]#`a#e#`a#q#`a#v#`a]#`a!{#`a#P#`a#Q#`a~Ov#sOS$QX#q$QXm$QXU$QXW$QXY$QX[$QX_$QXl$QX!Z$QX!z$QX!}$QX#S$QX#]$QX#e$QX#v$QX]$QX!{$QX#P$QX#Q$QX~P+aOS#uO#q#da]#da!{#da#P#da#Q#da~Oj#vO~P+aOu#wXx#wXy#wXz#wX!P#wX$O#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX!b#wX!d#wX!f#wX!p#wX!r#wX!t#wX!v#wX$c#wX~P!bOg#zXi#zXl#zXo#zXq#zXx#zXy#zXz#zX!P#zX#x#zX$R#zX$S#zX$T#zX$U#zX$V#zX$W#zX$X#zX$Y#zX$Z#zX$[#zX$]#zX$^#zX~O#}#wOu#zX$O#zX~P9SOx'iOy'kOz'ZO!P'cO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~OutX$OtX~P:wOu#{O$O#yO~O!x#WOu#yXx#yXy#yXz#yX!P#yX$O#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX!b#yX!d#yX!f#yX!p#yX!r#yX!t#yX!v#yX$c#yX~P#wOm#}O~OlnOqmO#xjO~Ol$YO~O$^!|Ox!Tay!Taz!Ta!P!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta$]!Tav!Ta~Om!Ta[!Ta#O!TaS!Ta#q!Taj!TaU!TaW!TaY!Ta_!Tal!Ta!Z!Ta!z!Ta!}!Ta#S!Ta#]!Ta#e!Ta#v!Ta]!Ta!{!Ta#P!Ta#Q!Ta~P>mOc$[OlTO#vPOm!]P~O!b$bO!d$cO!f$dO!p$eO!r$fO!t$gO!v$hO~O$c$iO~PAlOv#oa#}#oa~P#wO]RP#PRP#QRP~P]Ov#eO#W$da~Og#iOo$tOl#Za~Ov#oOS$faU$faW$faY$fa[$fa_$fal$fa!Z$fa!z$fa!}$fa#S$fa#]$fa#e$fa#q$fa#v$fa#}$fa]$fa!{$fa#P$fa#Q$fa~Ov#sOS$Qa#q$Qam$QaU$QaW$QaY$Qa[$Qa_$Qal$Qa!Z$Qa!z$Qa!}$Qa#S$Qa#]$Qa#e$Qa#v$Qa]$Qa!{$Qa#P$Qa#Q$Qa~O`yOayObyOcyOdyOlTOqmO!U'XO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~Oj%OO~P+aOu%ZO~P'cOu%ZO$O%[O~O$^!|Ox!Tay!Taz!Ta!P!Ta$R!Ta$S!Ta$T!Ta$U!Ta$V!Ta$W!Ta$X!Ta$Y!Ta$Z!Ta$[!Ta$]!Ta!b!Ta!d!Ta!f!Ta!p!Ta!r!Ta!t!Ta!v!Ta$c!Ta~Ou!Ta$O!Ta~PGgO!P#RO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O$R!vO~PI_O$Rwi~PI_O!P#RO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi$Rwi$Twi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O$Swi~PLVO$S!wO~PLVO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwiywizwi$Rwi$Swi$Twi$Uwi$Vwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~O!P#RO$W!zO$X!zO~PN}O!Pwi$Wwi$Xwi~PN}O$^!|Omwixwiywi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~Ozwi!Pwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi$]wi~P!#{Oz!}O!P#RO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Omwixwi[wi#OwiSwivwi#qwijwiUwiWwiYwi_wilwi!Zwi!zwi!}wi#Swi#]wi#ewi#vwi]wi!{wi#Pwi#Qwi~Oy#VO~P!&lOywi~P!&lO!d%^O~Ov%_Om$`X~P#wOv%_Om$`X~Om%aO~O$c%cO~PAlO!g%gO~O`yOayObyOcyOdyOlTOqmO!U'YO!ZzO!_{O#vPO#xjO$TvO$XuO$_wO~O!g%lO~O]%nO~OS!yqU!yqW!yqY!yq[!yq_!yql!yq!Z!yq!z!yq!}!yq#S!yq#]!yq#e!yq#q!yq#v!yq]!yq!{!yq#P!yq#Q!yq~P+aO]%qO#P%pO#Q%rO~Ov%sO~P+aO]%tO~O$h%vO~OS#hav#ha#q#ham#haU#haW#haY#ha[#ha_#hal#ha!Z#ha!z#ha!}#ha#S#ha#]#ha#e#ha#v#ha]#ha!{#ha#P#ha#Q#ha~P+aOusi$Osi~P:wO#}%wO~O!P'cO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O$R!vO~P!/[O$Rwi~P!/[O!P'cO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi$Rwi$Twi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O$Swi~P!1ZO$S!wO~P!1ZO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwiywizwi$Owi$Rwi$Swi$Twi$Uwi$Vwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~O!P'cO$W!zO$X!zO~P!3YO!Pwi$Wwi$Xwi~P!3YO$^!|Oxwiywizwi!Pwi$Rwi$Swi$Twi$Uwi$Vwi$Wwi$Xwi$Ywi$Zwi$[wi$]wi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~Ouwi$Owi~P!5_Oz'ZO!P'cO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ouwixwi$Owi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi~Oy'kO~P!7VOywi~P!7VOu%xO~P'cOc%|OlTO#vPO~Ov%_Om$`a~O#}&PO!b#zX!d#zX!f#zX!p#zX!r#zX!t#zX!v#zX$c#zX~P9SO!b!aa!d!aa!f!aa!p!aa!r!aa!t!aa!v!aa$c!aa~P:wO!b!ca!d!ca!f!ca!p!ca!r!ca!t!ca!v!ca$c!ca~P:wO!b!oa!d!oa!f!oa!p!oa!r!oa!t!oa!v!oa$c!oa~P:wOv#wXx#wXy#wXz#wX!P#wX!b#wX!d#wX!f#wX!p#wX!r#wX!t#wX!v#wX$R#wX$S#wX$T#wX$U#wX$V#wX$W#wX$X#wX$Y#wX$Z#wX$[#wX$]#wX$^#wX$c#wX!i#wX!j#wX!k#wX!l#wX~P!bO!x#WOv#yXx#yXy#yXz#yX!P#yX!b#yX!d#yX!f#yX!p#yX!r#yX!t#yX!v#yX$R#yX$S#yX$T#yX$U#yX$V#yX$W#yX$X#yX$Y#yX$Z#yX$[#yX$]#yX$^#yX$c#yX!i#yX!j#yX!k#yX!l#yX~P#wOx'jOy'lOz'[O!P'dO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|O~Ov&TO!b!qa!d!qa!f!qa!p!qa!r!qa!t!qa!v!qa$c!qa~P!@YO!b!ua!d!ua!f!ua!p!ua!r!ua!t!ua!v!ua$c!ua~P:wO]&YO#P%pO#Q&ZO~Om&`O~P+aOm#iav#ia~P#wO]&aO~O!i&dO!j&dO!k&eO!l&cOv!hX!b!hX!d!hX!f!hX!p!hX!r!hX!t!hX!v!hX$c!hX~P!@YOv&fO!b$bX!d$bX!f$bX!p$bX!r$bX!t$bX!v$bX$c$bX~Ov!Ta!i!Ta!j!Ta!k!Ta!l!Ta~PGgOv'mO!b$QX!d$QX!f$QX!p$QX!r$QX!t$QX!v$QX$c$QX~P!@YO#O&tO~P+aO]&uO~Ov&vO[#Ty~P+aOury$Ory~P:wO!b!aq!d!aq!f!aq!p!aq!r!aq!t!aq!v!aq$c!aq~P:wO!m&wO!n&wO~O!l&xOv!ha!b!ha!d!ha!f!ha!p!ha!r!ha!t!ha!v!ha$c!ha~O!Z&zO#vPO~Ov&fO!b$ba!d$ba!f$ba!p$ba!r$ba!t$ba!v$ba$c$ba~O!P'dO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi!iwi!jwi!kwi!lwi~O$R!vO~P!H{O$Rwi~P!H{O!P'dO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$Rwi$Twi$cwi!iwi!jwi!kwi!lwi~O$Swi~P!KTO$S!wO~P!KTO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwiywizwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$Rwi$Swi$Twi$Uwi$Vwi$cwi!iwi!jwi!kwi!lwi~O!P'dO$W!zO$X!zO~P!M]O!Pwi$Wwi$Xwi~P!M]Ovwi!iwi!jwi!kwi!lwi~P!5_Oz'[O!P'dO$R!vO$S!wO$T!xO$U!yO$V!yO$W!zO$X!zO$Y!{O$Z!{O$[!{O$]!{O$^!|Ovwixwi!bwi!dwi!fwi!pwi!rwi!twi!vwi$cwi!iwi!jwi!kwi!lwi~Oy'lO~P#!OOywi~P#!OO!b!qq!d!qq!f!qq!p!qq!r!qq!t!qq!v!qq$c!qq~P:wOv'mO!b$Qa!d$Qa!f$Qa!p$Qa!r$Qa!t$Qa!v$Qa$c$Qa~O]'OO~O!m'RO!n'RO~O!l'SOv!hi!b!hi!d!hi!f!hi!p!hi!r!hi!t!hi!v!hi$c!hi~Ov#ha!b#ha!d#ha!f#ha!p#ha!r#ha!t#ha!v#ha$c#ha~P!@YO[#T!Z~P+aO!m'UO!n'UO~O!l'VOv!hq!b!hq!d!hq!f!hq!p!hq!r!hq!t!hq!v!hq$c!hq~O!m'WO!n'WO~OP$X~", goto: "Ed$gPPP$hP%WP%eP%WP%WPP%WPPPPPP(Y)uP)uPP+iPP-]P(YP/X/X/XPP(aPPP/_0Y1Z2XP3]4d5n(aP7O7O7O(aP8h8t(aP8w8{P8{P8{PP9PPPPPPP8{P8{P8{P8{P(aP%WPP%WPPPP%WP9V9V9YP9]%W9i%WP%W9l%W9y9|:S:VP:f:u:{;V;^;d;j;p;v;|R#Q;'S=o;'S;=`>}<%lO=ok>UVO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kk>nTO#P=o#P#Q7z#Q;'S=o;'S;=`>}<%lO=ok?QP;=`<%l=ok?WTO#P?g#P#Q@P#Q;'S?g;'S;=`?y<%lO?gk?jTO#P?g#P#Q7z#Q;'S?g;'S;=`?y<%lO?gk?|P;=`<%l?gk@UP#xk!_!`8Qk@[VO!_=o!_!`@q!`#P=o#P#QAu#Q;'S=o;'S;=`>}<%lO=ok@tTO#P=o#P#QAT#Q;'S=o;'S;=`>}<%lO=okAYV#xkO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kkArP;=`<%l>kkAxVO!_?g!_!`?T!`#P?g#P#QAu#Q;'S?g;'S;=`?y<%lO?goBdT$aSO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskBvTO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskCYTO#PBs#P#Q%b#Q;'SBs;'S;=`Ci<%lOBskClP;=`<%lBsoCtPjP#P#QCwnC|O$cn~DRO$^~~DWOq~~D]O$R~~DbOu~~DgP$T~!_!`$m", + tokenData: + "Dj~RuXY#fYZ$Q[]#f]^$_pq#fqr$grs$rst)`uv)evw)jwx)oxy.Wyz.]z{.b{|.g|}.l}!O.s!O!P/g!P!Q/|!Q!R0^!R![1u![!]3m!]!^3z!^!_4R!_!`4e!`!a4m!c!}5P!}#O5b#O#P#w#P#QCo#Q#RC|#R#S5P#T#o5P#o#pDR#p#qDW#q#rD]#r#sDb~#kS#t~XY#f[]#fpq#f#O#P#w~#zQYZ#f]^#f~$VP#s~]^$Y~$_O#s~~$dP#s~YZ$Ym$jP!_!`$mm$rOzmk$uXOY$rZ]$r^r$rrs%bs#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk%gO#xkk%j]rs$rwx$r!Q![&c#O#P$r#T#U$r#U#V$r#Y#Z$r#b#c$r#f#g$r#h#i$r#i#j(T#l#m(v#n#o$rk&fZOY$rZ]$r^r$rrs%bs!Q$r!Q!['X![#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk'[ZOY$rZ]$r^r$rrs%bs!Q$r!Q![$r![#O$r#O#P%g#P;'S$r;'S;=`'}<%lO$rk(QP;=`<%l$rk(WP#o#p(Zk(^R!Q![(g!c!i(g#T#Z(gk(jS!Q![(g!c!i(g#T#Z(g#q#r$rk(yR!Q![)S!c!i)S#T#Z)Sk)VR!Q![$r!c!i$r#T#Z$r~)eO$_~~)jO$[~~)oO$S~k)rXOY)oZ])o^w)owx%bx#O)o#O#P*_#P;'S)o;'S;=`,u<%lO)ok*b]rs)owx)o!Q![+Z#O#P)o#T#U)o#U#V)o#Y#Z)o#b#c)o#f#g)o#h#i)o#i#j,{#l#m-n#n#o)ok+^ZOY)oZ])o^w)owx%bx!Q)o!Q![,P![#O)o#O#P*_#P;'S)o;'S;=`,u<%lO)ok,SZOY)oZ])o^w)owx%bx!Q)o!Q![)o![#O)o#O#P*_#P;'S)o;'S;=`,u<%lO)ok,xP;=`<%l)ok-OP#o#p-Rk-UR!Q![-_!c!i-_#T#Z-_k-bS!Q![-_!c!i-_#T#Z-_#q#r)ok-qR!Q![-z!c!i-z#T#Z-zk-}R!Q![)o!c!i)o#T#Z)o~.]Ol~~.bOm~~.gO$Y~~.lO$W~o.sOvc$O[~.xP$X~}!O.{~/QTP~OY.{Z].{^;'S.{;'S;=`/a<%lO.{~/dP;=`<%l.{o/lPgi!O!P/oo/tP!Pm!O!P/wQ/|OcQ~0RQ$Z~!P!Q0X!_!`$m~0^O$]~~0cUd~!O!P0u!Q![1u!g!h1Z!z!{2W#X#Y1Z#l#m2W~0xP!Q![0{~1QRd~!Q![0{!g!h1Z#X#Y1Z~1^R{|1g}!O1g!Q![1m~1jP!Q![1m~1rPd~!Q![1m~1zSd~!O!P0u!Q![1u!g!h1Z#X#Y1Z~2ZR!Q![2d!c!i2d#T#Z2d~2iUd~!O!P2{!Q![2d!c!i2d!r!s1Z#T#Z2d#d#e1Z~3OR!Q![3X!c!i3X#T#Z3X~3^Td~!Q![3X!c!i3X!r!s1Z#T#Z3X#d#e1Z~3rPo~![!]3u~3zOU~_4ROSR$O[o4YQ$gQzm!^!_4`!_!`$mm4eO$Um~4jP#}~!_!`$mo4tQ$hQzm!_!`$m!`!a4zm5PO$Vm~5US#v~!Q![5P!c!}5P#R#S5P#T#o5Po5gQik!_!`5m!}#OB_k5pQ!_!`5v!}#O=ok5yP!}#O5|k6PTO#P5|#P#Q6`#Q;'S5|;'S;=`8W<%lO5|k6cVO!_6x!_!`:s!`#P6x#P#Q;w#Q;'S6x;'S;=`=i<%lO6xk6{VO!_7b!_!`8^!`#P7b#P#Q8v#Q;'S7b;'S;=`=c<%lO7bk7eTO#P5|#P#Q7t#Q;'S5|;'S;=`8W<%lO5|k7wP!_!`7zk7}P!_!`8Qk8TP#P#Q%bk8ZP;=`<%l5|k8aTO#P8^#P#Q7t#Q;'S8^;'S;=`8p<%lO8^k8sP;=`<%l8^k8yUO!_5|!_!`9]!`#P5|#Q;'S5|;'S;=`8W<%lO5|k9`VO!_5|!_!`9u!`#P5|#P#Q6`#Q;'S5|;'S;=`8W<%lO5|k9xTO#P5|#P#Q:X#Q;'S5|;'S;=`8W<%lO5|k:^V#xkO!_6x!_!`:s!`#P6x#P#Q;w#Q;'S6x;'S;=`=i<%lO6xk:vVO!_8^!_!`;]!`#P8^#P#Q7t#Q;'S8^;'S;=`8p<%lO8^k;`TO#P8^#P#Q;o#Q;'S8^;'S;=`8p<%lO8^k;tP#xk!_!`7zk;zVO!_7b!_!`:s!`#P7b#P#QR#Q;'S=o;'S;=`>}<%lO=ok>UVO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kk>nTO#P=o#P#Q7z#Q;'S=o;'S;=`>}<%lO=ok?QP;=`<%l=ok?WTO#P?g#P#Q@P#Q;'S?g;'S;=`?y<%lO?gk?jTO#P?g#P#Q7z#Q;'S?g;'S;=`?y<%lO?gk?|P;=`<%l?gk@UP#xk!_!`8Qk@[VO!_=o!_!`@q!`#P=o#P#QAu#Q;'S=o;'S;=`>}<%lO=ok@tTO#P=o#P#QAT#Q;'S=o;'S;=`>}<%lO=okAYV#xkO!_>k!_!`?T!`#P>k#P#Q@X#Q;'S>k;'S;=`Ao<%lO>kkArP;=`<%l>kkAxVO!_?g!_!`?T!`#P?g#P#QAu#Q;'S?g;'S;=`?y<%lO?goBdT$aSO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskBvTO#PBs#P#QCV#Q;'SBs;'S;=`Ci<%lOBskCYTO#PBs#P#Q%b#Q;'SBs;'S;=`Ci<%lOBskClP;=`<%lBsoCtPjP#P#QCwnC|O$cn~DRO$^~~DWOq~~D]O$R~~DbOu~~DgP$T~!_!`$m", tokenizers: [0, 1, 2, 3, 4], - topRules: {"Chunk":[0,2]}, - dynamicPrecedences: {"139":1}, - specialized: [{term: 130, get: (value) => spec_identifier[value] || -1}], - tokenPrec: 4566 -}) + topRules: { Chunk: [0, 2] }, + dynamicPrecedences: { 139: 1 }, + specialized: [{ term: 130, get: (value) => spec_identifier[value] || -1 }], + tokenPrec: 4566, +}); diff --git a/client/space_lua/parse-lua.terms.js b/client/space_lua/parse-lua.terms.js index ed589dea..b3b1745c 100644 --- a/client/space_lua/parse-lua.terms.js +++ b/client/space_lua/parse-lua.terms.js @@ -1,6 +1,5 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. -export const - Comment = 1, +export const Comment = 1, Chunk = 2, Block = 3, Label = 5, @@ -35,4 +34,4 @@ export const AttNameList = 109, AttName = 110, Attrib = 111, - ReturnStatement = 112 + ReturnStatement = 112; diff --git a/client/space_lua/parse.test.ts b/client/space_lua/parse.test.ts index f6279b25..e2fb2efe 100644 --- a/client/space_lua/parse.test.ts +++ b/client/space_lua/parse.test.ts @@ -60,9 +60,7 @@ test("Test Lua parser", () => { goto hello`); parse(`while true do print() end`); parse(`repeat print() until false`); - parse( - `if 1 == 2 then print() elseif 1 < 2 then print2() else print3() end`, - ); + parse(`if 1 == 2 then print() elseif 1 < 2 then print2() else print3() end`); parse(`if true then print() end`); parse(`if true then print() else print2() end`); parse(`if true then print() elseif false then print2() end`); @@ -116,9 +114,11 @@ hello const code2 = stripLuaComments(code); expect(code2.length).toEqual(code.length); console.log(code2); - console.log(stripLuaComments(`e([==[ + console.log( + stripLuaComments(`e([==[ --- Hello - ]==])`)); + ]==])`), + ); }); test("Test query parsing", () => { @@ -131,13 +131,9 @@ test("Test query parsing", () => { ); parse(`_(query[[from p = index.tag("page") order by p.lastModified]])`); // group by single key - parse( - `_(query[[from p = index.tag("page") group by p.category]])`, - ); + parse(`_(query[[from p = index.tag("page") group by p.category]])`); // group by multiple keys - parse( - `_(query[[from p = index.tag("page") group by p.category, p.status]])`, - ); + parse(`_(query[[from p = index.tag("page") group by p.category, p.status]])`); // group by + having parse( `_(query[[from p = index.tag("page") group by p.category having #group > 1]])`, @@ -152,17 +148,22 @@ test("Test numeric constant parsing", () => { // Examples from Lua 5.4 Reference Manual, except hexadecimal constants with fractional part expect((parseExpressionString(`3`) as LuaNumberLiteral).value).toEqual(3); expect((parseExpressionString(`345`) as LuaNumberLiteral).value).toEqual(345); - expect((parseExpressionString(`0xff`) as LuaNumberLiteral).value).toEqual(0xff); - expect((parseExpressionString(`0xBEBADA`) as LuaNumberLiteral).value).toEqual(0xBEBADA, + expect((parseExpressionString(`0xff`) as LuaNumberLiteral).value).toEqual( + 0xff, + ); + expect((parseExpressionString(`0xBEBADA`) as LuaNumberLiteral).value).toEqual( + 0xbebada, ); expect((parseExpressionString(`3.0`) as LuaNumberLiteral).value).toEqual(3.0); - expect((parseExpressionString(`3.1416`) as LuaNumberLiteral).value)// biome-ignore lint/suspicious/noApproximativeNumericConstant: testing exact value 3.1416, not Math.PI - .toEqual(3.1416, - ); - expect((parseExpressionString(`314.16e-2`) as LuaNumberLiteral).value).toEqual(314.16e-2, - ); - expect((parseExpressionString(`0.31416E1`) as LuaNumberLiteral).value).toEqual(0.31416E1, - ); - expect((parseExpressionString(`34e1`) as LuaNumberLiteral).value).toEqual(34e1, + expect((parseExpressionString(`3.1416`) as LuaNumberLiteral).value) // biome-ignore lint/suspicious/noApproximativeNumericConstant: testing exact value 3.1416, not Math.PI + .toEqual(3.1416); + expect( + (parseExpressionString(`314.16e-2`) as LuaNumberLiteral).value, + ).toEqual(314.16e-2); + expect( + (parseExpressionString(`0.31416E1`) as LuaNumberLiteral).value, + ).toEqual(0.31416e1); + expect((parseExpressionString(`34e1`) as LuaNumberLiteral).value).toEqual( + 34e1, ); }); diff --git a/client/space_lua/parse.ts b/client/space_lua/parse.ts index 17133111..f8973e2a 100644 --- a/client/space_lua/parse.ts +++ b/client/space_lua/parse.ts @@ -52,10 +52,7 @@ const customIndent = indentNodeProp.add({ export const luaLanguage = LRLanguage.define({ name: "space-lua", parser: parser.configure({ - props: [ - luaStyleTags, - customIndent, - ], + props: [luaStyleTags, customIndent], }), languageData: { commentTokens: { line: "--", block: { open: "--[[", close: "--]]" } }, @@ -97,8 +94,9 @@ function expressionHasFunctionDef(e: LuaExpression): boolean { } return false; case "Binary": - return expressionHasFunctionDef(e.left) || - expressionHasFunctionDef(e.right); + return ( + expressionHasFunctionDef(e.left) || expressionHasFunctionDef(e.right) + ); case "Unary": return expressionHasFunctionDef(e.argument); case "Parenthesized": @@ -119,8 +117,9 @@ function expressionHasFunctionDef(e: LuaExpression): boolean { } return false; case "TableAccess": - return expressionHasFunctionDef(e.object) || - expressionHasFunctionDef(e.key); + return ( + expressionHasFunctionDef(e.object) || expressionHasFunctionDef(e.key) + ); case "PropertyAccess": return expressionHasFunctionDef(e.object); case "Query": @@ -145,7 +144,8 @@ function expressionHasFunctionDef(e: LuaExpression): boolean { return true; } if ( - c.orderBy[j].using && typeof c.orderBy[j].using !== "string" + c.orderBy[j].using && + typeof c.orderBy[j].using !== "string" ) { return true; } @@ -161,8 +161,7 @@ function expressionHasFunctionDef(e: LuaExpression): boolean { return false; case "FilteredCall": return ( - expressionHasFunctionDef(e.call) || - expressionHasFunctionDef(e.filter) + expressionHasFunctionDef(e.call) || expressionHasFunctionDef(e.filter) ); default: return false; @@ -187,8 +186,10 @@ function exprReferencesNames(e: LuaExpression, names: Set): boolean { case "Variable": return names.has(e.name); case "Binary": - return exprReferencesNames(e.left, names) || - exprReferencesNames(e.right, names); + return ( + exprReferencesNames(e.left, names) || + exprReferencesNames(e.right, names) + ); case "Unary": return exprReferencesNames(e.argument, names); case "Parenthesized": @@ -200,8 +201,10 @@ function exprReferencesNames(e: LuaExpression, names: Set): boolean { } return false; case "TableAccess": - return exprReferencesNames(e.object, names) || - exprReferencesNames(e.key, names); + return ( + exprReferencesNames(e.object, names) || + exprReferencesNames(e.key, names) + ); case "PropertyAccess": return exprReferencesNames(e.object, names); case "TableConstructor": @@ -275,8 +278,10 @@ function lvalueReferencesNames(lv: LuaLValue, names: Set): boolean { case "PropertyAccess": return exprReferencesNames(lv.object as LuaExpression, names); case "TableAccess": - return exprReferencesNames(lv.object as LuaExpression, names) || - exprReferencesNames(lv.key, names); + return ( + exprReferencesNames(lv.object as LuaExpression, names) || + exprReferencesNames(lv.key, names) + ); } } @@ -409,10 +414,7 @@ function blockCapturesNames(block: LuaBlock, names: Set): boolean { return false; } -function statementCapturesNames( - s: LuaStatement, - names: Set, -): boolean { +function statementCapturesNames(s: LuaStatement, names: Set): boolean { switch (s.type) { case "Local": { const exprs = (s as any).expressions as LuaExpression[] | undefined; @@ -505,8 +507,9 @@ function exprCapturesNames(e: LuaExpression, names: Set): boolean { case "FunctionDefinition": return functionBodyCapturesNames(e.body, names); case "Binary": - return exprCapturesNames(e.left, names) || - exprCapturesNames(e.right, names); + return ( + exprCapturesNames(e.left, names) || exprCapturesNames(e.right, names) + ); case "Unary": return exprCapturesNames(e.argument, names); case "Parenthesized": @@ -518,8 +521,9 @@ function exprCapturesNames(e: LuaExpression, names: Set): boolean { } return false; case "TableAccess": - return exprCapturesNames(e.object, names) || - exprCapturesNames(e.key, names); + return ( + exprCapturesNames(e.object, names) || exprCapturesNames(e.key, names) + ); case "PropertyAccess": return exprCapturesNames(e.object, names); case "TableConstructor": @@ -574,8 +578,7 @@ function exprCapturesNames(e: LuaExpression, names: Set): boolean { return false; case "FilteredCall": return ( - exprCapturesNames(e.call, names) || - exprCapturesNames(e.filter, names) + exprCapturesNames(e.call, names) || exprCapturesNames(e.filter, names) ); default: return false; @@ -644,7 +647,8 @@ function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock { case "FunctionCallStatement": { if (!hasFunctionDef) { const call = (s as any).call as LuaFunctionCallExpression; - hasFunctionDef = expressionHasFunctionDef(call.prefix) || + hasFunctionDef = + expressionHasFunctionDef(call.prefix) || expressionsHaveFunctionDef(call.args); } break; @@ -711,7 +715,8 @@ function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock { hasCloseHere = hasCloseHere || !!child.hasCloseHere; hasFunctionDef = hasFunctionDef || !!child.hasFunctionDef; if (!hasFunctionDef) { - hasFunctionDef = expressionHasFunctionDef((s as any).start) || + hasFunctionDef = + expressionHasFunctionDef((s as any).start) || expressionHasFunctionDef((s as any).end) || ((s as any).step ? expressionHasFunctionDef((s as any).step) @@ -814,7 +819,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement { from?: number; to?: number; }[] = []; - let elseBlock: LuaBlock | undefined ; + let elseBlock: LuaBlock | undefined; for (let i = 0; i < t.children!.length; i += 4) { const child = t.children![i]; if (!child || !child.children || !child.children[0]) { @@ -904,8 +909,8 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement { case "Assign": return { type: "Assignment", - variables: t.children![0].children! - .filter((c) => c.type && c.type !== ",") + variables: t + .children![0].children!.filter((c) => c.type && c.type !== ",") .map((lvalue) => parseLValue(lvalue, ctx)), expressions: parseExpList(t.children![2], ctx), ctx: context(t, ctx), @@ -949,9 +954,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement { console.error(t); throw new Error( `Unknown statement type: ${ - t.children![0]?.text - ? t.children![0].text - : String(t.type) + t.children![0]?.text ? t.children![0].text : String(t.type) }`, ); } @@ -982,8 +985,8 @@ function parseAttNames(t: ParseTree, ctx: ASTCtx): LuaAttName[] { if (t.type !== "AttNameList") { throw new Error(`Expected AttNameList, got ${t.type}`); } - return t.children! - .filter((c) => c.type && c.type !== ",") + return t + .children!.filter((c) => c.type && c.type !== ",") .map((att) => parseAttName(att, ctx)); } @@ -1044,7 +1047,7 @@ function parseFunctionName(t: ParseTree, ctx: ASTCtx): LuaFunctionName { throw new Error(`Expected FunctionName, got ${t.type}`); } const propNames: string[] = []; - let colonName: string | undefined ; + let colonName: string | undefined; for (let i = 0; i < t.children!.length; i += 2) { const prop = t.children![i]; propNames.push(prop.children![0].text!); @@ -1065,8 +1068,8 @@ function parseNameList(t: ParseTree): string[] { if (t.type !== "NameList") { throw new Error(`Expected NameList, got ${t.type}`); } - return t.children! - .filter((c) => c.type === "Name") + return t + .children!.filter((c) => c.type === "Name") .map((c) => c.children![0].text!); } @@ -1074,8 +1077,8 @@ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] { if (t.type !== "ExpList") { throw new Error(`Expected ExpList, got ${t.type}`); } - return t.children! - .filter((c) => c.type && c.type !== ",") + return t + .children!.filter((c) => c.type && c.type !== ",") .map((e) => parseExpression(e, ctx)); } @@ -1094,44 +1097,46 @@ function parseString(s: string): string { } return text; } - return s.slice(1, -1).replace( - /\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g, - (match, capture) => { - switch (capture) { - case "a": - return "\x07"; // Bell - case "b": - return "\b"; // Backspace - case "f": - return "\f"; // Form feed - case "n": - return "\n"; // Newline - case "r": - return "\r"; // Carriage return - case "t": - return "\t"; // Horizontal tab - case "v": - return "\v"; // Vertical tab - case "\\": - return "\\"; // Backslash - case '"': - return '"'; // Double quote - case "'": - return "'"; // Single quote - default: - // Handle hexadecimal \x00 - if (capture.startsWith("x")) { - return String.fromCharCode(parseInt(capture.slice(1), 16)); - } - // Handle unicode \u{XXXX} - if (capture.startsWith("u{")) { - const codePoint = parseInt(capture.slice(2, -1), 16); - return String.fromCodePoint(codePoint); - } - return match; // return the original match if nothing fits - } - }, - ); + return s + .slice(1, -1) + .replace( + /\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g, + (match, capture) => { + switch (capture) { + case "a": + return "\x07"; // Bell + case "b": + return "\b"; // Backspace + case "f": + return "\f"; // Form feed + case "n": + return "\n"; // Newline + case "r": + return "\r"; // Carriage return + case "t": + return "\t"; // Horizontal tab + case "v": + return "\v"; // Vertical tab + case "\\": + return "\\"; // Backslash + case '"': + return '"'; // Double quote + case "'": + return "'"; // Single quote + default: + // Handle hexadecimal \x00 + if (capture.startsWith("x")) { + return String.fromCharCode(parseInt(capture.slice(1), 16)); + } + // Handle unicode \u{XXXX} + if (capture.startsWith("u{")) { + const codePoint = parseInt(capture.slice(2, -1), 16); + return String.fromCodePoint(codePoint); + } + return match; // return the original match if nothing fits + } + }, + ); } function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression { @@ -1224,10 +1229,10 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression { case "TableConstructor": return { type: "TableConstructor", - fields: t.children! - .slice(1, -1) + fields: t + .children!.slice(1, -1) .filter((c) => - ["FieldExp", "FieldProp", "FieldDynamic"].includes(c.type!) + ["FieldExp", "FieldProp", "FieldDynamic"].includes(c.type!), ) .map((tf) => parseTableField(tf, ctx)), ctx: context(t, ctx), @@ -1347,10 +1352,13 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause { } case "GroupByClause": { // children: ckw<"group">, ckw<"by">, exp, ",", exp, ... - const expressions: LuaExpression[] = t.children! - .filter((c) => - c.type !== undefined && c.type !== "group" && c.type !== "by" && - c.type !== "," + const expressions: LuaExpression[] = t + .children!.filter( + (c) => + c.type !== undefined && + c.type !== "group" && + c.type !== "by" && + c.type !== ",", ) .map((c) => parseExpression(c, ctx)); return { @@ -1384,8 +1392,10 @@ function parseFunctionBody(t: ParseTree, ctx: ASTCtx): LuaFunctionBody { } return { type: "FunctionBody", - parameters: t.children![1].children! - .filter((c) => c.type && ["Name", "Ellipsis"].includes(c.type)) + parameters: t + .children![1].children!.filter( + (c) => c.type && ["Name", "Ellipsis"].includes(c.type), + ) .map((c) => c.children![0].text!), block: parseBlock(t.children![3], ctx), ctx: context(t, ctx), @@ -1573,9 +1583,7 @@ export function parse(s: string, ctx: ASTCtx = {}): LuaBlock { if (e && typeof e === "object" && "astCtx" in e) { throw new LuaRuntimeError( e.message, - LuaStackFrame.lostFrame.withCtx( - (e as any).astCtx as ASTCtx, - ), + LuaStackFrame.lostFrame.withCtx((e as any).astCtx as ASTCtx), ); } throw e; @@ -1619,9 +1627,7 @@ function luaUnexpectedSymbolMessage(src: string, from: number): string { /** * Helper function to parse a Lua expression string */ -export function parseExpressionString( - expr: string, -): LuaExpression { +export function parseExpressionString(expr: string): LuaExpression { const parsedLua = parse(`_(${expr})`) as LuaBlock; return (parsedLua.statements[0] as LuaFunctionCallStatement).call.args[0]; } diff --git a/client/space_lua/query_collection.test.ts b/client/space_lua/query_collection.test.ts index 68c086bb..51f722b1 100644 --- a/client/space_lua/query_collection.test.ts +++ b/client/space_lua/query_collection.test.ts @@ -1,11 +1,7 @@ import { expect, test } from "vitest"; import { parseExpressionString } from "./parse.ts"; import { ArrayQueryCollection } from "./query_collection.ts"; -import { - LuaEnv, - LuaNativeJSFunction, - LuaStackFrame, -} from "./runtime.ts"; +import { LuaEnv, LuaNativeJSFunction, LuaStackFrame } from "./runtime.ts"; test("ArrayQueryCollection", async () => { const rootEnv = new LuaEnv(); @@ -16,10 +12,14 @@ test("ArrayQueryCollection", async () => { }), ); - const collection = new ArrayQueryCollection([{ x: 1, y: 1 }, { x: 2, y: 2 }, { - x: 3, - y: 3, - }]); + const collection = new ArrayQueryCollection([ + { x: 1, y: 1 }, + { x: 2, y: 2 }, + { + x: 3, + y: 3, + }, + ]); const result = await collection.query( { objectVariable: "p", @@ -300,11 +300,13 @@ test("ArrayQueryCollection - nulls ordering", async () => { const r3 = await collection.query( { objectVariable: "p", - orderBy: [{ - expr: parseExpressionString("p.priority"), - desc: true, - nulls: "last", - }], + orderBy: [ + { + expr: parseExpressionString("p.priority"), + desc: true, + nulls: "last", + }, + ], }, rootEnv, LuaStackFrame.lostFrame, @@ -320,11 +322,13 @@ test("ArrayQueryCollection - nulls ordering", async () => { const r4 = await collection.query( { objectVariable: "p", - orderBy: [{ - expr: parseExpressionString("p.priority"), - desc: false, - nulls: "first", - }], + orderBy: [ + { + expr: parseExpressionString("p.priority"), + desc: false, + nulls: "first", + }, + ], }, rootEnv, LuaStackFrame.lostFrame, @@ -339,10 +343,7 @@ test("ArrayQueryCollection - nulls ordering", async () => { test("ArrayQueryCollection - SWO violation detection", async () => { const rootEnv = new LuaEnv(); - rootEnv.setLocal( - "badCmp", - new LuaNativeJSFunction((a, b) => a <= b), - ); + rootEnv.setLocal("badCmp", new LuaNativeJSFunction((a, b) => a <= b)); const collection = new ArrayQueryCollection([ { name: "alice", score: 10 }, @@ -354,11 +355,13 @@ test("ArrayQueryCollection - SWO violation detection", async () => { collection.query( { objectVariable: "p", - orderBy: [{ - expr: parseExpressionString("p.score"), - desc: false, - using: "badCmp", - }], + orderBy: [ + { + expr: parseExpressionString("p.score"), + desc: false, + using: "badCmp", + }, + ], }, rootEnv, LuaStackFrame.lostFrame, diff --git a/client/space_lua/query_collection.ts b/client/space_lua/query_collection.ts index 55b77c89..663ea55b 100644 --- a/client/space_lua/query_collection.ts +++ b/client/space_lua/query_collection.ts @@ -58,9 +58,8 @@ function buildGroupItemEnv( if (item instanceof LuaTable) { const keyVal = item.rawGet("key"); const groupVal = item.rawGet("group"); - const firstItem = (groupVal instanceof LuaTable) - ? groupVal.rawGet(1) - : undefined; + const firstItem = + groupVal instanceof LuaTable ? groupVal.rawGet(1) : undefined; if (firstItem) { for (const k of luaKeys(firstItem)) { @@ -176,8 +175,7 @@ export interface LuaQueryCollection { * Implements a query collection for a regular JavaScript array */ export class ArrayQueryCollection implements LuaQueryCollection { - constructor(private readonly array: T[]) { - } + constructor(private readonly array: T[]) {} query( query: LuaCollectionQuery, env: LuaEnv, @@ -209,8 +207,10 @@ function containsAggregate(expr: LuaExpression): boolean { if (fc.prefix.type === "Variable" && getAggregateSpec(fc.prefix.name)) { return true; } - return containsAggregate(fc) || - containsAggregate((expr as LuaFilteredCallExpression).filter); + return ( + containsAggregate(fc) || + containsAggregate((expr as LuaFilteredCallExpression).filter) + ); } case "FunctionCall": { const fc = expr as LuaFunctionCallExpression; @@ -365,30 +365,16 @@ export async function evalExpressionWithAggregates( } const left = singleResult(await recurse(bin.left)); const right = singleResult(await recurse(bin.right)); - return luaOp( - bin.operator, - left, - right, - undefined, - undefined, - expr.ctx, - sf, - ); + return luaOp(bin.operator, left, right, undefined, undefined, expr.ctx, sf); } if (expr.type === "Unary") { const un = expr as LuaUnaryExpression; const arg = singleResult(await recurse(un.argument)); switch (un.operator) { case "-": - return typeof arg === "number" ? -arg : luaOp( - "-", - 0, - arg, - undefined, - undefined, - expr.ctx, - sf, - ); + return typeof arg === "number" + ? -arg + : luaOp("-", 0, arg, undefined, undefined, expr.ctx, sf); case "not": return !luaTruthy(arg); case "#": @@ -447,10 +433,7 @@ function normalizeSelectResults(results: any[]): any[] { const rebuilt = new LuaTable(); for (const k of canonicalKeys) { const v = item.rawGet(k); - void rebuilt.rawSet( - k, - (v === undefined || v === null) ? LIQ_NULL : v, - ); + void rebuilt.rawSet(k, v === undefined || v === null ? LIQ_NULL : v); } for (const k of luaKeys(item)) { if (typeof k !== "string") { @@ -487,14 +470,10 @@ async function usingCompare( keyIdx: number, ): Promise { const res = luaTruthy( - singleResult( - await luaCall(luaCmp, [aVal, bVal], sf.astCtx ?? {}, sf), - ), + singleResult(await luaCall(luaCmp, [aVal, bVal], sf.astCtx ?? {}, sf)), ); const reverseRes = luaTruthy( - singleResult( - await luaCall(luaCmp, [bVal, aVal], sf.astCtx ?? {}, sf), - ), + singleResult(await luaCall(luaCmp, [bVal, aVal], sf.astCtx ?? {}, sf)), ); // both true means SWO violation @@ -636,12 +615,7 @@ export async function applyQuery( if (query.where) { const filteredResults = []; for (const value of results) { - const itemEnv = buildItemEnvLocal( - query.objectVariable, - value, - env, - sf, - ); + const itemEnv = buildItemEnvLocal(query.objectVariable, value, env, sf); if (await evalExpression(query.where, itemEnv, sf)) { filteredResults.push(value); } @@ -656,29 +630,23 @@ export async function applyQuery( if (query.groupBy) { // Extract bare names from group-by expressions for local binding - groupByNames = query.groupBy.map((expr) => { - if (expr.type === "Variable") { - return expr.name; - } - if (expr.type === "PropertyAccess") { - return expr.property; - } - return undefined as unknown as string; - }).filter(Boolean); + groupByNames = query.groupBy + .map((expr) => { + if (expr.type === "Variable") { + return expr.name; + } + if (expr.type === "PropertyAccess") { + return expr.property; + } + return undefined as unknown as string; + }) + .filter(Boolean); - const groups = new Map< - string, - { key: any; items: any[] } - >(); + const groups = new Map(); const groupByExprs = query.groupBy as LuaExpression[]; for (const item of results) { - const itemEnv = buildItemEnvLocal( - query.objectVariable, - item, - env, - sf, - ); + const itemEnv = buildItemEnvLocal(query.objectVariable, item, env, sf); const keyParts: any[] = []; const keyRecord: Record = {}; @@ -698,9 +666,10 @@ export async function applyQuery( } } - const compositeKey = keyParts.length === 1 - ? generateKey(keyParts[0]) - : JSON.stringify(keyParts.map(generateKey)); + const compositeKey = + keyParts.length === 1 + ? generateKey(keyParts[0]) + : JSON.stringify(keyParts.map(generateKey)); let entry = groups.get(compositeKey); if (!entry) { let keyVal: any; @@ -734,8 +703,7 @@ export async function applyQuery( const item = items[i]; groupTable.rawSetArrayIndex( i + 1, - (item instanceof LuaTable || typeof item !== "object" || - item === null) + item instanceof LuaTable || typeof item !== "object" || item === null ? item : jsToLuaValue(item), ); @@ -769,12 +737,7 @@ export async function applyQuery( env, ); } else { - const itemEnv = buildItemEnvLocal( - query.objectVariable, - value, - env, - sf, - ); + const itemEnv = buildItemEnvLocal(query.objectVariable, value, env, sf); condResult = await evalExpression(query.having, itemEnv, sf); } if (condResult) { @@ -785,12 +748,8 @@ export async function applyQuery( } const mkEnv = grouped - ? ( - ov: string | undefined, - item: any, - e: LuaEnv, - s: LuaStackFrame, - ) => buildGroupItemEnv(ov, groupByNames, item, e, s) + ? (ov: string | undefined, item: any, e: LuaEnv, s: LuaStackFrame) => + buildGroupItemEnv(ov, groupByNames, item, e, s) : buildItemEnvLocal; let selectResults: any[] | undefined; @@ -855,7 +814,8 @@ export async function applyQuery( resolvedUsing, violated, sf, - )); + ), + ); // Check for SWO violations in comparators for (let i = 0; i < violated.length; i++) { @@ -944,9 +904,7 @@ export async function queryLua( enricher?: (key: KvKey, item: any) => any, ): Promise { const results: T[] = []; - for await ( - let { key, value } of kv.query({ prefix }) - ) { + for await (let { key, value } of kv.query({ prefix })) { if (enricher) { value = enricher(key, value); } @@ -979,8 +937,8 @@ function luaTableToJSWithNulls( isSqlNull(v) ? "__SQL_NULL__" : v instanceof LuaTable - ? luaTableToJSWithNulls(v, sf) - : v, + ? luaTableToJSWithNulls(v, sf) + : v, ); } return arr; @@ -991,8 +949,8 @@ function luaTableToJSWithNulls( obj[key] = isSqlNull(v) ? "__SQL_NULL__" : v instanceof LuaTable - ? luaTableToJSWithNulls(v, sf) - : v; + ? luaTableToJSWithNulls(v, sf) + : v; } return obj; } diff --git a/client/space_lua/render_lua_html.ts b/client/space_lua/render_lua_html.ts index ad05b07b..82087905 100644 --- a/client/space_lua/render_lua_html.ts +++ b/client/space_lua/render_lua_html.ts @@ -37,8 +37,12 @@ function isEmpty(v: any): boolean { } function isPlainObject(v: any): v is Record { - return typeof v === "object" && v !== null && !Array.isArray(v) && - v.constructor === Object; + return ( + typeof v === "object" && + v !== null && + !Array.isArray(v) && + v.constructor === Object + ); } function hasStringKeys(tbl: LuaTable): boolean { diff --git a/client/space_lua/rp.bench.ts b/client/space_lua/rp.bench.ts index ad4e14e7..ced808a2 100644 --- a/client/space_lua/rp.bench.ts +++ b/client/space_lua/rp.bench.ts @@ -44,10 +44,7 @@ function makeEnv(): { global: LuaEnv; sf: LuaStackFrame } { global.setLocal("string", stringLib); - global.setLocal( - "type", - new LuaBuiltinFunction((_sf, v) => luaTypeOf(v)), - ); + global.setLocal("type", new LuaBuiltinFunction((_sf, v) => luaTypeOf(v))); const sf = LuaStackFrame.createWithGlobalEnv(global); @@ -65,7 +62,7 @@ async function run(ast: any) { if (e instanceof LuaRuntimeError) { throw e; } - throw new Error(`Lua execution error: ${e && (e as any).message || e}`); + throw new Error(`Lua execution error: ${(e && (e as any).message) || e}`); } } @@ -176,8 +173,8 @@ const luaTableDotMissRead = ` `; // The truthiness_test.lua uses the `string.format`. -const truthinessPath = - new URL("./truthiness_test.lua", import.meta.url).pathname; +const truthinessPath = new URL("./truthiness_test.lua", import.meta.url) + .pathname; const truthinessCode = await readFile(truthinessPath, "utf-8"); const astWhileSync = parseLua(luaWhileSync); diff --git a/client/space_lua/rp.ts b/client/space_lua/rp.ts index 78e68494..4b48fb24 100644 --- a/client/space_lua/rp.ts +++ b/client/space_lua/rp.ts @@ -4,9 +4,11 @@ export type RP = T | Promise; // Returns true when v is a Promise or a has a then function. export function isPromise(v: RP): v is Promise { - return v !== null && + return ( + v !== null && (typeof v === "object" || typeof v === "function") && - typeof (v as any).then === "function"; + typeof (v as any).then === "function" + ); } export function rpThen(v: RP, f: (a: A) => RP): RP { diff --git a/client/space_lua/runtime.test.ts b/client/space_lua/runtime.test.ts index ac7c3f34..a37683fb 100644 --- a/client/space_lua/runtime.test.ts +++ b/client/space_lua/runtime.test.ts @@ -11,11 +11,9 @@ test("Test Lua Rutime", async () => { // Test LuaMultires expect(new LuaMultiRes([]).flatten().values).toEqual([]); expect(new LuaMultiRes([1, 2, 3]).flatten().values).toEqual([1, 2, 3]); - expect(new LuaMultiRes([1, new LuaMultiRes([2, 3])]).flatten().values).toEqual([ - 1, - 2, - 3, - ]); + expect( + new LuaMultiRes([1, new LuaMultiRes([2, 3])]).flatten().values, + ).toEqual([1, 2, 3]); // Test JavaScript to Lua conversion expect(jsToLuaValue(1)).toEqual(1); @@ -39,7 +37,9 @@ test("Test Lua Rutime", async () => { expect(luaVal.get(2).get("name")).toEqual("John"); // Functions in objects luaVal = jsToLuaValue({ name: "Pete", first: (l: any[]) => l[0] }); - expect(luaVal.get("first").call(LuaStackFrame.lostFrame, [1, 2, 3])).toEqual(1); + expect(luaVal.get("first").call(LuaStackFrame.lostFrame, [1, 2, 3])).toEqual( + 1, + ); // Test luaToString expect(await luaToString(new Promise((resolve) => resolve(1)))).toEqual("1"); diff --git a/client/space_lua/runtime.ts b/client/space_lua/runtime.ts index 5a183dbe..4cc5abf3 100644 --- a/client/space_lua/runtime.ts +++ b/client/space_lua/runtime.ts @@ -113,10 +113,7 @@ export function luaTypeName(val: any): LuaType { } // Check whether a value is callable without invoking it. -export function luaIsCallable( - v: LuaValue, - sf: LuaStackFrame, -): boolean { +export function luaIsCallable(v: LuaValue, sf: LuaStackFrame): boolean { if (v === null || v === undefined) { return false; } @@ -137,9 +134,7 @@ export function luaIsCallable( } // In Lua, `__close` must be a function (no `__call` fallback). -function luaIsCloseMethod( - v: LuaValue, -): boolean { +function luaIsCloseMethod(v: LuaValue): boolean { return typeof v === "function" || isILuaFunction(v); } @@ -261,8 +256,7 @@ export class LuaEnv implements ILuaSettable, ILuaGettable { private readonly consts = new Set(); private readonly numericTypes = new Map(); - constructor(readonly parent?: LuaEnv) { - } + constructor(readonly parent?: LuaEnv) {} setLocal(name: string, value: LuaValue, numType?: NumericType) { this.variables.set(name, value); @@ -327,10 +321,7 @@ export class LuaEnv implements ILuaSettable, ILuaGettable { return false; } - get( - name: string, - _sf?: LuaStackFrame, - ): Promise | LuaValue | null { + get(name: string, _sf?: LuaStackFrame): Promise | LuaValue | null { if (this.variables.has(name)) { return this.variables.get(name); } @@ -378,8 +369,7 @@ export class LuaStackFrame { readonly parent?: LuaStackFrame, readonly currentFunction?: LuaFunction, readonly threadState: LuaThreadState = { closeStack: undefined }, - ) { - } + ) {} static createWithGlobalEnv( globalEnv: LuaEnv, @@ -458,7 +448,10 @@ export class LuaFunction implements ILuaFunction { private capturedEnv: LuaEnv; funcHasGotos?: boolean; - constructor(readonly body: LuaFunctionBody, closure: LuaEnv) { + constructor( + readonly body: LuaFunctionBody, + closure: LuaEnv, + ) { this.capturedEnv = closure; } @@ -549,8 +542,7 @@ function mapFunctionReturnValue(values: any[]): any { } export class LuaNativeJSFunction implements ILuaFunction { - constructor(readonly fn: (...args: JSValue[]) => JSValue) { - } + constructor(readonly fn: (...args: JSValue[]) => JSValue) {} // Performs automatic conversion between Lua and JS values for arguments, but not for return values call(sf: LuaStackFrame, ...args: LuaValue[]): Promise | LuaValue { @@ -574,8 +566,7 @@ export class LuaNativeJSFunction implements ILuaFunction { export class LuaBuiltinFunction implements ILuaFunction { constructor( readonly fn: (sf: LuaStackFrame, ...args: LuaValue[]) => LuaValue, - ) { - } + ) {} call(sf: LuaStackFrame, ...args: LuaValue[]): Promise | LuaValue { // _CTX is already available via the stack frame @@ -727,7 +718,8 @@ export class LuaTable implements ILuaSettable, ILuaGettable { const normalizedKey = LuaTable.normalizeNumericKey(key); if ( - typeof normalizedKey === "number" && Number.isInteger(normalizedKey) && + typeof normalizedKey === "number" && + Number.isInteger(normalizedKey) && normalizedKey >= 1 ) { const idx = normalizedKey - 1; @@ -835,7 +827,8 @@ export class LuaTable implements ILuaSettable, ILuaGettable { } if ( - typeof normalizedKey === "number" && Number.isInteger(normalizedKey) && + typeof normalizedKey === "number" && + Number.isInteger(normalizedKey) && normalizedKey >= 1 ) { const idx = normalizedKey - 1; @@ -928,17 +921,11 @@ export class LuaTable implements ILuaSettable, ILuaGettable { const ctx = sf?.astCtx ?? EMPTY_CTX; if (key === null || key === undefined) { - throw new LuaRuntimeError( - "table index is nil", - errSf, - ); + throw new LuaRuntimeError("table index is nil", errSf); } if (typeof key === "number" && Number.isNaN(key)) { - throw new LuaRuntimeError( - "table index is NaN", - errSf, - ); + throw new LuaRuntimeError("table index is NaN", errSf); } if (this.has(key)) { @@ -1037,7 +1024,8 @@ export class LuaTable implements ILuaSettable, ILuaGettable { } if ( - typeof normalizedKey === "number" && Number.isInteger(normalizedKey) && + typeof normalizedKey === "number" && + Number.isInteger(normalizedKey) && normalizedKey >= 1 ) { const idx = normalizedKey - 1; @@ -1111,10 +1099,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable { const s = singleResult(v); if (typeof s !== "string") { - throw new LuaRuntimeError( - "'__tostring' must return a string", - sf, - ); + throw new LuaRuntimeError("'__tostring' must return a string", sf); } return s; } @@ -1227,10 +1212,7 @@ export async function luaSet( numType?: NumericType, ): Promise { if (!obj) { - throw new LuaRuntimeError( - `Not a settable object: nil`, - sf, - ); + throw new LuaRuntimeError(`Not a settable object: nil`, sf); } const normKey = isTaggedFloat(key) ? key.value : key; @@ -1252,10 +1234,7 @@ export function luaGet( const errSf = ctx ? sf.withCtx(ctx) : sf; if (obj === null || obj === undefined) { - throw new LuaRuntimeError( - `attempt to index a nil value`, - errSf, - ); + throw new LuaRuntimeError(`attempt to index a nil value`, errSf); } // In Lua reading with a nil key returns nil silently @@ -1344,7 +1323,7 @@ export function luaCall( if (isPromise(jsArgs)) { return jsArgs.then((resolved) => - (callee as (...a: any[]) => any)(...resolved) + (callee as (...a: any[]) => any)(...resolved), ); } return (callee as (...a: any[]) => any)(...jsArgs); @@ -1384,13 +1363,9 @@ export function luaCall( // ILuaFunction (LuaFunction/LuaBuiltinFunction/LuaNativeJSFunction/etc.) if (isILuaFunction(callee)) { const base = (sf || LuaStackFrame.lostFrame).withCtx(ctx); - const frameForCall = callee instanceof LuaFunction - ? base.withFunction(callee) - : base; - return callee.call( - frameForCall, - ...args, - ); + const frameForCall = + callee instanceof LuaFunction ? base.withFunction(callee) : base; + return callee.call(frameForCall, ...args); } throw new LuaRuntimeError( @@ -1494,7 +1469,8 @@ export class LuaRuntimeError extends Error { // Add position indicator const pointer = `${" ".repeat(column)}^`; - traceStr += `* ${ctx.ref || "(unknown source)"} @ ${line}:${column}:\n` + + traceStr += + `* ${ctx.ref || "(unknown source)"} @ ${line}:${column}:\n` + ` ${codeLine}\n` + ` ${pointer}\n`; current = current.parent; @@ -1613,7 +1589,7 @@ export function luaFormatNumber(n: number, kind?: "int" | "float"): string { if (n === Infinity) return "inf"; if (n === -Infinity) return "-inf"; if (n === 0) { - return (1 / n === -Infinity) ? "-0.0" : "0.0"; + return 1 / n === -Infinity ? "-0.0" : "0.0"; } // Delegate to luaFormat for `%.14g` const s = luaFormat("%.14g", n); @@ -1709,13 +1685,12 @@ export function luaValueToJS(value: any, sf: LuaStackFrame): any { return value.toJS(sf); } if ( - value instanceof LuaNativeJSFunction || value instanceof LuaFunction || + value instanceof LuaNativeJSFunction || + value instanceof LuaFunction || value instanceof LuaBuiltinFunction ) { return (...args: any[]) => { - const jsArgs = rpAll( - args.map((v) => luaValueToJS(v, sf)), - ); + const jsArgs = rpAll(args.map((v) => luaValueToJS(v, sf))); if (isPromise(jsArgs)) { return luaValueToJS( jsArgs.then((jsArgs) => (value as ILuaFunction).call(sf, ...jsArgs)), diff --git a/client/space_lua/stdlib.ts b/client/space_lua/stdlib.ts index 3837106c..d85f0e36 100644 --- a/client/space_lua/stdlib.ts +++ b/client/space_lua/stdlib.ts @@ -37,15 +37,12 @@ import { isTaggedFloat, makeLuaFloat } from "./numeric.ts"; import { isPromise } from "./rp.ts"; const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => { - console.log( - "[Lua]", - ...(await Promise.all(args.map((v) => luaToString(v)))), - ); + console.log("[Lua]", ...(await Promise.all(args.map((v) => luaToString(v))))); }); const assertFunction = new LuaBuiltinFunction( async (sf, value: any, message?: string) => { - if (!await value) { + if (!(await value)) { throw new LuaRuntimeError(`Assertion failed: ${message}`, sf); } }, @@ -143,10 +140,7 @@ const tostringFunction = new LuaBuiltinFunction( const unwrap = (v: any): string => { const s = singleResult(v); if (typeof s !== "string") { - throw new LuaRuntimeError( - "'__tostring' must return a string", - sf, - ); + throw new LuaRuntimeError("'__tostring' must return a string", sf); } return s; }; @@ -203,10 +197,7 @@ async function pcallBoundary( sf: LuaStackFrame, fn: ILuaFunction, args: LuaValue[], -): Promise< - | { ok: true; values: LuaValue[] } - | { ok: false; message: string } -> { +): Promise<{ ok: true; values: LuaValue[] } | { ok: false; message: string }> { const closeStack = luaEnsureCloseStack(sf); const mark = closeStack.length; @@ -274,11 +265,9 @@ const setmetatableFunction = new LuaBuiltinFunction( }, ); -const rawlenFunction = new LuaBuiltinFunction( - (_sf, value: LuaValue) => { - return luaLen(value, _sf, true); - }, -); +const rawlenFunction = new LuaBuiltinFunction((_sf, value: LuaValue) => { + return luaLen(value, _sf, true); +}); const rawsetFunction = new LuaBuiltinFunction( (_sf, table: LuaTable, key: LuaValue, value: LuaValue) => { @@ -290,7 +279,8 @@ const rawgetFunction = new LuaBuiltinFunction( (_sf, table: any, key: LuaValue) => { const isArray = Array.isArray(table); - const isPlainObj = typeof table === "object" && + const isPlainObj = + typeof table === "object" && table !== null && (table as any).constructor === Object; @@ -339,13 +329,11 @@ const rawgetFunction = new LuaBuiltinFunction( }, ); -const rawequalFunction = new LuaBuiltinFunction( - (_sf, a: any, b: any) => { - const av = isTaggedFloat(a) ? a.value : a; - const bv = isTaggedFloat(b) ? b.value : b; - return av === bv; - }, -); +const rawequalFunction = new LuaBuiltinFunction((_sf, a: any, b: any) => { + const av = isTaggedFloat(a) ? a.value : a; + const bv = isTaggedFloat(b) ? b.value : b; + return av === bv; +}); const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => { return (table as any).metatable; @@ -353,12 +341,12 @@ const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => { const dofileFunction = new LuaBuiltinFunction(async (sf, filename: string) => { const global = sf.threadLocal.get("_GLOBAL") as LuaEnv; - const file = await luaCall( + const file = (await luaCall( (global.get("space") as any).get("readFile"), [filename], sf.astCtx!, sf, - ) as Uint8Array; + )) as Uint8Array; const code = new TextDecoder().decode(file); try { const parsedExpr = parse(code); @@ -434,7 +422,8 @@ const nextFunction = new LuaBuiltinFunction( } // Find index in the key list const idx = keys.indexOf(index); - if (idx === -1) { // Not found + if (idx === -1) { + // Not found throw new LuaRuntimeError("invalid key to 'next': key not found", sf); } const key = keys[idx + 1]; diff --git a/client/space_lua/stdlib/format.ts b/client/space_lua/stdlib/format.ts index 7b7f73ac..8daeee67 100644 --- a/client/space_lua/stdlib/format.ts +++ b/client/space_lua/stdlib/format.ts @@ -58,7 +58,8 @@ function parseSpec( // Parse width let width = 0; - if (i < len && fmt.charCodeAt(i) === 42) { // '*' + if (i < len && fmt.charCodeAt(i) === 42) { + // '*' width = -1; i++; } else { @@ -71,10 +72,12 @@ function parseSpec( // Parse precision let hasPrec = false; let prec = 0; - if (i < len && fmt.charCodeAt(i) === 46) { // '.' + if (i < len && fmt.charCodeAt(i) === 46) { + // '.' hasPrec = true; i++; - if (i < len && fmt.charCodeAt(i) === 42) { // '*' + if (i < len && fmt.charCodeAt(i) === 42) { + // '*' prec = -1; i++; } else { @@ -109,9 +112,10 @@ function parseSpec( function pad(s: string, width: number, flags: number, numPad: boolean): string { if (width <= 0 || s.length >= width) return s; const n = width - s.length; - if (numPad && (flags & FLAG_ZERO) && !(flags & FLAG_MINUS)) { + if (numPad && flags & FLAG_ZERO && !(flags & FLAG_MINUS)) { let signLen = 0; - if (s.charCodeAt(0) === 45 || s.charCodeAt(0) === 43) { // '-' or '+' + if (s.charCodeAt(0) === 45 || s.charCodeAt(0) === 43) { + // '-' or '+' signLen = 1; } else if ( s.charCodeAt(0) === 48 && @@ -235,14 +239,17 @@ function formatFloat(n: number, spec: FormatSpec): string { let body: string; - if (lower === 102) { // 'f' + if (lower === 102) { + // 'f' body = abs.toFixed(prec); - } else if (lower === 101) { // 'e' + } else if (lower === 101) { + // 'e' body = abs.toExponential(prec); // Ensure exponent has at least 2 digits body = ensureExpTwoDigits(body); - } else { // 'g' - const gPrec = (prec === 0) ? 1 : prec; + } else { + // 'g' + const gPrec = prec === 0 ? 1 : prec; if (abs === 0) { body = "0"; } else { @@ -268,7 +275,7 @@ function formatFloat(n: number, spec: FormatSpec): string { } // Alt flag for 'f'/'e': ensure decimal point exists - if ((spec.flags & FLAG_HASH) && lower !== 103) { + if (spec.flags & FLAG_HASH && lower !== 103) { if (body.indexOf(".") === -1) { // Insert dot before 'e' if present, else append const eIdx = body.indexOf("e"); @@ -283,7 +290,7 @@ function formatFloat(n: number, spec: FormatSpec): string { } // Alt flag for 'g': keep trailing zeros but ensure decimal point - if ((spec.flags & FLAG_HASH) && lower === 103) { + if (spec.flags & FLAG_HASH && lower === 103) { if (body.indexOf(".") === -1) { const expIdx = findExpIndex(body); if (expIdx !== -1) { @@ -337,7 +344,8 @@ function stripTrailingZerosG(s: string): string { if (dotIdx === -1) return s; // nothing to strip let end = mantissa.length; - while (end > dotIdx + 1 && mantissa.charCodeAt(end - 1) === 48) { // '0' + while (end > dotIdx + 1 && mantissa.charCodeAt(end - 1) === 48) { + // '0' end--; } // Remove dot if nothing after it @@ -389,7 +397,8 @@ function formatHexFloat(n: number, spec: FormatSpec): string { if (pIdx !== -1) { let hasDot = false; for (let k = 0; k < pIdx; k++) { - if (body.charCodeAt(k) === 46) { // '.' + if (body.charCodeAt(k) === 46) { + // '.' hasDot = true; break; } @@ -436,8 +445,8 @@ function hexFloatBody(abs: number, spec: FormatSpec): string { const view = new DataView(buf.buffer); view.setFloat64(0, abs); const bits = view.getBigUint64(0); - const biasedExp = Number((bits >> 52n) & 0x7FFn); - const frac = bits & 0xFFFFFFFFFFFFFn; + const biasedExp = Number((bits >> 52n) & 0x7ffn); + const frac = bits & 0xfffffffffffffn; let exponent: number; let mantBits: bigint; @@ -557,7 +566,8 @@ function padHexRight(s: string, len: number): string { function stripHexTrailingZeros(s: string): string { let end = s.length; - while (end > 0 && s.charCodeAt(end - 1) === 48) { // '0' + while (end > 0 && s.charCodeAt(end - 1) === 48) { + // '0' end--; } if (end === s.length) return s; @@ -669,7 +679,8 @@ export function luaFormat(fmt: string, ...args: any[]): string { while (i < len) { const c = fmt.charCodeAt(i); - if (c !== 37) { // not '%' + if (c !== 37) { + // not '%' // Fast path: scan for next '%' or end let j = i + 1; while (j < len && fmt.charCodeAt(j) !== 37) j++; @@ -743,15 +754,18 @@ export function luaFormat(fmt: string, ...args: any[]): string { false, ); break; - case 112: { // 'p' + case 112: { + // 'p' out += formatPointer(args[ai++], spec); break; } - case 113: { // 'q' + case 113: { + // 'q' out += formatQ(args[ai++]); break; } - case 115: { // 's' + case 115: { + // 's' let s = String(args[ai++]); if (spec.hasPrec && s.length > spec.prec) { s = s.slice(0, spec.prec); diff --git a/client/space_lua/stdlib/js.ts b/client/space_lua/stdlib/js.ts index 32116051..11cab912 100644 --- a/client/space_lua/stdlib/js.ts +++ b/client/space_lua/stdlib/js.ts @@ -12,13 +12,9 @@ export const jsApi = new LuaTable({ * @param args - The arguments to pass to the constructor. * @returns The new instance. */ - new: new LuaBuiltinFunction( - (sf, constructorFn: any, ...args) => { - return new constructorFn( - ...args.map((v) => luaValueToJS(v, sf)), - ); - }, - ), + new: new LuaBuiltinFunction((sf, constructorFn: any, ...args) => { + return new constructorFn(...args.map((v) => luaValueToJS(v, sf))); + }), /** * Imports a JavaScript module. * @param url - The URL of the module to import. diff --git a/client/space_lua/stdlib/load.ts b/client/space_lua/stdlib/load.ts index 76783f4a..6c84ecef 100644 --- a/client/space_lua/stdlib/load.ts +++ b/client/space_lua/stdlib/load.ts @@ -18,9 +18,7 @@ export function luaLoad(code: LuaValue, sf: LuaStackFrame): LuaValue { // Be vocal when no _GLOBAL is set if (!globalEnvMaybe) { - console.warn( - "load() called without _GLOBAL in thread-local environment", - ); + console.warn("load() called without _GLOBAL in thread-local environment"); return new LuaMultiRes([null, "Global environment not set"]); } diff --git a/client/space_lua/stdlib/math.ts b/client/space_lua/stdlib/math.ts index df113a9b..58c8d2f9 100644 --- a/client/space_lua/stdlib/math.ts +++ b/client/space_lua/stdlib/math.ts @@ -60,7 +60,8 @@ export const mathApi = new LuaTable({ } if (typeof x === "string") { const n = untagNumber(x); // Number(x) coerces the string - if (Number.isNaN(n) || !Number.isFinite(n) || !Number.isInteger(n)) return null; + if (Number.isNaN(n) || !Number.isFinite(n) || !Number.isInteger(n)) + return null; return n; } return null; @@ -100,15 +101,15 @@ export const mathApi = new LuaTable({ ceil: new LuaBuiltinFunction((_sf, x: number) => Math.ceil(untagNumber(x))), floor: new LuaBuiltinFunction((_sf, x: number) => Math.floor(untagNumber(x))), max: new LuaBuiltinFunction((_sf, ...args: number[]) => - Math.max(...args.map(untagNumber)) + Math.max(...args.map(untagNumber)), ), min: new LuaBuiltinFunction((_sf, ...args: number[]) => - Math.min(...args.map(untagNumber)) + Math.min(...args.map(untagNumber)), ), // Rounding and remainder - fmod: new LuaBuiltinFunction((_sf, x: number, y: number) => - untagNumber(x) % untagNumber(y) + fmod: new LuaBuiltinFunction( + (_sf, x: number, y: number) => untagNumber(x) % untagNumber(y), ), modf: new LuaBuiltinFunction((_sf, x: number) => { const xn = untagNumber(x); @@ -141,8 +142,8 @@ export const mathApi = new LuaTable({ }), // Returns m * 2^e (the inverse of frexp). Mirrors C99/Lua. - ldexp: new LuaBuiltinFunction((_sf, m: number, e: number) => - untagNumber(m) * 2 ** untagNumber(e) + ldexp: new LuaBuiltinFunction( + (_sf, m: number, e: number) => untagNumber(m) * 2 ** untagNumber(e), ), // Power and logarithms @@ -154,8 +155,8 @@ export const mathApi = new LuaTable({ return Math.log(untagNumber(x)) / Math.log(untagNumber(base)); }), // Power function (deprecated in Lua 5.4 but retained for compatibility) - pow: new LuaBuiltinFunction((_sf, x: number, y: number) => - untagNumber(x) ** untagNumber(y) + pow: new LuaBuiltinFunction( + (_sf, x: number, y: number) => untagNumber(x) ** untagNumber(y), ), sqrt: new LuaBuiltinFunction((_sf, x: number) => Math.sqrt(untagNumber(x))), @@ -178,14 +179,14 @@ export const mathApi = new LuaTable({ tanh: new LuaBuiltinFunction((_sf, x: number) => Math.tanh(untagNumber(x))), // Additional utility - deg: new LuaBuiltinFunction((_sf, x: number) => - untagNumber(x) * 180 / Math.PI + deg: new LuaBuiltinFunction( + (_sf, x: number) => (untagNumber(x) * 180) / Math.PI, ), - rad: new LuaBuiltinFunction((_sf, x: number) => - untagNumber(x) * Math.PI / 180 + rad: new LuaBuiltinFunction( + (_sf, x: number) => (untagNumber(x) * Math.PI) / 180, ), ult: new LuaBuiltinFunction((_sf, m: number, n: number) => { - return (untagNumber(m) >>> 0) < (untagNumber(n) >>> 0); + return untagNumber(m) >>> 0 < untagNumber(n) >>> 0; }), // Keep the cosineSimilarity utility function diff --git a/client/space_lua/stdlib/net.ts b/client/space_lua/stdlib/net.ts index 8927168f..477847d8 100644 --- a/client/space_lua/stdlib/net.ts +++ b/client/space_lua/stdlib/net.ts @@ -14,17 +14,18 @@ export const netApi = new LuaTable({ ): Promise => { // JSONify any non-serializable body if ( - options?.body && typeof options.body !== "string" && + options?.body && + typeof options.body !== "string" && !(options.body instanceof Uint8Array) ) { options.body = JSON.stringify(options.body); } const fetchOptions = options ? { - method: options.method, - headers: {} as Record, - body: options.body, - } + method: options.method, + headers: {} as Record, + body: options.body, + } : {}; fetchOptions.headers = buildProxyHeaders(options.headers); const resp = await client.httpSpacePrimitives.authenticatedFetch( @@ -41,7 +42,8 @@ export const netApi = new LuaTable({ } // Do sensible things with the body based on the content type let body: any; - const contentTypeHeader = options.responseEncoding || + const contentTypeHeader = + options.responseEncoding || resp.headers.get("x-proxy-header-content-type"); const statusCode = +(resp.headers.get("x-proxy-status-code") || "200"); if (contentTypeHeader?.startsWith("application/json")) { @@ -85,8 +87,11 @@ export const netApi = new LuaTable({ function buildProxyUrl(client: Client, url: string) { url = url.replace(/^https?:\/\//, ""); // Strip off the /.fs and replace with /.proxy - return client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) + - "/.proxy/" + url; + return ( + client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) + + "/.proxy/" + + url + ); } function buildProxyHeaders(headers?: Record): Record { @@ -100,9 +105,7 @@ function buildProxyHeaders(headers?: Record): Record { return newHeaders; } -function extractProxyHeaders( - headers: Headers, -): Record { +function extractProxyHeaders(headers: Headers): Record { const newHeaders: Record = {}; for (const [key, value] of headers.entries()) { if (key.toLowerCase().startsWith("x-proxy-header-")) { diff --git a/client/space_lua/stdlib/os.ts b/client/space_lua/stdlib/os.ts index b2aabdd4..0d21396e 100644 --- a/client/space_lua/stdlib/os.ts +++ b/client/space_lua/stdlib/os.ts @@ -16,9 +16,7 @@ function weekNumber( if (iso) { const target = new Date(date); - target.setUTCDate( - target.getUTCDate() + 3 - ((target.getUTCDay() + 6) % 7), - ); + target.setUTCDate(target.getUTCDate() + 3 - ((target.getUTCDay() + 6) % 7)); const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 4)); const weekStart = new Date(yearStart); @@ -34,11 +32,10 @@ function weekNumber( const offset = (7 + (startDay - weekStartDay)) % 7; const firstWeekStart = new Date(yearStart); - firstWeekStart.setUTCDate(yearStart.getUTCDate() + (7 - offset) % 7); + firstWeekStart.setUTCDate(yearStart.getUTCDate() + ((7 - offset) % 7)); if (date < firstWeekStart) return 0; - return 1 + - Math.floor((date.getTime() - firstWeekStart.getTime()) / ONE_WEEK); + return 1 + Math.floor((date.getTime() - firstWeekStart.getTime()) / ONE_WEEK); } function dayOfYear(d: Date, utc: boolean): number { @@ -47,25 +44,25 @@ function dayOfYear(d: Date, utc: boolean): number { const current = utc ? new Date( - Date.UTC( - year, - d.getUTCMonth(), - d.getUTCDate(), - d.getUTCHours(), - d.getUTCMinutes(), - d.getUTCSeconds(), - ), - ) + Date.UTC( + year, + d.getUTCMonth(), + d.getUTCDate(), + d.getUTCHours(), + d.getUTCMinutes(), + d.getUTCSeconds(), + ), + ) : new Date( - Date.UTC( - year, - d.getMonth(), - d.getDate(), - d.getHours(), - d.getMinutes(), - d.getSeconds(), - ), - ); + Date.UTC( + year, + d.getMonth(), + d.getDate(), + d.getHours(), + d.getMinutes(), + d.getSeconds(), + ), + ); return Math.floor((current.getTime() - start.getTime()) / ONE_DAY); } @@ -76,9 +73,7 @@ function isoWeekYear(d: Date, utc: boolean): number { const day = utc ? d.getUTCDate() : d.getDate(); const target = new Date(Date.UTC(year, month, day)); - target.setUTCDate( - target.getUTCDate() + 3 - ((target.getUTCDay() + 6) % 7), - ); + target.setUTCDate(target.getUTCDate() + 3 - ((target.getUTCDay() + 6) % 7)); return target.getUTCFullYear(); } @@ -146,107 +141,104 @@ function dateTable(d: Date, utc: boolean): LuaTable { // Build the specifier map for a given `Date` and `utc` flag. // Returns a record mapping single-char specifier to its output string. -function buildSpecMap( - d: Date, - utc: boolean, -): Record string> { +function buildSpecMap(d: Date, utc: boolean): Record string> { const h = () => hr(d, utc); const h12 = () => h() % 12 || 12; const dow = () => wd(d, utc); return { // Date - "Y": () => yr(d, utc).toString(), - "y": () => pad2(yr(d, utc) % 100), - "C": () => pad2(Math.floor(yr(d, utc) / 100)), - "m": () => pad2(mo(d, utc) + 1), - "d": () => pad2(da(d, utc)), - "e": () => da(d, utc).toString().padStart(2, " "), - "j": () => pad3(dayOfYear(d, utc)), + Y: () => yr(d, utc).toString(), + y: () => pad2(yr(d, utc) % 100), + C: () => pad2(Math.floor(yr(d, utc) / 100)), + m: () => pad2(mo(d, utc) + 1), + d: () => pad2(da(d, utc)), + e: () => da(d, utc).toString().padStart(2, " "), + j: () => pad3(dayOfYear(d, utc)), // Time - "H": () => pad2(h()), - "I": () => pad2(h12()), - "M": () => pad2(mi(d, utc)), - "S": () => pad2(sc(d, utc)), - "p": () => h() >= 12 ? "PM" : "AM", + H: () => pad2(h()), + I: () => pad2(h12()), + M: () => pad2(mi(d, utc)), + S: () => pad2(sc(d, utc)), + p: () => (h() >= 12 ? "PM" : "AM"), // Weekday - "A": () => + A: () => d.toLocaleString("en-US", { weekday: "long", ...(utc ? { timeZone: "UTC" } : {}), }), - "a": () => + a: () => d.toLocaleString("en-US", { weekday: "short", ...(utc ? { timeZone: "UTC" } : {}), }), - "w": () => dow().toString(), - "u": () => (dow() === 0 ? 7 : dow()).toString(), + w: () => dow().toString(), + u: () => (dow() === 0 ? 7 : dow()).toString(), // Month name - "b": () => + b: () => d.toLocaleString("en-US", { month: "short", ...(utc ? { timeZone: "UTC" } : {}), }), - "h": () => + h: () => d.toLocaleString("en-US", { month: "short", ...(utc ? { timeZone: "UTC" } : {}), }), - "B": () => + B: () => d.toLocaleString("en-US", { month: "long", ...(utc ? { timeZone: "UTC" } : {}), }), // Week number - "U": () => pad2(weekNumber(d, utc, 0, false)), - "W": () => pad2(weekNumber(d, utc, 1, false)), - "V": () => pad2(weekNumber(d, utc, 1, true)), - "G": () => isoWeekYear(d, utc).toString(), - "g": () => pad2(isoWeekYear(d, utc) % 100), + U: () => pad2(weekNumber(d, utc, 0, false)), + W: () => pad2(weekNumber(d, utc, 1, false)), + V: () => pad2(weekNumber(d, utc, 1, true)), + G: () => isoWeekYear(d, utc).toString(), + g: () => pad2(isoWeekYear(d, utc) % 100), // Composite specifiers - "c": () => + c: () => d.toLocaleString("en-US", { ...(utc ? { timeZone: "UTC" } : {}), }), - "x": () => + x: () => d.toLocaleDateString("en-US", { ...(utc ? { timeZone: "UTC" } : {}), }), - "X": () => + X: () => d.toLocaleTimeString("en-US", { ...(utc ? { timeZone: "UTC" } : {}), }), - "D": () => + D: () => `${pad2(mo(d, utc) + 1)}/${pad2(da(d, utc))}/${pad2(yr(d, utc) % 100)}`, - "F": () => + F: () => `${yr(d, utc).toString()}-${pad2(mo(d, utc) + 1)}-${pad2(da(d, utc))}`, - "R": () => `${pad2(h())}:${pad2(mi(d, utc))}`, - "T": () => `${pad2(h())}:${pad2(mi(d, utc))}:${pad2(sc(d, utc))}`, - "r": () => + R: () => `${pad2(h())}:${pad2(mi(d, utc))}`, + T: () => `${pad2(h())}:${pad2(mi(d, utc))}:${pad2(sc(d, utc))}`, + r: () => `${pad2(h12())}:${pad2(mi(d, utc))}:${pad2(sc(d, utc))} ${ h() >= 12 ? "PM" : "AM" }`, // Epoch - "s": () => Math.floor(d.getTime() / 1000).toString(), + s: () => Math.floor(d.getTime() / 1000).toString(), // Whitespace - "n": () => "\n", - "t": () => "\t", + n: () => "\n", + t: () => "\t", // Timezone - "Z": () => { + Z: () => { if (utc) return "UTC"; const match = d.toTimeString().match(/\((.*)\)/); return match ? match[1] : ""; }, - "z": () => { + z: () => { if (utc) return "+0000"; const offset = -d.getTimezoneOffset(); const sign = offset >= 0 ? "+" : "-"; @@ -344,27 +336,26 @@ export const osApi = new LuaTable({ // Otherwise, format specifiers follow ISO C `strftime`. // // If format is absent, it defaults to `%c`. - date: new LuaBuiltinFunction( - (_sf, format?: string, timestamp?: number) => { - let fmt = format ?? "%c"; - let utc = false; + date: new LuaBuiltinFunction((_sf, format?: string, timestamp?: number) => { + let fmt = format ?? "%c"; + let utc = false; - if (fmt.startsWith("!")) { - utc = true; - fmt = fmt.slice(1); - } + if (fmt.startsWith("!")) { + utc = true; + fmt = fmt.slice(1); + } - const d = timestamp !== undefined && timestamp !== null + const d = + timestamp !== undefined && timestamp !== null ? new Date(timestamp * 1000) : new Date(); - if (fmt === "*t") { - return dateTable(d, utc); - } + if (fmt === "*t") { + return dateTable(d, utc); + } - return luaFormatTime(fmt, d, utc); - }, - ), + return luaFormatTime(fmt, d, utc); + }), // Returns an approximation of CPU time used by the program in seconds. clock: new LuaBuiltinFunction((_sf): number => { diff --git a/client/space_lua/stdlib/pattern.ts b/client/space_lua/stdlib/pattern.ts index c4c5fce7..ac765a17 100644 --- a/client/space_lua/stdlib/pattern.ts +++ b/client/space_lua/stdlib/pattern.ts @@ -54,7 +54,7 @@ function toBytes(s: string): Uint8Array { const len = s.length; const arr = new Uint8Array(len); for (let i = 0; i < len; i++) { - arr[i] = s.charCodeAt(i) & 0xFF; + arr[i] = s.charCodeAt(i) & 0xff; } return arr; } @@ -79,8 +79,11 @@ function matchClass(c: number, cl: number): boolean { res = c >= 97 && c <= 122; break; case 112: // 'p' - res = (c >= 33 && c <= 47) || (c >= 58 && c <= 64) || - (c >= 91 && c <= 96) || (c >= 123 && c <= 126); + res = + (c >= 33 && c <= 47) || + (c >= 58 && c <= 64) || + (c >= 91 && c <= 96) || + (c >= 123 && c <= 126); break; case 115: // 's' res = c === 32 || (c >= 9 && c <= 13); @@ -89,12 +92,12 @@ function matchClass(c: number, cl: number): boolean { res = c >= 65 && c <= 90; break; case 119: // 'w' - res = (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || - (c >= 48 && c <= 57); + res = + (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57); break; case 120: // 'x' - res = (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || - (c >= 97 && c <= 102); + res = + (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102); break; case 122: // 'z' res = c === 0; @@ -102,7 +105,7 @@ function matchClass(c: number, cl: number): boolean { default: return cl === c; } - return (cl >= 97 && cl <= 122) ? res : !res; + return cl >= 97 && cl <= 122 ? res : !res; } function classEnd(p: Uint8Array, plen: number, pi: number): number { @@ -150,9 +153,7 @@ function matchBracketClass( if (pch === CH_ESC) { pi++; if (matchClass(c, p[pi])) return sig; - } else if ( - pi + 2 < ec && p[pi + 1] === CH_DASH - ) { + } else if (pi + 2 < ec && p[pi + 1] === CH_DASH) { pi += 2; if (pch <= c && c <= p[pi]) return sig; } else if (pch === c) { @@ -182,11 +183,7 @@ function singleMatch( return pch === c; } -function matchBalance( - ms: MatchState, - si: number, - pi: number, -): number { +function matchBalance(ms: MatchState, si: number, pi: number): number { if (pi >= ms.plen - 1) { throw new Error("malformed pattern (missing arguments to '%b')"); } @@ -209,12 +206,7 @@ function matchBalance( return -1; } -function maxExpand( - ms: MatchState, - si: number, - pi: number, - ep: number, -): number { +function maxExpand(ms: MatchState, si: number, pi: number, ep: number): number { let i = 0; while (singleMatch(ms, si + i, pi, ep)) i++; while (i >= 0) { @@ -225,12 +217,7 @@ function maxExpand( return -1; } -function minExpand( - ms: MatchState, - si: number, - pi: number, - ep: number, -): number { +function minExpand(ms: MatchState, si: number, pi: number, ep: number): number { for (;;) { const res = match(ms, si, ep + 1); if (res >= 0) return res; @@ -326,7 +313,8 @@ function match(ms: MatchState, si: number, pi: number): number { } if (pch === CH_ESC && pi + 1 < ms.plen) { const next = ms.p[pi + 1]; - if (next === 98) { // 'b' + if (next === 98) { + // 'b' si = matchBalance(ms, si, pi + 2); if (si >= 0) { pi += 4; @@ -335,7 +323,8 @@ function match(ms: MatchState, si: number, pi: number): number { ms.matchdepth++; return -1; } - if (next === 102) { // 'f' + if (next === 102) { + // 'f' pi += 2; if (pi >= ms.plen || ms.p[pi] !== CH_LBRACKET) { throw new Error("missing '[' after '%f' in pattern"); @@ -370,7 +359,9 @@ function match(ms: MatchState, si: number, pi: number): number { if (ep < ms.plen) { const suffix = ms.p[ep]; if ( - suffix === CH_STAR || suffix === CH_QUESTION || suffix === CH_DASH + suffix === CH_STAR || + suffix === CH_QUESTION || + suffix === CH_DASH ) { pi = ep + 1; continue; @@ -614,10 +605,12 @@ function expandReplacementString( } else if (rc === CH_0) { parts.push(ms.src.substring(matchStart, matchEnd)); } else if (rc >= 49 && rc <= CH_9) { - parts.push(getRawCaptureString( - ms, - getOneRawCapture(ms, rc - 49, matchStart, matchEnd), - )); + parts.push( + getRawCaptureString( + ms, + getOneRawCapture(ms, rc - 49, matchStart, matchEnd), + ), + ); } else { throw new Error("invalid use of '%' in replacement string"); } diff --git a/client/space_lua/stdlib/prng.ts b/client/space_lua/stdlib/prng.ts index 82301866..850c9672 100644 --- a/client/space_lua/stdlib/prng.ts +++ b/client/space_lua/stdlib/prng.ts @@ -10,7 +10,7 @@ export class LuaPRNG { private rotl(x: bigint, k: number): bigint { k = k & 63; - return ((x << BigInt(k)) | (x >> BigInt(64 - k))) & 0xFFFFFFFFFFFFFFFFn; + return ((x << BigInt(k)) | (x >> BigInt(64 - k))) & 0xffffffffffffffffn; } private nextrand(): bigint { @@ -20,10 +20,11 @@ export class LuaPRNG { const s2 = s[2]; const s3 = s[3]; - const res = this.rotl((s1 * 5n) & 0xFFFFFFFFFFFFFFFFn, 7) * 9n & - 0xFFFFFFFFFFFFFFFFn; + const res = + (this.rotl((s1 * 5n) & 0xffffffffffffffffn, 7) * 9n) & + 0xffffffffffffffffn; - const t = (s1 << 17n) & 0xFFFFFFFFFFFFFFFFn; + const t = (s1 << 17n) & 0xffffffffffffffffn; s[2] = s2 ^ s0; s[3] = s3 ^ s1; s[1] = s1 ^ s[2]; @@ -35,17 +36,17 @@ export class LuaPRNG { } public setSeed(seed1: bigint, seed2: bigint = 0n): [bigint, bigint] { - const MASK = 0xFFFFFFFFFFFFFFFFn; + const MASK = 0xffffffffffffffffn; const s = this.state; const sm64 = (x: bigint): bigint => { - x = (x ^ (x >> 30n)) * 0xBF58476D1CE4E5B9n & MASK; - x = (x ^ (x >> 27n)) * 0x94D049BB133111EBn & MASK; + x = ((x ^ (x >> 30n)) * 0xbf58476d1ce4e5b9n) & MASK; + x = ((x ^ (x >> 27n)) * 0x94d049bb133111ebn) & MASK; return (x ^ (x >> 31n)) & MASK; }; s[0] = sm64(seed1 & MASK); - s[1] = sm64((seed1 & MASK) | 0xFFn); + s[1] = sm64((seed1 & MASK) | 0xffn); s[2] = sm64(seed2 & MASK); s[3] = sm64(0n); @@ -101,9 +102,8 @@ export class LuaPRNG { if (arg2 === undefined) { if (arg1 === 0) { // Raw 64-bit as signed bigint - const signed = rv > 0x7FFFFFFFFFFFFFFFn - ? rv - 0x10000000000000000n - : rv; + const signed = + rv > 0x7fffffffffffffffn ? rv - 0x10000000000000000n : rv; return signed; } if (arg1 < 1) { @@ -133,7 +133,10 @@ export class LuaPRNG { "bad argument #1 to 'randomseed' (number has no integer representation)", ); } - if (arg2 !== undefined && (!Number.isFinite(arg2) || !Number.isInteger(arg2))) { + if ( + arg2 !== undefined && + (!Number.isFinite(arg2) || !Number.isInteger(arg2)) + ) { throw new Error( "bad argument #2 to 'randomseed' (number has no integer representation)", ); diff --git a/client/space_lua/stdlib/space_lua.ts b/client/space_lua/stdlib/space_lua.ts index 3529cb9f..63853cc4 100644 --- a/client/space_lua/stdlib/space_lua.ts +++ b/client/space_lua/stdlib/space_lua.ts @@ -89,10 +89,7 @@ export async function interpolateLuaString( const luaResult = singleResult(await evalExpression(parsedExpr, env, sf)); result += await luaToString(luaResult); } catch (e: any) { - throw new LuaRuntimeError( - `Error evaluating "${expr}": ${e.message}`, - sf, - ); + throw new LuaRuntimeError(`Error evaluating "${expr}": ${e.message}`, sf); } currentIndex = endIndex + 1; @@ -109,11 +106,9 @@ export const spaceluaApi = new LuaTable({ * @param luaExpression - The lua expression to parse. * @returns The parsed expression. */ - parseExpression: new LuaBuiltinFunction( - (_sf, luaExpression: string) => { - return parseExpressionString(luaExpression); - }, - ), + parseExpression: new LuaBuiltinFunction((_sf, luaExpression: string) => { + return parseExpressionString(luaExpression); + }), /** * Evaluates a parsed lua expression and returns the result. * @@ -139,10 +134,8 @@ export const spaceluaApi = new LuaTable({ /** * Returns your SilverBullet instance's base URL */ - baseUrl: new LuaBuiltinFunction( - () => { - //NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;` - return document.baseURI.replace(/\/*$/, ""); - }, - ), + baseUrl: new LuaBuiltinFunction(() => { + //NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;` + return document.baseURI.replace(/\/*$/, ""); + }), }); diff --git a/client/space_lua/stdlib/string.ts b/client/space_lua/stdlib/string.ts index 1b1736da..217118c6 100644 --- a/client/space_lua/stdlib/string.ts +++ b/client/space_lua/stdlib/string.ts @@ -24,9 +24,7 @@ function capturesToLua(caps: CaptureResult[]): any { const c = caps[0]; return "s" in c ? c.s : c.position; } - return new LuaMultiRes( - caps.map((c) => ("s" in c ? c.s : c.position)), - ); + return new LuaMultiRes(caps.map((c) => ("s" in c ? c.s : c.position))); } export const stringApi = new LuaTable({ @@ -72,13 +70,7 @@ export const stringApi = new LuaTable({ }, ), gsub: new LuaBuiltinFunction( - async ( - sf, - s: string, - pattern: string, - repl: any, - n?: number, - ) => { + async (sf, s: string, pattern: string, repl: any, n?: number) => { const callbacks: GsubCallbacks = {}; if (typeof repl === "string") { callbacks.replString = repl; @@ -121,13 +113,11 @@ export const stringApi = new LuaTable({ upper: new LuaBuiltinFunction((_sf, s: string) => { return luaToString(s.toUpperCase()); }), - match: new LuaBuiltinFunction( - (_sf, s: string, pattern: string, init = 1) => { - const caps = patternMatch(s, pattern, init); - if (!caps) return null; - return capturesToLua(caps); - }, - ), + match: new LuaBuiltinFunction((_sf, s: string, pattern: string, init = 1) => { + const caps = patternMatch(s, pattern, init); + if (!caps) return null; + return capturesToLua(caps); + }), rep: new LuaBuiltinFunction((_sf, s: string, n: number, sep?: string) => { if (n <= 0) return ""; sep = sep ?? ""; diff --git a/client/space_lua/stdlib/string_pack.ts b/client/space_lua/stdlib/string_pack.ts index 13d91e21..9442d209 100644 --- a/client/space_lua/stdlib/string_pack.ts +++ b/client/space_lua/stdlib/string_pack.ts @@ -73,7 +73,8 @@ function getOption( fmt: string, pos: number, h: Header, -): [KOption, number, number] { // [opt, size, newPos] +): [KOption, number, number] { + // [opt, size, newPos] const c = fmt[pos++]; switch (c) { case "b": @@ -171,7 +172,10 @@ function getDetails( let ntoalign = 0; if ( - opt !== "char" && opt !== "nop" && opt !== "padding" && opt !== "paddalign" + opt !== "char" && + opt !== "nop" && + opt !== "padding" && + opt !== "paddalign" ) { const realign = Math.min(align, h.maxalign); if (realign > 0) { @@ -381,7 +385,9 @@ export const strUnpackFn = new LuaBuiltinFunction( if (opt.ntoalign + opt.size > buf.length - pos) { if ( - opt.opt !== "nop" && opt.opt !== "paddalign" && opt.opt !== "padding" + opt.opt !== "nop" && + opt.opt !== "paddalign" && + opt.opt !== "padding" ) { throw new LuaRuntimeError("data string too short", sf); } @@ -464,23 +470,21 @@ export const strUnpackFn = new LuaBuiltinFunction( }, ); -export const strPackSizeFn = new LuaBuiltinFunction( - (_sf, fmt: string) => { - const h = makeHeader(); - let totalsize = 0; - let pos = 0; +export const strPackSizeFn = new LuaBuiltinFunction((_sf, fmt: string) => { + const h = makeHeader(); + let totalsize = 0; + let pos = 0; - while (pos < fmt.length) { - let opt: ParsedOption; - [opt, pos] = getDetails(fmt, pos, h, totalsize); + while (pos < fmt.length) { + let opt: ParsedOption; + [opt, pos] = getDetails(fmt, pos, h, totalsize); - if (opt.opt === "string" || opt.opt === "zstr") { - throw new LuaRuntimeError("variable-length format", _sf); - } - - totalsize += opt.ntoalign + opt.size; + if (opt.opt === "string" || opt.opt === "zstr") { + throw new LuaRuntimeError("variable-length format", _sf); } - return totalsize; - }, -); + totalsize += opt.ntoalign + opt.size; + } + + return totalsize; +}); diff --git a/client/space_lua/stdlib/table.ts b/client/space_lua/stdlib/table.ts index 73bce11f..37bdcf70 100644 --- a/client/space_lua/stdlib/table.ts +++ b/client/space_lua/stdlib/table.ts @@ -84,9 +84,8 @@ export const tableApi = new LuaTable({ return luaFormatNumber(v.value, "float"); } - const ty = typeof v === "object" && v instanceof LuaTable - ? "table" - : typeof v; + const ty = + typeof v === "object" && v instanceof LuaTable ? "table" : typeof v; throw new LuaRuntimeError( `invalid value (${ty}) at index ${idx} in table for 'concat'`, sf, @@ -301,10 +300,7 @@ export const tableApi = new LuaTable({ const ta = typeof av; const tb = typeof bv; - throw new LuaRuntimeError( - `attempt to compare ${ta} with ${tb}`, - sf, - ); + throw new LuaRuntimeError(`attempt to compare ${ta} with ${tb}`, sf); }; const sorted = await asyncQuickSort(values, cmp); @@ -410,7 +406,7 @@ export const tableApi = new LuaTable({ */ unpack: new LuaBuiltinFunction( async (sf, tbl: LuaTable | any[], i?: number, j?: number) => { - i = (i === undefined || i === null) ? 1 : i; + i = i === undefined || i === null ? 1 : i; if (j === undefined || j === null) { j = Array.isArray(tbl) ? tbl.length diff --git a/client/space_lua/tonumber.ts b/client/space_lua/tonumber.ts index 4e3e421f..1e367c6e 100644 --- a/client/space_lua/tonumber.ts +++ b/client/space_lua/tonumber.ts @@ -18,7 +18,8 @@ function skipSpace(s: string, i: number): number { const n = s.length; while (i < n) { const c = s.charCodeAt(i); - if (c === 32 || (c >= 9 && c <= 13)) { // SP, HT, LF, VT, FF, CR + if (c === 32 || (c >= 9 && c <= 13)) { + // SP, HT, LF, VT, FF, CR i++; } else { break; @@ -28,21 +29,24 @@ function skipSpace(s: string, i: number): number { } function charToDigitBase(c: number, base: number): number { - if (c >= 48 && c <= 57) { // '0'..'9' + if (c >= 48 && c <= 57) { + // '0'..'9' const v = c - 48; if (v < base) { return v; } return -1; } - if (c >= 65 && c <= 90) { // 'A'..'Z' + if (c >= 65 && c <= 90) { + // 'A'..'Z' const v = 10 + (c - 65); if (v < base) { return v; } return -1; } - if (c >= 97 && c <= 122) { // 'a'..'z' + if (c >= 97 && c <= 122) { + // 'a'..'z' const v = 10 + (c - 97); if (v < base) { return v; @@ -64,10 +68,12 @@ function parseIntWithBase( } let sign = 1; - if (s.charCodeAt(i) === 45) { // '-' + if (s.charCodeAt(i) === 45) { + // '-' sign = -1; i++; - } else if (s.charCodeAt(i) === 43) { // '+' + } else if (s.charCodeAt(i) === 43) { + // '+' i++; } @@ -80,7 +86,8 @@ function parseIntWithBase( while (i < n) { const c = s.charCodeAt(i); - const isAlnum = (c >= 48 && c <= 57) || // '0'..'9' + const isAlnum = + (c >= 48 && c <= 57) || // '0'..'9' (c >= 65 && c <= 90) || // 'A'..'Z' (c >= 97 && c <= 122); // 'a'..'z' @@ -119,10 +126,12 @@ function parseInt(s: string): { ok: boolean; value: number } { } let neg = false; - if (s.charCodeAt(i) === 45) { // '-' + if (s.charCodeAt(i) === 45) { + // '-' neg = true; i++; - } else if (s.charCodeAt(i) === 43) { // '+' + } else if (s.charCodeAt(i) === 43) { + // '+' i++; } @@ -134,18 +143,23 @@ function parseInt(s: string): { ok: boolean; value: number } { let any = false; // hex? - if (s.charCodeAt(i) === 48 && i + 1 < n) { // '0' + if (s.charCodeAt(i) === 48 && i + 1 < n) { + // '0' const x = s.charCodeAt(i + 1); - if (x === 120 || x === 88) { // 'x' or 'X' + if (x === 120 || x === 88) { + // 'x' or 'X' i += 2; while (i < n) { const c = s.charCodeAt(i); let d = -1; - if (c >= 48 && c <= 57) { // '0'..'9' + if (c >= 48 && c <= 57) { + // '0'..'9' d = c - 48; // '0' - } else if (c >= 65 && c <= 70) { // 'A'..'F' + } else if (c >= 65 && c <= 70) { + // 'A'..'F' d = 10 + (c - 65); - } else if (c >= 97 && c <= 102) { // 'a'..'f' + } else if (c >= 97 && c <= 102) { + // 'a'..'f' d = 10 + (c - 97); } else { d = -1; @@ -168,7 +182,8 @@ function parseInt(s: string): { ok: boolean; value: number } { // decimal integer while (i < n) { const c = s.charCodeAt(i); - if (c < 48 || c > 57) { // not '0'..'9' + if (c < 48 || c > 57) { + // not '0'..'9' break; } acc = acc * 10 + (c - 48); @@ -193,10 +208,12 @@ function parseDecFloat(s: string): { ok: boolean; value: number } { let sign = 1; const c0 = s.charCodeAt(i); - if (c0 === 45) { // '-' + if (c0 === 45) { + // '-' sign = -1; i++; - } else if (c0 === 43) { // '+' + } else if (c0 === 43) { + // '+' i++; } @@ -207,7 +224,8 @@ function parseDecFloat(s: string): { ok: boolean; value: number } { // integer part while (i < n) { const c = s.charCodeAt(i); - if (c < 48 || c > 57) { // not '0'..'9' + if (c < 48 || c > 57) { + // not '0'..'9' break; } val = val * 10 + (c - 48); @@ -217,12 +235,14 @@ function parseDecFloat(s: string): { ok: boolean; value: number } { // fractional part if (i < n) { - if (s.charCodeAt(i) === 46) { // '.' + if (s.charCodeAt(i) === 46) { + // '.' i++; let scale = 1; while (i < n) { const c = s.charCodeAt(i); - if (c < 48 || c > 57) { // not '0'..'9' + if (c < 48 || c > 57) { + // not '0'..'9' break; } scale *= 0.1; @@ -238,7 +258,8 @@ function parseDecFloat(s: string): { ok: boolean; value: number } { let hasExp = false; if (i < n) { const ec = s.charCodeAt(i); - if (ec === 101 || ec === 69) { // 'e' or 'E' + if (ec === 101 || ec === 69) { + // 'e' or 'E' hasExp = true; i++; if (i >= n) { @@ -247,10 +268,12 @@ function parseDecFloat(s: string): { ok: boolean; value: number } { let expSign = 1; if (i < n) { const es = s.charCodeAt(i); - if (es === 45) { // '-' + if (es === 45) { + // '-' expSign = -1; i++; - } else if (es === 43) { // '+' + } else if (es === 43) { + // '+' i++; } } @@ -260,7 +283,8 @@ function parseDecFloat(s: string): { ok: boolean; value: number } { let anyExp = false; while (i < n) { const c = s.charCodeAt(i); - if (c < 48 || c > 57) { // not '0'..'9' + if (c < 48 || c > 57) { + // not '0'..'9' break; } exp = exp * 10 + (c - 48); @@ -297,18 +321,22 @@ function parseHexFloat(s: string): { ok: boolean; value: number } { let sign = 1; const c0 = s.charCodeAt(i); - if (c0 === 45) { // '-' + if (c0 === 45) { + // '-' sign = -1; i++; - } else if (c0 === 43) { // '+' + } else if (c0 === 43) { + // '+' i++; } - if (!(i + 1 < n && s.charCodeAt(i) === 48)) { // '0' + if (!(i + 1 < n && s.charCodeAt(i) === 48)) { + // '0' return { ok: false, value: 0 }; } const x = s.charCodeAt(i + 1); - if (!(x === 120 || x === 88)) { // 'x' or 'X' + if (!(x === 120 || x === 88)) { + // 'x' or 'X' return { ok: false, value: 0 }; } i += 2; @@ -323,11 +351,14 @@ function parseHexFloat(s: string): { ok: boolean; value: number } { while (i < n) { const c = s.charCodeAt(i); let d = -1; - if (c >= 48 && c <= 57) { // '0'..'9' + if (c >= 48 && c <= 57) { + // '0'..'9' d = c - 48; - } else if (c >= 65 && c <= 70) { // 'A'..'F' + } else if (c >= 65 && c <= 70) { + // 'A'..'F' d = 10 + (c - 65); - } else if (c >= 97 && c <= 102) { // 'a'..'f' + } else if (c >= 97 && c <= 102) { + // 'a'..'f' d = 10 + (c - 97); } else { d = -1; @@ -342,17 +373,21 @@ function parseHexFloat(s: string): { ok: boolean; value: number } { // optional fractional part if (i < n) { - if (s.charCodeAt(i) === 46) { // '.' + if (s.charCodeAt(i) === 46) { + // '.' sawDot = true; i++; while (i < n) { const c = s.charCodeAt(i); let d = -1; - if (c >= 48 && c <= 57) { // '0'..'9' + if (c >= 48 && c <= 57) { + // '0'..'9' d = c - 48; - } else if (c >= 65 && c <= 70) { // 'A'..'F' + } else if (c >= 65 && c <= 70) { + // 'A'..'F' d = 10 + (c - 65); - } else if (c >= 97 && c <= 102) { // 'a'..'f' + } else if (c >= 97 && c <= 102) { + // 'a'..'f' d = 10 + (c - 97); } else { d = -1; @@ -376,14 +411,17 @@ function parseHexFloat(s: string): { ok: boolean; value: number } { if (i < n) { const ec = s.charCodeAt(i); - if (ec === 112 || ec === 80) { // 'p' or 'P' + if (ec === 112 || ec === 80) { + // 'p' or 'P' i++; if (i < n) { const sc = s.charCodeAt(i); - if (sc === 45) { // '-' + if (sc === 45) { + // '-' expSign = -1; i++; - } else if (sc === 43) { // '+' + } else if (sc === 43) { + // '+' i++; } } @@ -394,7 +432,8 @@ function parseHexFloat(s: string): { ok: boolean; value: number } { let anyExp = false; while (i < n) { const c = s.charCodeAt(i); - if (c < 48 || c > 57) { // not '0'..'9' + if (c < 48 || c > 57) { + // not '0'..'9' break; } exp = exp * 10 + (c - 48); @@ -433,7 +472,7 @@ function parseHexFloat(s: string): { ok: boolean; value: number } { return { ok: false, value: 0 }; } - const frac = fracVal === 0 ? 0 : (fracVal / fracScale); + const frac = fracVal === 0 ? 0 : fracVal / fracScale; const result = sign * (intVal + frac) * 2 ** (expSign * exp); return { ok: true, value: result }; } diff --git a/client/space_lua/util.ts b/client/space_lua/util.ts index 63afe52d..a4063dcc 100644 --- a/client/space_lua/util.ts +++ b/client/space_lua/util.ts @@ -6,7 +6,7 @@ export function evalPromiseValues(vals: any[]): Promise | any[] { for (let i = 0; i < vals.length; i++) { if (isPromise(vals[i])) { promises.push( - (vals[i] as Promise).then((v: any) => promiseResults[i] = v), + (vals[i] as Promise).then((v: any) => (promiseResults[i] = v)), ); } else { promiseResults[i] = vals[i]; @@ -32,19 +32,19 @@ async function getPivot( z: any, compare: (a: any, b: any) => Promise, ) { - if (await compare(x, y) < 0) { - if (await compare(y, z) < 0) { + if ((await compare(x, y)) < 0) { + if ((await compare(y, z)) < 0) { return y; } - if (await compare(z, x) < 0) { + if ((await compare(z, x)) < 0) { return x; } return z; } - if (await compare(y, z) > 0) { + if ((await compare(y, z)) > 0) { return y; } - if (await compare(z, x) > 0) { + if ((await compare(z, x)) > 0) { return x; } return z; @@ -65,7 +65,9 @@ export async function asyncQuickSort( right = arr.length - 1, ) { if (left < right) { - let i = left, j = right, tmp; + let i = left, + j = right, + tmp; const pivot = await getPivot( arr[i], arr[i + Math.floor((j - i) / 2)], @@ -73,10 +75,10 @@ export async function asyncQuickSort( compare, ); while (true) { - while (await compare(arr[i], pivot) < 0) { + while ((await compare(arr[i], pivot)) < 0) { i++; } - while (await compare(pivot, arr[j]) < 0) { + while ((await compare(pivot, arr[j])) < 0) { j--; } if (i >= j) { @@ -116,7 +118,9 @@ export async function asyncMergeSort( const mid = Math.min(left + size, n); const right = Math.min(left + 2 * size, n); - let i = left, j = mid, k = left; + let i = left, + j = mid, + k = left; while (i < mid && j < right) { const cmp = await compare(arr[i], arr[j]); if (cmp <= 0) work[k++] = arr[i++]; diff --git a/client/space_lua_api.ts b/client/space_lua_api.ts index 3d1323fe..f6888df2 100644 --- a/client/space_lua_api.ts +++ b/client/space_lua_api.ts @@ -38,11 +38,11 @@ export function exposeSyscalls(env: LuaEnv, system: System) { } const luaFn = isLuaNativeSyscall ? new LuaBuiltinFunction((_sf, ...args) => { - return system.localSyscall(syscallName, args); - }) + return system.localSyscall(syscallName, args); + }) : new LuaNativeJSFunction((...args) => { - return system.localSyscall(syscallName, args); - }); + return system.localSyscall(syscallName, args); + }); env.get(ns, nativeFs).set(fn, luaFn, nativeFs); } } @@ -70,31 +70,21 @@ export async function buildThreadLocalEnv( } export async function handleLuaError(e: LuaRuntimeError, system: System) { - console.error( - "Lua eval exception", - e.message, - e.sf?.astCtx, - ); + console.error("Lua eval exception", e.message, e.sf?.astCtx); if (e.sf?.astCtx?.ref) { // We got an error and actually know where it came from, let's navigate there to help debugging - await system.localSyscall( - "editor.flashNotification", - [ - `Lua error: ${e.message}`, - "error", - ], - ); + await system.localSyscall("editor.flashNotification", [ + `Lua error: ${e.message}`, + "error", + ]); const ref = resolveASTReference(e.sf.astCtx); if (!ref) return; - await system.localSyscall( - "editor.flashNotification", - [ - `Navigating to the place in the code where this error occurred in ${ref.path}`, - "info", - ], - ); + await system.localSyscall("editor.flashNotification", [ + `Navigating to the place in the code where this error occurred in ${ref.path}`, + "info", + ]); await system.localSyscall("editor.navigate", [ref]); } } diff --git a/client/spaces/checked_space_primitives.ts b/client/spaces/checked_space_primitives.ts index 6e3a019e..7a750c32 100644 --- a/client/spaces/checked_space_primitives.ts +++ b/client/spaces/checked_space_primitives.ts @@ -11,12 +11,11 @@ export class CheckedSpacePrimitives implements SpacePrimitives { constructor( private wrapped: SpacePrimitives, private readOnly: boolean, - ) { - } + ) {} async fetchFileList(): Promise { return (await this.wrapped.fetchFileList()).filter(({ name }) => - this.isReadable(name) + this.isReadable(name), ); } @@ -39,7 +38,7 @@ export class CheckedSpacePrimitives implements SpacePrimitives { data: Uint8Array, meta?: FileMeta, ): Promise { - if (!await this.isWritable(path)) { + if (!(await this.isWritable(path))) { throw new Error("Couldn't write file, path is not writable"); } return this.wrapped.writeFile(path, data, meta); @@ -49,7 +48,7 @@ export class CheckedSpacePrimitives implements SpacePrimitives { // We allow deletion of paths we can't write to. This is for the case when // the user has an invalidly named file in their space and they need to // remove it/rename it - if (!await this.isWritable(path)) { + if (!(await this.isWritable(path))) { throw new Error("Couldn't delete file, path is not writable"); } return this.wrapped.deleteFile(path); diff --git a/client/spaces/datastore_space_primitives.ts b/client/spaces/datastore_space_primitives.ts index 2a393853..d16135ee 100644 --- a/client/spaces/datastore_space_primitives.ts +++ b/client/spaces/datastore_space_primitives.ts @@ -8,10 +8,7 @@ const filesMetaPrefix = ["meta"]; const filesContentPrefix = ["content"]; export class DataStoreSpacePrimitives implements SpacePrimitives { - constructor( - private kv: KvPrimitives, - ) { - } + constructor(private kv: KvPrimitives) {} async fetchFileList(): Promise { const fileMetas: FileMeta[] = []; @@ -22,19 +19,11 @@ export class DataStoreSpacePrimitives implements SpacePrimitives { return fileMetas; } - async readFile( - path: string, - ): Promise<{ data: Uint8Array; meta: FileMeta }> { + async readFile(path: string): Promise<{ data: Uint8Array; meta: FileMeta }> { // Fetch content and metadata in parallel const [fileMeta, fileContent] = await this.kv.batchGet([ - [ - ...filesMetaPrefix, - path, - ], - [ - ...filesContentPrefix, - path, - ], + [...filesMetaPrefix, path], + [...filesContentPrefix, path], ]); if (!fileMeta) { throw notFoundError; @@ -91,9 +80,7 @@ export class DataStoreSpacePrimitives implements SpacePrimitives { } async deleteFile(path: string): Promise { - const [fileMeta] = await this.kv.batchGet([ - [...filesMetaPrefix, path], - ]); + const [fileMeta] = await this.kv.batchGet([[...filesMetaPrefix, path]]); if (!fileMeta) { throw notFoundError; } diff --git a/client/spaces/evented_space_primitives.ts b/client/spaces/evented_space_primitives.ts index c97c6bd8..14d35610 100644 --- a/client/spaces/evented_space_primitives.ts +++ b/client/spaces/evented_space_primitives.ts @@ -30,8 +30,7 @@ export class EventedSpacePrimitives implements SpacePrimitives { private eventHook: EventHook, private ds: DataStore, private snapshotKey = ["$spaceSnapshot"], - ) { - } + ) {} async enable() { console.log("Loading snapshot and enabling events"); @@ -103,14 +102,10 @@ export class EventedSpacePrimitives implements SpacePrimitives { // Check what happened to the file if ( - ( - // New file scenario - !oldHash - ) || ( - // Changed file scenario - oldHash && - oldHash !== newHash - ) + // New file scenario + !oldHash || + // Changed file scenario + (oldHash && oldHash !== newHash) ) { console.log( "Detected file change during listing", @@ -118,12 +113,7 @@ export class EventedSpacePrimitives implements SpacePrimitives { oldHash, newHash, ); - await this.dispatchEvent( - "file:changed", - meta.name, - oldHash, - newHash, - ); + await this.dispatchEvent("file:changed", meta.name, oldHash, newHash); } // Page found, not deleted deletedFiles.delete(meta.name); @@ -147,9 +137,7 @@ export class EventedSpacePrimitives implements SpacePrimitives { } } - async readFile( - path: string, - ): Promise<{ data: Uint8Array; meta: FileMeta }> { + async readFile(path: string): Promise<{ data: Uint8Array; meta: FileMeta }> { if (!this.enabled) { return this.wrapped.readFile(path); } @@ -181,11 +169,7 @@ export class EventedSpacePrimitives implements SpacePrimitives { const wasFetching = this.operationInProgress; this.operationInProgress = true; try { - const newMeta = await this.wrapped.writeFile( - path, - data, - meta, - ); + const newMeta = await this.wrapped.writeFile(path, data, meta); if (!wasFetching) { await this.triggerEventsAndCache(path, newMeta.lastModified); } diff --git a/client/spaces/filtered_space_primitives.ts b/client/spaces/filtered_space_primitives.ts index de934706..9cf3471d 100644 --- a/client/spaces/filtered_space_primitives.ts +++ b/client/spaces/filtered_space_primitives.ts @@ -6,8 +6,7 @@ export class FilteredSpacePrimitives implements SpacePrimitives { private wrapped: SpacePrimitives, private filterFn: (name: FileMeta) => boolean, private onFetchList?: () => Promise, - ) { - } + ) {} async fetchFileList(): Promise { if (this.onFetchList) { diff --git a/client/spaces/http_space_primitives.ts b/client/spaces/http_space_primitives.ts index f77235d8..7da574af 100644 --- a/client/spaces/http_space_primitives.ts +++ b/client/spaces/http_space_primitives.ts @@ -1,8 +1,6 @@ import type { SpacePrimitives } from "./space_primitives.ts"; import { encodePageURI } from "@silverbulletmd/silverbullet/lib/ref"; -import { - flushCachesAndUnregisterServiceWorker, -} from "../../client/service_worker/util.ts"; +import { flushCachesAndUnregisterServiceWorker } from "../../client/service_worker/util.ts"; import type { FileMeta } from "@silverbulletmd/silverbullet/type/index"; import { notFoundError, @@ -20,8 +18,7 @@ export class HttpSpacePrimitives implements SpacePrimitives { readonly expectedSpacePath: string, private authErrorCallback: (message: string, ...args: any[]) => void, private bearerToken?: string, - ) { - } + ) {} public async authenticatedFetch( url: string, @@ -38,7 +35,7 @@ export class HttpSpacePrimitives implements SpacePrimitives { if (this.bearerToken) { options.headers = { ...options.headers, - "Authorization": `Bearer ${this.bearerToken}`, + Authorization: `Bearer ${this.bearerToken}`, }; } @@ -118,11 +115,7 @@ export class HttpSpacePrimitives implements SpacePrimitives { errorMessage.includes("load failed") || errorMessage.includes("unavailable") ) { - console.error( - "Got error fetching, throwing offline", - url, - e.message, - ); + console.error("Got error fetching, throwing offline", url, e.message); throw offlineError; } throw e; @@ -151,16 +144,11 @@ export class HttpSpacePrimitives implements SpacePrimitives { console.log("Expected space path", this.expectedSpacePath); console.log("Got space path", resp.headers.get("X-Space-Path")); await flushCachesAndUnregisterServiceWorker(); - this.authErrorCallback( - wrongSpacePathError.message, - "reload", - ); + this.authErrorCallback(wrongSpacePathError.message, "reload"); } } - async readFile( - path: string, - ): Promise<{ data: Uint8Array; meta: FileMeta }> { + async readFile(path: string): Promise<{ data: Uint8Array; meta: FileMeta }> { const res = await this.authenticatedFetch( `${this.url}/${encodePageURI(path)}`, { @@ -242,14 +230,18 @@ export class HttpSpacePrimitives implements SpacePrimitives { // If not: throws an error or invokes a redirect async ping() { - const parentEndpoint = this.url.split("/").slice(0, -1).join("/") + - "/.ping"; - const resp = await this.authenticatedFetch(parentEndpoint, { - method: "GET", - headers: { - Accept: "application/json", + const parentEndpoint = + this.url.split("/").slice(0, -1).join("/") + "/.ping"; + const resp = await this.authenticatedFetch( + parentEndpoint, + { + method: "GET", + headers: { + Accept: "application/json", + }, }, - }, pingTimeout); + pingTimeout, + ); if (!resp.ok) { throw new Error(`Ping failed: ${resp.status} ${resp.statusText}`); diff --git a/client/spaces/space_primitives.test.ts b/client/spaces/space_primitives.test.ts index e1e0ff1c..d6292b21 100644 --- a/client/spaces/space_primitives.test.ts +++ b/client/spaces/space_primitives.test.ts @@ -36,15 +36,14 @@ async function testBasicOperations(spacePrimitives: SpacePrimitives) { }, ); - const { data: retrievedData, meta: retrievedMeta } = await spacePrimitives - .readFile("test.txt"); + const { data: retrievedData, meta: retrievedMeta } = + await spacePrimitives.readFile("test.txt"); expect(retrievedData).toEqual(stringToBytes("Hello World")); // Check that the meta data is persisted expect(retrievedMeta.lastModified).toEqual(20); - const fbContent = (await spacePrimitives.readFile("test.txt")) - .data; + const fbContent = (await spacePrimitives.readFile("test.txt")).data; expect(new TextDecoder().decode(fbContent)).toEqual("Hello World"); expect(await spacePrimitives.fetchFileList()).toEqual([fileMeta]); @@ -66,7 +65,8 @@ async function testBasicOperations(spacePrimitives: SpacePrimitives) { // Test weird file names await spacePrimitives.writeFile("test+'s.txt", stringToBytes("Hello world!")); - expect(stringToBytes("Hello world!")).toEqual((await spacePrimitives.readFile("test+'s.txt")).data, + expect(stringToBytes("Hello world!")).toEqual( + (await spacePrimitives.readFile("test+'s.txt")).data, ); await spacePrimitives.deleteFile("test+'s.txt"); @@ -90,8 +90,8 @@ async function testFileOverwriting(spacePrimitives: SpacePrimitives) { // File list should still have only one entry for this file const filesAfterOverwrite = await spacePrimitives.fetchFileList(); - const overwriteFiles = filesAfterOverwrite.filter((f) => - f.name === "overwrite.txt" + const overwriteFiles = filesAfterOverwrite.filter( + (f) => f.name === "overwrite.txt", ); expect(overwriteFiles.length).toEqual(1); @@ -134,7 +134,8 @@ async function testSpecialFileNames(spacePrimitives: SpacePrimitives) { stringToBytes(`Content of ${fileName}`), ); const fileData = await spacePrimitives.readFile(fileName); - expect(new TextDecoder().decode(fileData.data)).toEqual(`Content of ${fileName}`, + expect(new TextDecoder().decode(fileData.data)).toEqual( + `Content of ${fileName}`, ); } @@ -162,7 +163,10 @@ async function testErrorHandling(spacePrimitives: SpacePrimitives) { try { await spacePrimitives.deleteFile("nonexistent.txt"); - expect(false, "Should throw error when deleting non-existent file").toBeTruthy(); + expect( + false, + "Should throw error when deleting non-existent file", + ).toBeTruthy(); } catch (e: any) { expect(e).toEqual(notFoundError); } @@ -200,11 +204,7 @@ async function testMetadataPreservation(spacePrimitives: SpacePrimitives) { size: testContent.length, // Use actual content length }; - await spacePrimitives.writeFile( - "meta-test.txt", - testContent, - customMeta, - ); + await spacePrimitives.writeFile("meta-test.txt", testContent, customMeta); const metaFile = await spacePrimitives.readFile("meta-test.txt"); // Check that some metadata is preserved (implementations may handle timestamps differently) diff --git a/client/spaces/space_primitives.ts b/client/spaces/space_primitives.ts index 4ed22c57..1ced80d8 100644 --- a/client/spaces/space_primitives.ts +++ b/client/spaces/space_primitives.ts @@ -13,9 +13,7 @@ export interface SpacePrimitives { */ getFileMeta(path: string, observing?: boolean): Promise; - readFile( - path: string, - ): Promise<{ data: Uint8Array; meta: FileMeta }>; + readFile(path: string): Promise<{ data: Uint8Array; meta: FileMeta }>; writeFile( path: string, diff --git a/client/spaces/sync.test.ts b/client/spaces/sync.test.ts index 991e1848..4381e0f5 100644 --- a/client/spaces/sync.test.ts +++ b/client/spaces/sync.test.ts @@ -19,7 +19,8 @@ test("Test sync with no filtering", async () => { console.log("Initial sync ops", await doSync()); expect((await secondary.fetchFileList()).length).toEqual(1); - expect((await secondary.readFile("index.md")).data).toEqual(stringToBytes("Hello"), + expect((await secondary.readFile("index.md")).data).toEqual( + stringToBytes("Hello"), ); // Should be a no-op @@ -39,7 +40,8 @@ test("Test sync with no filtering", async () => { expect((await primary.fetchFileList()).length).toEqual(2); expect((await secondary.fetchFileList()).length).toEqual(2); - expect((await primary.readFile("index.md")).data).toEqual(stringToBytes("Hello!!"), + expect((await primary.readFile("index.md")).data).toEqual( + stringToBytes("Hello!!"), ); // Let's make some random edits on both ends @@ -86,7 +88,8 @@ test("Test sync with no filtering", async () => { await doSync(); - expect((await primary.readFile("index.md")).data).toEqual(stringToBytes("I'm back"), + expect((await primary.readFile("index.md")).data).toEqual( + stringToBytes("I'm back"), ); // Cause a conflict @@ -100,9 +103,11 @@ test("Test sync with no filtering", async () => { await doSync(); // Verify that primary won - expect((await primary.readFile("index.md")).data).toEqual(stringToBytes("Hello 1"), + expect((await primary.readFile("index.md")).data).toEqual( + stringToBytes("Hello 1"), ); - expect((await secondary.readFile("index.md")).data).toEqual(stringToBytes("Hello 1"), + expect((await secondary.readFile("index.md")).data).toEqual( + stringToBytes("Hello 1"), ); // test + index + index.conflicting copy @@ -132,14 +137,10 @@ test("Test sync with filtering", async () => { const secondary = new DataStoreSpacePrimitives(new MemoryKvPrimitives()); const snapshot = new SyncSnapshot(); - let sync = new SpaceSync( - primary, - secondary, - { - conflictResolver: SpaceSync.primaryConflictResolver, - isSyncCandidate: (path) => path.endsWith(".md"), // Only sync .md files - }, - ); + let sync = new SpaceSync(primary, secondary, { + conflictResolver: SpaceSync.primaryConflictResolver, + isSyncCandidate: (path) => path.endsWith(".md"), // Only sync .md files + }); console.log( "Write one non-sync file on the primary, which SHOULD sync to the secondary", @@ -152,7 +153,8 @@ test("Test sync with filtering", async () => { expect(snapshot.nonSyncedFiles.size).toEqual(0); expect((await secondary.fetchFileList()).length).toEqual(1); - expect((await secondary.readFile("index.txt")).data).toEqual(stringToBytes("Hello"), + expect((await secondary.readFile("index.txt")).data).toEqual( + stringToBytes("Hello"), ); console.log("Updating on secondary"); @@ -205,14 +207,10 @@ test("Test sync with filtering", async () => { //////////// // Now let's start another sync session, but now wanting to sync everything console.log("Going to switch to syncing everything now"); - sync = new SpaceSync( - primary, - secondary, - { - conflictResolver: SpaceSync.primaryConflictResolver, - isSyncCandidate: () => true, - }, - ); + sync = new SpaceSync(primary, secondary, { + conflictResolver: SpaceSync.primaryConflictResolver, + isSyncCandidate: () => true, + }); ops = await doSync(); // This should pull 2 files from remote to local @@ -221,14 +219,10 @@ test("Test sync with filtering", async () => { expect((await secondary.fetchFileList()).length).toEqual(3); console.log("And now to syncing nothing"); - sync = new SpaceSync( - primary, - secondary, - { - conflictResolver: SpaceSync.primaryConflictResolver, - isSyncCandidate: () => false, - }, - ); + sync = new SpaceSync(primary, secondary, { + conflictResolver: SpaceSync.primaryConflictResolver, + isSyncCandidate: () => false, + }); ops = await doSync(); // This should delete 3 files from the primary @@ -239,14 +233,10 @@ test("Test sync with filtering", async () => { expect((await secondary.fetchFileList()).length).toEqual(3); // Ok, now we're going to sync everything again - sync = new SpaceSync( - primary, - secondary, - { - conflictResolver: SpaceSync.primaryConflictResolver, - isSyncCandidate: () => true, - }, - ); + sync = new SpaceSync(primary, secondary, { + conflictResolver: SpaceSync.primaryConflictResolver, + isSyncCandidate: () => true, + }); // await secondary. ops = await doSync(); expect(ops).toEqual(3); @@ -279,7 +269,8 @@ test("Local push sync", async () => { await primary.writeFile("index.md", stringToBytes("Hello")); expect(1).toEqual(await sync.syncSingleFile("index.md", snapshot)); - expect((await secondary.readFile("index.md")).data).toEqual(stringToBytes("Hello"), + expect((await secondary.readFile("index.md")).data).toEqual( + stringToBytes("Hello"), ); console.log("Let's write a new file on primary that is not a sync candidate"); diff --git a/client/spaces/sync.ts b/client/spaces/sync.ts index a4a49386..0c63ff05 100644 --- a/client/spaces/sync.ts +++ b/client/spaces/sync.ts @@ -125,24 +125,32 @@ export class SpaceSync extends EventEmitter { }); // console.log("[sync]", "Iterating over all files"); let filesProcessed = 0; - await processWithConcurrency(sortedPaths, async (path) => { - const fileOperations = await this.syncFile( - path, - primaryFileMap.get(path), - secondaryFileMap.get(path), - !nonSyncCandidates.has(path), - snapshot, - ); - operations = operations + fileOperations; - filesProcessed++; - if (fileOperations > 0) { - // Only report something significant - await this.emit("syncProgress", { - filesProcessed, - totalFiles: sortedPaths.length, - }, snapshot); - } - }, syncConcurrency); + await processWithConcurrency( + sortedPaths, + async (path) => { + const fileOperations = await this.syncFile( + path, + primaryFileMap.get(path), + secondaryFileMap.get(path), + !nonSyncCandidates.has(path), + snapshot, + ); + operations = operations + fileOperations; + filesProcessed++; + if (fileOperations > 0) { + // Only report something significant + await this.emit( + "syncProgress", + { + filesProcessed, + totalFiles: sortedPaths.length, + }, + snapshot, + ); + } + }, + syncConcurrency, + ); console.log( "[Sync]", "Completed:", @@ -236,7 +244,8 @@ export class SpaceSync extends EventEmitter { let operations = 0; if ( - primaryMeta !== undefined && secondaryMeta === undefined && + primaryMeta !== undefined && + secondaryMeta === undefined && !snapshot.files.has(path) ) { // New file, created on primary, copy from primary to secondary @@ -246,30 +255,21 @@ export class SpaceSync extends EventEmitter { path, ); const { data, meta } = await this.primary.readFile(path); - const writtenMeta = await this.secondary.writeFile( - path, - data, - meta, - ); + const writtenMeta = await this.secondary.writeFile(path, data, meta); // Update file snapshot - snapshot.files.set(path, [ - meta.lastModified, - writtenMeta.lastModified, - ]); + snapshot.files.set(path, [meta.lastModified, writtenMeta.lastModified]); // Let's make sure this file is not marked as nonSynced, because we have a local copy snapshot.nonSyncedFiles.delete(path); operations++; } else if ( - secondaryMeta !== undefined && (( - // Scenario: a new file was created on the secondary - primaryMeta === undefined && - !snapshot.files.has(path) - ) || ( + secondaryMeta !== undefined && + // Scenario: a new file was created on the secondary + ((primaryMeta === undefined && !snapshot.files.has(path)) || // Scenario: a file that was previously unsynced, should now be synced - snapshot.nonSyncedFiles.has(path) && syncBack && - // because we don't have it yet - primaryMeta === undefined - )) + (snapshot.nonSyncedFiles.has(path) && + syncBack && + // because we don't have it yet + primaryMeta === undefined)) ) { // New file to sync, present on secondary if (syncBack) { @@ -280,20 +280,14 @@ export class SpaceSync extends EventEmitter { path, ); const { data, meta } = await this.secondary.readFile(path); - const writtenMeta = await this.primary.writeFile( - path, - data, - meta, - ); + const writtenMeta = await this.primary.writeFile(path, data, meta); // Update file snapshot - snapshot.files.set(path, [ - writtenMeta.lastModified, - meta.lastModified, - ]); + snapshot.files.set(path, [writtenMeta.lastModified, meta.lastModified]); // Make sure the file is not marked as nonSynced anymore snapshot.nonSyncedFiles.delete(path); operations++; - } else { // !syncBack + } else { + // !syncBack // This has syncBack set to false, which means we'll just update the nonSyncedFiles and snapshot console.log( "[sync]", @@ -307,7 +301,8 @@ export class SpaceSync extends EventEmitter { snapshot.nonSyncedFiles.set(path, secondaryMeta); } } else if ( - primaryMeta !== undefined && snapshot.files.has(path) && + primaryMeta !== undefined && + snapshot.files.has(path) && secondaryMeta === undefined ) { // File deleted on secondary @@ -331,7 +326,8 @@ export class SpaceSync extends EventEmitter { await this.primary.deleteFile(path); } operations++; - } else { // !syncBack + } else { + // !syncBack console.log( "[sync]", "File deleted on secondary, only updating snapshot", @@ -350,7 +346,8 @@ export class SpaceSync extends EventEmitter { } } else if ( // The file is on the secondary, and not on the primary - secondaryMeta !== undefined && snapshot.files.has(path) && + secondaryMeta !== undefined && + snapshot.files.has(path) && primaryMeta === undefined && // And we're talking about a synced file syncBack @@ -366,7 +363,8 @@ export class SpaceSync extends EventEmitter { snapshot.nonSyncedFiles.delete(path); operations++; } else if ( - snapshot.files.has(path) && primaryMeta === undefined && + snapshot.files.has(path) && + primaryMeta === undefined && secondaryMeta === undefined ) { // File deleted on both sides, :shrug: @@ -379,7 +377,8 @@ export class SpaceSync extends EventEmitter { snapshot.nonSyncedFiles.delete(path); operations++; } else if ( - primaryMeta !== undefined && secondaryMeta !== undefined && + primaryMeta !== undefined && + secondaryMeta !== undefined && snapshot.files.get(path) && primaryMeta.lastModified !== snapshot.files.get(path)![0] && secondaryMeta.lastModified === snapshot.files.get(path)![1] @@ -391,20 +390,14 @@ export class SpaceSync extends EventEmitter { path, ); const { data, meta } = await this.primary.readFile(path); - const writtenMeta = await this.secondary.writeFile( - path, - data, - meta, - ); - snapshot.files.set(path, [ - meta.lastModified, - writtenMeta.lastModified, - ]); + const writtenMeta = await this.secondary.writeFile(path, data, meta); + snapshot.files.set(path, [meta.lastModified, writtenMeta.lastModified]); // Delete from non-synced files just in case, because we clearly have a local copy snapshot.nonSyncedFiles.delete(path); operations++; } else if ( - primaryMeta !== undefined && secondaryMeta !== undefined && + primaryMeta !== undefined && + secondaryMeta !== undefined && snapshot.files.get(path) && primaryMeta.lastModified === snapshot.files.get(path)![0] && secondaryMeta.lastModified !== snapshot.files.get(path)![1] @@ -418,19 +411,13 @@ export class SpaceSync extends EventEmitter { path, ); const { data, meta } = await this.secondary.readFile(path); - const writtenMeta = await this.primary.writeFile( - path, - data, - meta, - ); - snapshot.files.set(path, [ - writtenMeta.lastModified, - meta.lastModified, - ]); + const writtenMeta = await this.primary.writeFile(path, data, meta); + snapshot.files.set(path, [writtenMeta.lastModified, meta.lastModified]); // Make sure it's not in nonSyncedFiles snapshot.nonSyncedFiles.delete(path); operations++; - } else { // !syncBack + } else { + // !syncBack console.log( "[sync]", "File has changed on secondary, but not primary: shouldn't be synced", @@ -455,16 +442,16 @@ export class SpaceSync extends EventEmitter { } } } else if ( - ( // File changed on both ends, but we don't have any info in the snapshot (resync scenario?): have to run through conflict handling - primaryMeta !== undefined && secondaryMeta !== undefined && - !snapshot.files.has(path) - ) || - ( // File changed on both ends, CONFLICT! - primaryMeta !== undefined && secondaryMeta !== undefined && + // File changed on both ends, but we don't have any info in the snapshot (resync scenario?): have to run through conflict handling + (primaryMeta !== undefined && + secondaryMeta !== undefined && + !snapshot.files.has(path)) || + // File changed on both ends, CONFLICT! + (primaryMeta !== undefined && + secondaryMeta !== undefined && snapshot.files.get(path) && primaryMeta.lastModified !== snapshot.files.get(path)![0] && - secondaryMeta.lastModified !== snapshot.files.get(path)![1] - ) + secondaryMeta.lastModified !== snapshot.files.get(path)![1]) ) { console.log( "[sync]", @@ -547,14 +534,11 @@ export class SpaceSync extends EventEmitter { } let operations = 0; - const revisionFileName = filePieces.length === 1 - ? `${path}.conflicted:${pageData2.meta.lastModified}` - : `${fileNameBase}.conflicted:${pageData2.meta.lastModified}.${fileNameExt}`; - console.log( - "[sync]", - "Going to create conflicting copy", - revisionFileName, - ); + const revisionFileName = + filePieces.length === 1 + ? `${path}.conflicted:${pageData2.meta.lastModified}` + : `${fileNameBase}.conflicted:${pageData2.meta.lastModified}.${fileNameExt}`; + console.log("[sync]", "Going to create conflicting copy", revisionFileName); // Copy secondary to conflict copy const localConflictMeta = await primary.writeFile( @@ -569,10 +553,7 @@ export class SpaceSync extends EventEmitter { operations++; // Write replacement on top - const writeMeta = await secondary.writeFile( - path, - pageData1.data, - ); + const writeMeta = await secondary.writeFile(path, pageData1.data); operations++; // Updating snapshot diff --git a/client/types/ui.ts b/client/types/ui.ts index f8f3c16e..14a4d092 100644 --- a/client/types/ui.ts +++ b/client/types/ui.ts @@ -102,8 +102,7 @@ export const initialViewState: AppViewState = { showFilterBox: false, filterBoxHelpText: "", filterBoxLabel: "", - filterBoxOnSelect: () => { - }, + filterBoxOnSelect: () => {}, filterBoxOptions: [], filterBoxPlaceHolder: "", @@ -125,47 +124,47 @@ export type Action = | { type: "start-navigate"; mode: "page" | "meta" | "document" | "all" } | { type: "stop-navigate" } | { - type: "update-commands"; - commands: Map; - } + type: "update-commands"; + commands: Map; + } | { type: "show-palette"; context?: string; commands: Map } | { type: "hide-palette" } | { type: "show-notification"; notification: Notification } | { type: "dismiss-notification"; id: number } | { - type: "show-panel"; - id: "rhs" | "lhs" | "bhs" | "modal"; - config: PanelConfig; - } + type: "show-panel"; + id: "rhs" | "lhs" | "bhs" | "modal"; + config: PanelConfig; + } | { type: "hide-panel"; id: string } | { - type: "show-filterbox"; - options: FilterOption[]; - placeHolder: string; - helpText: string; - label: string; - onSelect: (option: FilterOption | undefined) => void; - } + type: "show-filterbox"; + options: FilterOption[]; + placeHolder: string; + helpText: string; + label: string; + onSelect: (option: FilterOption | undefined) => void; + } | { type: "hide-filterbox" } | { - type: "show-prompt"; - message: string; - defaultValue: string; - callback: (value: string | undefined) => void; - } + type: "show-prompt"; + message: string; + defaultValue: string; + callback: (value: string | undefined) => void; + } | { type: "hide-prompt" } | { - type: "show-confirm"; - message: string; - callback: (value: boolean) => void; - } + type: "show-confirm"; + message: string; + callback: (value: boolean) => void; + } | { type: "hide-confirm" } | { type: "set-ui-option"; key: string; value: any } | { - type: "set-progress"; - progressPercentage?: number; - progressType?: string; - }; + type: "set-progress"; + progressPercentage?: number; + progressType?: string; + }; /** * Client configuration that is set at boot time, doesn't change at runtime @@ -193,8 +192,8 @@ export type BootConfig = { */ export type ServiceWorkerTargetMessage = | { - type: "skip-waiting"; - } + type: "skip-waiting"; + } | { type: "config"; config: BootConfig } | { type: "flush-cache" } | { type: "shutdown" } @@ -207,36 +206,47 @@ export type ServiceWorkerTargetMessage = /** * Events received from the service worker -> client */ -export type ServiceWorkerSourceMessage = { - type: "sync-status"; - status: Omit; -} | { - type: "sync-conflict"; - path: string; -} | { - type: "space-sync-complete"; - operations: number; -} | { - type: "file-sync-complete"; - path: string; - operations: number; -} | { - type: "sync-error"; - message: string; -} | { - type: "online-status"; - isOnline: boolean; -} | { - type: "auth-error"; - message: string; - actionOrRedirectHeader: string; -} | { - type: "cacheFlushed"; -} | { - type: "dataWiped"; -} | { - type: "service-worker-started"; -} | { - type: "encryption-key"; - key: string; -}; +export type ServiceWorkerSourceMessage = + | { + type: "sync-status"; + status: Omit; + } + | { + type: "sync-conflict"; + path: string; + } + | { + type: "space-sync-complete"; + operations: number; + } + | { + type: "file-sync-complete"; + path: string; + operations: number; + } + | { + type: "sync-error"; + message: string; + } + | { + type: "online-status"; + isOnline: boolean; + } + | { + type: "auth-error"; + message: string; + actionOrRedirectHeader: string; + } + | { + type: "cacheFlushed"; + } + | { + type: "dataWiped"; + } + | { + type: "service-worker-started"; + } + | { + type: "encryption-key"; + key: string; + }; diff --git a/package.json b/package.json index f8409fde..fe5877df 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,8 @@ "test": "vitest run", "check": "tsc --noEmit", "lint": "biome lint .", + "fmt": "biome format --write .", + "fmt:check": "biome format .", "bench": "vitest bench" }, "dependencies": { diff --git a/plug-api/lib/async.test.ts b/plug-api/lib/async.test.ts index 42ae16d1..73ab651a 100644 --- a/plug-api/lib/async.test.ts +++ b/plug-api/lib/async.test.ts @@ -162,23 +162,29 @@ test("processWithConcurrency test - performance with concurrency", async () => { // Parallel should be significantly faster than sequential // Allow some margin for timing variations - expect( - parallel < sequential * 0.8, - ).toBeTruthy(); // `Parallel (${parallel}ms) should be faster than sequential (${sequential}ms)` + expect(parallel < sequential * 0.8).toBeTruthy(); // `Parallel (${parallel}ms) should be faster than sequential (${sequential}ms)` }); test("Batch test", async () => { // Generate an array with numbers up to 100 const elements = Array.from(Array(100).keys()); - const multiplied = await batchRequests(elements, async (batch) => { - await sleep(2); - // Batches should be 9 or smaller (last batch will be smaller) - expect(batch.length <= 9).toBeTruthy(); - return batch.map((e) => e * 2); - }, 9); + const multiplied = await batchRequests( + elements, + async (batch) => { + await sleep(2); + // Batches should be 9 or smaller (last batch will be smaller) + expect(batch.length <= 9).toBeTruthy(); + return batch.map((e) => e * 2); + }, + 9, + ); expect(multiplied).toEqual(elements.map((e) => e * 2)); - const multiplied2 = await batchRequests(elements, (batch) => { - return Promise.resolve(batch.map((e) => e * 2)); - }, 10000); + const multiplied2 = await batchRequests( + elements, + (batch) => { + return Promise.resolve(batch.map((e) => e * 2)); + }, + 10000, + ); expect(multiplied2).toEqual(elements.map((e) => e * 2)); }); diff --git a/plug-api/lib/async.ts b/plug-api/lib/async.ts index 6b111782..fc469960 100644 --- a/plug-api/lib/async.ts +++ b/plug-api/lib/async.ts @@ -38,7 +38,7 @@ export function timeout(ms: number): Promise { return new Promise((_resolve, reject) => setTimeout(() => { reject(new Error("timeout")); - }, ms) + }, ms), ); } @@ -105,7 +105,8 @@ export async function batchRequests( const batchResults = await Promise.all(batches.map(fn)); // Flatten the results for (const batchResult of batchResults) { - if (Array.isArray(batchResult)) { // If fn returns an array, collect them + if (Array.isArray(batchResult)) { + // If fn returns an array, collect them results.push(...batchResult); } } diff --git a/plug-api/lib/crypto.ts b/plug-api/lib/crypto.ts index eed173e8..13c2cbd8 100644 --- a/plug-api/lib/crypto.ts +++ b/plug-api/lib/crypto.ts @@ -42,9 +42,8 @@ export async function hashSHA256( ): Promise { // Transform the string into an ArrayBuffer const encoder = new TextEncoder(); - const data: Uint8Array = typeof message === "string" - ? encoder.encode(message) - : message; + const data: Uint8Array = + typeof message === "string" ? encoder.encode(message) : message; // Generate the hash const hashBuffer = await globalThis.crypto.subtle.digest( @@ -53,9 +52,9 @@ export async function hashSHA256( ); // Transform the hash into a hex string - return Array.from(new Uint8Array(hashBuffer)).map((b) => - b.toString(16).padStart(2, "0") - ).join(""); + return Array.from(new Uint8Array(hashBuffer)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); } /** diff --git a/plug-api/lib/dates.ts b/plug-api/lib/dates.ts index 7aa56a5d..cbd12639 100644 --- a/plug-api/lib/dates.ts +++ b/plug-api/lib/dates.ts @@ -3,11 +3,19 @@ export function niceDate(d: Date): string { } export function localDateString(d: Date): string { - return d.getFullYear() + - "-" + String(d.getMonth() + 1).padStart(2, "0") + - "-" + String(d.getDate()).padStart(2, "0") + - "T" + String(d.getHours()).padStart(2, "0") + - ":" + String(d.getMinutes()).padStart(2, "0") + - ":" + String(d.getSeconds()).padStart(2, "0") + - "." + String(d.getMilliseconds()).padStart(3, "0"); + return ( + d.getFullYear() + + "-" + + String(d.getMonth() + 1).padStart(2, "0") + + "-" + + String(d.getDate()).padStart(2, "0") + + "T" + + String(d.getHours()).padStart(2, "0") + + ":" + + String(d.getMinutes()).padStart(2, "0") + + ":" + + String(d.getSeconds()).padStart(2, "0") + + "." + + String(d.getMilliseconds()).padStart(3, "0") + ); } diff --git a/plug-api/lib/json.ts b/plug-api/lib/json.ts index 1fadfc1c..dd0a0154 100644 --- a/plug-api/lib/json.ts +++ b/plug-api/lib/json.ts @@ -52,11 +52,17 @@ export function deepEqual(a: any, b: any): boolean { export function cleanStringDate(d: Date): string { // If no significant time, return a date string only if ( - d.getUTCHours() === 0 && d.getUTCMinutes() === 0 && d.getUTCSeconds() === 0 + d.getUTCHours() === 0 && + d.getUTCMinutes() === 0 && + d.getUTCSeconds() === 0 ) { - return d.getUTCFullYear() + "-" + - String(d.getUTCMonth() + 1).padStart(2, "0") + "-" + - String(d.getUTCDate()).padStart(2, "0"); + return ( + d.getUTCFullYear() + + "-" + + String(d.getUTCMonth() + 1).padStart(2, "0") + + "-" + + String(d.getUTCDate()).padStart(2, "0") + ); } else { return d.toISOString(); } diff --git a/plug-api/lib/ref.test.ts b/plug-api/lib/ref.test.ts index 6eef1ae6..535f15db 100644 --- a/plug-api/lib/ref.test.ts +++ b/plug-api/lib/ref.test.ts @@ -84,15 +84,21 @@ test("parseToRef() link cases", () => { test("encodeRef() cases", () => { // Encoding expect(encodeRef({ path: "foo.md" })).toEqual("foo"); - expect(encodeRef({ path: "foo.md", details: { type: "position", pos: 10 } })).toEqual("foo@10"); - expect(encodeRef({ - path: "foo.md", - details: { type: "linecolumn", line: 10, column: 69 }, - })).toEqual("foo@L10C69"); - expect(encodeRef({ - path: "foo.md", - details: { type: "header", header: "bar" }, - })).toEqual("foo#bar"); + expect( + encodeRef({ path: "foo.md", details: { type: "position", pos: 10 } }), + ).toEqual("foo@10"); + expect( + encodeRef({ + path: "foo.md", + details: { type: "linecolumn", line: 10, column: 69 }, + }), + ).toEqual("foo@L10C69"); + expect( + encodeRef({ + path: "foo.md", + details: { type: "header", header: "bar" }, + }), + ).toEqual("foo#bar"); }); test("isValidPath() and isValidName()", () => { diff --git a/plug-api/lib/ref.ts b/plug-api/lib/ref.ts index 800f0b17..edbf33c5 100644 --- a/plug-api/lib/ref.ts +++ b/plug-api/lib/ref.ts @@ -77,8 +77,9 @@ export function isValidName(name: string): boolean { // If the name, parses as a link and doesn't provide any other info we can be // sure it was only parsed as a path and that the path then conforms to all // the requirements - return !!ref && !ref.details && !ref.meta && name !== "" && - encodeRef(ref) === name; + return ( + !!ref && !ref.details && !ref.meta && name !== "" && encodeRef(ref) === name + ); } /** @@ -206,23 +207,22 @@ export function getOffsetFromHeader( parseTree: ParseTree, header: string, ): number { - const node = findNodeMatching( - parseTree, - (subTree) => { - if (!subTree.type || !subTree.type.startsWith("ATXHeading")) { - return false; - } + const node = findNodeMatching(parseTree, (subTree) => { + if (!subTree.type || !subTree.type.startsWith("ATXHeading")) { + return false; + } - const mark = findNodeOfType(subTree, "HeaderMark"); - if (!mark || mark.from === undefined || mark.to === undefined) { - return false; - } + const mark = findNodeOfType(subTree, "HeaderMark"); + if (!mark || mark.from === undefined || mark.to === undefined) { + return false; + } - return renderToText(subTree) + return ( + renderToText(subTree) .slice(mark.to - mark.from) - .trimStart() === header.trim(); - }, - ); + .trimStart() === header.trim() + ); + }); if (!node) { return -1; diff --git a/plug-api/lib/resolve.test.ts b/plug-api/lib/resolve.test.ts index 94ba5b68..22f8f1dc 100644 --- a/plug-api/lib/resolve.test.ts +++ b/plug-api/lib/resolve.test.ts @@ -3,39 +3,19 @@ import { resolveMarkdownLink } from "@silverbulletmd/silverbullet/lib/resolve"; test("Test URL resolver", () => { // Absolute paths - expect( - resolveMarkdownLink("foo", "/bar"), - ).toEqual("bar"); - expect( - resolveMarkdownLink("/foo/bar/baz", "/qux"), - ).toEqual("qux"); - expect( - resolveMarkdownLink("foo", "/bar@123#456"), - ).toEqual("bar@123#456"); - expect( - resolveMarkdownLink("foo/bar", "/baz.jpg"), - ).toEqual("baz.jpg"); + expect(resolveMarkdownLink("foo", "/bar")).toEqual("bar"); + expect(resolveMarkdownLink("/foo/bar/baz", "/qux")).toEqual("qux"); + expect(resolveMarkdownLink("foo", "/bar@123#456")).toEqual("bar@123#456"); + expect(resolveMarkdownLink("foo/bar", "/baz.jpg")).toEqual("baz.jpg"); // Relative paths - expect( - resolveMarkdownLink("bar", "foo"), - ).toEqual("foo"); - expect( - resolveMarkdownLink("foo/bar.jpg", "baz"), - ).toEqual("foo/baz"); - expect( - resolveMarkdownLink("/foo/bar", "baz"), - ).toEqual("/foo/baz"); - expect( - resolveMarkdownLink("foo///bar", "baz"), - ).toEqual("foo///baz"); - expect( - resolveMarkdownLink("bar", "../foo/baz"), - ).toEqual("foo/baz"); - expect( - resolveMarkdownLink("bar", "../../foo/baz"), - ).toEqual("foo/baz"); - expect( - resolveMarkdownLink("bar/qux", "foo/../baz"), - ).toEqual("bar/foo/../baz"); + expect(resolveMarkdownLink("bar", "foo")).toEqual("foo"); + expect(resolveMarkdownLink("foo/bar.jpg", "baz")).toEqual("foo/baz"); + expect(resolveMarkdownLink("/foo/bar", "baz")).toEqual("/foo/baz"); + expect(resolveMarkdownLink("foo///bar", "baz")).toEqual("foo///baz"); + expect(resolveMarkdownLink("bar", "../foo/baz")).toEqual("foo/baz"); + expect(resolveMarkdownLink("bar", "../../foo/baz")).toEqual("foo/baz"); + expect(resolveMarkdownLink("bar/qux", "foo/../baz")).toEqual( + "bar/foo/../baz", + ); }); diff --git a/plug-api/lib/resolve.ts b/plug-api/lib/resolve.ts index f63e1cdd..4db548a3 100644 --- a/plug-api/lib/resolve.ts +++ b/plug-api/lib/resolve.ts @@ -4,9 +4,11 @@ import type { Path } from "@silverbulletmd/silverbullet/lib/ref"; * Determines wether a url points into the world wide web or to the local SB instance */ export function isLocalURL(url: string): boolean { - return !url.includes("://") && + return ( + !url.includes("://") && !url.startsWith("mailto:") && - !url.startsWith("tel:"); + !url.startsWith("tel:") + ); } /** @@ -22,10 +24,7 @@ export function fileName(name: string | Path): string | Path { return name.split("/").pop()!; } -const builtinPrefixes = [ - "tag:", - "search:", -]; +const builtinPrefixes = ["tag:", "search:"]; /** * Builtin pages are pages which SB should automatically consider as existing @@ -51,11 +50,8 @@ export function resolveMarkdownLink( if (relative.startsWith("/")) { return relative.slice(1); } else { - const splitAbsolute = absolute - .split("/") - .slice(0, -1); - const splitRelative = relative - .split("/"); + const splitAbsolute = absolute.split("/").slice(0, -1); + const splitRelative = relative.split("/"); while (splitRelative && splitRelative[0] === "..") { splitAbsolute.pop(); diff --git a/plug-api/lib/tags.ts b/plug-api/lib/tags.ts index f9e1dfa3..c36c72fb 100644 --- a/plug-api/lib/tags.ts +++ b/plug-api/lib/tags.ts @@ -1,15 +1,19 @@ /** Extract the name from hashtag text, removing # prefix and if necessary */ export function extractHashtag(text: string): string { - if (text[0] !== "#") { // you shouldn't call this function at all + if (text[0] !== "#") { + // you shouldn't call this function at all console.error("extractHashtag called on already clean string", text); return text; } else if (text[1] === "<") { - if (text.slice(-1) !== ">") { // this is malformed: #") { + // this is malformed: # + } else { + // this is correct # return text.slice(2, -1); } - } else { // this is just #name + } else { + // this is just #name return text.slice(1); } } diff --git a/plug-api/lib/transclusion.ts b/plug-api/lib/transclusion.ts index b7914681..78eb3753 100644 --- a/plug-api/lib/transclusion.ts +++ b/plug-api/lib/transclusion.ts @@ -34,9 +34,10 @@ export type ContentDimensions = { * Parse an alias, possibly containing dimensions into an object * @example "alias", "alias|100", "alias|100x200", "100", "100x200" */ -export function parseDimensionFromAlias( - text: string, -): { alias: string; dimension?: ContentDimensions } { +export function parseDimensionFromAlias(text: string): { + alias: string; + dimension?: ContentDimensions; +} { let alias: string; let dim: ContentDimensions | undefined; if (text.includes("|")) { @@ -71,10 +72,8 @@ export function parseDimensionFromAlias( * Parses a transclusion of the type `![[]]` or `![]()` * @param text */ -export function parseTransclusion( - text: string, -): Transclusion | null { - let url, alias ; +export function parseTransclusion(text: string): Transclusion | null { + let url, alias; let linktype: LinkType = "markdownlink"; // TODO: Take in the tree and use tree nodes to get url and alias (Applies to all regex uses) mdLinkRegex.lastIndex = 0; @@ -84,10 +83,7 @@ export function parseTransclusion( ({ url, title: alias } = match.groups); if (isLocalURL(url)) { - url = resolveMarkdownLink( - client.currentName(), - decodeURI(url), - ); + url = resolveMarkdownLink(client.currentName(), decodeURI(url)); } linktype = "markdownlink"; } else if ((match = wikiLinkRegex.exec(text)) && match.groups) { diff --git a/plug-api/lib/tree.test.ts b/plug-api/lib/tree.test.ts index 180aa149..a0410cba 100644 --- a/plug-api/lib/tree.test.ts +++ b/plug-api/lib/tree.test.ts @@ -45,7 +45,9 @@ test("Test parsing", () => { // console.log(JSON.stringify(mdTree, null, 2)); const wikiLink = nodeAtPos(mdTree, mdTest1.indexOf("Wiki Page"))!; expect(wikiLink.type).toEqual("WikiLinkPage"); - expect(findParentMatching(wikiLink, (n) => n.type === "BulletList")).not.toEqual(null); + expect( + findParentMatching(wikiLink, (n) => n.type === "BulletList"), + ).not.toEqual(null); const allTodos = collectNodesMatching(mdTree, (n) => n.type === "Task"); expect(allTodos.length).toEqual(2); diff --git a/plug-api/lib/tree.ts b/plug-api/lib/tree.ts index 94172bda..0dd5e7fb 100644 --- a/plug-api/lib/tree.ts +++ b/plug-api/lib/tree.ts @@ -79,7 +79,7 @@ export async function collectNodesMatchingAsync( for (const child of tree.children) { results = [ ...results, - ...await collectNodesMatchingAsync(child, matchFn), + ...(await collectNodesMatchingAsync(child, matchFn)), ]; } } @@ -255,9 +255,7 @@ export function renderToText(tree?: ParseTree): string { export function cleanTree(tree: ParseTree, omitTrimmable = true): ParseTree { if (tree.type === "โš ") { - throw new Error( - `Parse error at pos ${tree.from}`, - ); + throw new Error(`Parse error at pos ${tree.from}`); } if (tree.text !== undefined) { return tree; @@ -272,7 +270,7 @@ export function cleanTree(tree: ParseTree, omitTrimmable = true): ParseTree { if (node.type && node.type !== "Comment") { ast.children!.push(cleanTree(node, omitTrimmable)); } - if (node.text && (omitTrimmable && node.text.trim() || !omitTrimmable)) { + if (node.text && ((omitTrimmable && node.text.trim()) || !omitTrimmable)) { ast.children!.push(node); } } diff --git a/plug-api/lib/yaml.test.ts b/plug-api/lib/yaml.test.ts index 0ad71af2..154d4d0d 100644 --- a/plug-api/lib/yaml.test.ts +++ b/plug-api/lib/yaml.test.ts @@ -181,9 +181,7 @@ description: A sample package author: John Doe # Main author `; - const patches: YamlPatch[] = [ - { op: "delete-key", path: "version" }, - ]; + const patches: YamlPatch[] = [{ op: "delete-key", path: "version" }]; const result = applyPatches(initialYaml, patches); @@ -214,9 +212,7 @@ dependencies: author: John Doe `; - const patches: YamlPatch[] = [ - { op: "delete-key", path: "tags" }, - ]; + const patches: YamlPatch[] = [{ op: "delete-key", path: "tags" }]; const result = applyPatches(initialYaml, patches); @@ -246,9 +242,7 @@ config: author: John Doe `; - const patches: YamlPatch[] = [ - { op: "delete-key", path: "config" }, - ]; + const patches: YamlPatch[] = [{ op: "delete-key", path: "config" }]; const result = applyPatches(initialYaml, patches); @@ -270,9 +264,7 @@ name: my-package version: 1.0.0 `; - const patches: YamlPatch[] = [ - { op: "delete-key", path: "nonexistent" }, - ]; + const patches: YamlPatch[] = [{ op: "delete-key", path: "nonexistent" }]; const result = applyPatches(initialYaml, patches); @@ -341,9 +333,7 @@ description: A sample package license: MIT `; - const patches: YamlPatch[] = [ - { op: "delete-key", path: "version" }, - ]; + const patches: YamlPatch[] = [{ op: "delete-key", path: "version" }]; const result = applyPatches(initialYaml, patches); diff --git a/plug-api/lib/yaml.ts b/plug-api/lib/yaml.ts index ee1aecf1..f795c901 100644 --- a/plug-api/lib/yaml.ts +++ b/plug-api/lib/yaml.ts @@ -25,7 +25,9 @@ function serializeToYamlScalar( // Simple strings without special chars/meaning don't need quotes return value; } else if ( - typeof value === "number" || typeof value === "boolean" || value === null + typeof value === "number" || + typeof value === "boolean" || + value === null ) { return String(value); } @@ -46,10 +48,15 @@ function serializeToYamlValue( // Determine indentation for list items (base + 2 spaces) const itemIndentation = `${baseIndentation} `; // Format each item recursively, preceded by '- ' marker - return "\n" + - value.map((item) => - `${itemIndentation}- ${serializeToYamlValue(item, itemIndentation)}` - ).join("\n"); + return ( + "\n" + + value + .map( + (item) => + `${itemIndentation}- ${serializeToYamlValue(item, itemIndentation)}`, + ) + .join("\n") + ); // Note: serializeToYamlValue is used recursively here to handle nested arrays/objects if needed in future // However, the current `applyMinimalSetKeyPatches` only handles top-level keys. } else if (typeof value === "object" && value !== null) { @@ -58,22 +65,25 @@ function serializeToYamlValue( const itemIndentation = `${baseIndentation} `; const entries = Object.entries(value); if (entries.length === 0) return "{}"; // Flow style empty objects - return "\n" + - entries.map(([key, val]) => - `${itemIndentation}${key}: ${ - serializeToYamlValue(val, itemIndentation) - }` - ).join("\n"); + return ( + "\n" + + entries + .map( + ([key, val]) => + `${itemIndentation}${key}: ${serializeToYamlValue( + val, + itemIndentation, + )}`, + ) + .join("\n") + ); } else { // Handle scalars using the dedicated function return serializeToYamlScalar(value); } } -export function applyPatches( - yamlString: string, - patches: YamlPatch[], -): string { +export function applyPatches(yamlString: string, patches: YamlPatch[]): string { let currentYaml = yamlString; for (const patch of patches) { diff --git a/plug-api/syscalls/asset.ts b/plug-api/syscalls/asset.ts index 3a614eec..ce4f85f0 100644 --- a/plug-api/syscalls/asset.ts +++ b/plug-api/syscalls/asset.ts @@ -15,7 +15,7 @@ export async function readAsset( name: string, encoding: "utf8" | "dataurl" = "utf8", ): Promise { - const dataUrl = await syscall("asset.readAsset", plugName, name) as string; + const dataUrl = (await syscall("asset.readAsset", plugName, name)) as string; switch (encoding) { case "utf8": return new TextDecoder().decode(base64DecodeDataUrl(dataUrl)); diff --git a/plug-api/syscalls/config.ts b/plug-api/syscalls/config.ts index 4803f90f..9b278502 100644 --- a/plug-api/syscalls/config.ts +++ b/plug-api/syscalls/config.ts @@ -31,10 +31,7 @@ export function set( /** * Inserts a config value into an array */ -export function insert( - path: string | string[], - value: T, -): Promise { +export function insert(path: string | string[], value: T): Promise { return syscall("config.insert", path, value); } diff --git a/plug-api/syscalls/editor.ts b/plug-api/syscalls/editor.ts index c8032e09..20fd7a08 100644 --- a/plug-api/syscalls/editor.ts +++ b/plug-api/syscalls/editor.ts @@ -77,9 +77,11 @@ export function getCursor(): Promise { /** * Returns the line number and column number of the cursor in the editor */ -export function getSelection(): Promise< - { from: number; to: number; text: string } -> { +export function getSelection(): Promise<{ + from: number; + to: number; + text: string; +}> { return syscall("editor.getSelection"); } @@ -259,9 +261,7 @@ export function showPanel( * Hides a panel in the editor * @param id the location of the panel to hide */ -export function hidePanel( - id: "lhs" | "rhs" | "bhs" | "modal", -): Promise { +export function hidePanel(id: "lhs" | "rhs" | "bhs" | "modal"): Promise { return syscall("editor.hidePanel", id); } @@ -360,9 +360,7 @@ export function prompt( * @param message the message to show in the confirmation dialog * @returns */ -export function confirm( - message: string, -): Promise { +export function confirm(message: string): Promise { return syscall("editor.confirm", message); } @@ -371,9 +369,7 @@ export function confirm( * @param message the message to show in the confirmation dialog * @returns */ -export function alert( - message: string, -): Promise { +export function alert(message: string): Promise { return syscall("editor.alert", message); } diff --git a/plug-api/syscalls/jsonschema.ts b/plug-api/syscalls/jsonschema.ts index 66daccf0..72aed754 100644 --- a/plug-api/syscalls/jsonschema.ts +++ b/plug-api/syscalls/jsonschema.ts @@ -18,8 +18,6 @@ export function validateObject( * @param schema the JSON schema to validate * @returns an error message if the schema is invalid, or undefined if it is valid */ -export function validateSchema( - schema: any, -): Promise { +export function validateSchema(schema: any): Promise { return syscall("jsonschema.validateSchema", schema); } diff --git a/plug-api/syscalls/lua.ts b/plug-api/syscalls/lua.ts index 5ee5c764..f8029221 100644 --- a/plug-api/syscalls/lua.ts +++ b/plug-api/syscalls/lua.ts @@ -1,20 +1,14 @@ import { syscall } from "../syscall.ts"; import type { LuaBlock, LuaExpression } from "../../client/space_lua/ast.ts"; -export function parse( - code: string, -): Promise { +export function parse(code: string): Promise { return syscall("lua.parse", code); } -export function parseExpression( - expression: string, -): Promise { +export function parseExpression(expression: string): Promise { return syscall("lua.parseExpression", expression); } -export function evalExpression( - expression: string, -): Promise { +export function evalExpression(expression: string): Promise { return syscall("lua.evalExpression", expression); } diff --git a/plug-api/syscalls/mq.ts b/plug-api/syscalls/mq.ts index 5577bd6d..2113fcd9 100644 --- a/plug-api/syscalls/mq.ts +++ b/plug-api/syscalls/mq.ts @@ -21,10 +21,7 @@ export function send(queue: string, body: any): Promise { * @param queue the name of the queue * @param bodies the bodies of the messages to send */ -export function batchSend( - queue: string, - bodies: any[], -): Promise { +export function batchSend(queue: string, bodies: any[]): Promise { return syscall("mq.batchSend", queue, bodies); } diff --git a/plug-api/syscalls/space.ts b/plug-api/syscalls/space.ts index cf161890..b3d65c7a 100644 --- a/plug-api/syscalls/space.ts +++ b/plug-api/syscalls/space.ts @@ -44,9 +44,7 @@ export function pageExists(name: string): Promise { * @param name the name of the page to read * @returns the text of the page */ -export function readPage( - name: string, -): Promise { +export function readPage(name: string): Promise { return syscall("space.readPage", name); } @@ -111,9 +109,7 @@ export function getDocumentMeta(name: string): Promise { * @param name path of the document to read * @returns the document data as a UInt8Array */ -export function readDocument( - name: string, -): Promise { +export function readDocument(name: string): Promise { return syscall("space.readDocument", name); } @@ -192,10 +188,7 @@ export function getFileMeta(name: string): Promise { * @param data the data of the file to write * @returns the metadata for the written file */ -export function writeFile( - name: string, - data: Uint8Array, -): Promise { +export function writeFile(name: string, data: Uint8Array): Promise { return syscall("space.writeFile", name, data); } diff --git a/plug-api/syscalls/system.ts b/plug-api/syscalls/system.ts index f66d9d3c..ed441d73 100644 --- a/plug-api/syscalls/system.ts +++ b/plug-api/syscalls/system.ts @@ -13,10 +13,7 @@ import type { SyscallMeta } from "@silverbulletmd/silverbullet/type/index"; * @param args arguments to pass to the function * @returns */ -export function invokeFunction( - name: string, - ...args: any[] -): Promise { +export function invokeFunction(name: string, ...args: any[]): Promise { return syscall("system.invokeFunction", name, ...args); } diff --git a/plug-api/syscalls/yaml.ts b/plug-api/syscalls/yaml.ts index 53746ca6..64215cbb 100644 --- a/plug-api/syscalls/yaml.ts +++ b/plug-api/syscalls/yaml.ts @@ -10,9 +10,7 @@ import { syscall } from "../syscall.ts"; * @param text the YAML text to parse * @returns a JavaScript object representation of the YAML text */ -export function parse( - text: string, -): Promise { +export function parse(text: string): Promise { return syscall("yaml.parse", text); } @@ -21,8 +19,6 @@ export function parse( * @param obj the object to stringify * @returns a YAML string representation of the object */ -export function stringify( - obj: any, -): Promise { +export function stringify(obj: any): Promise { return syscall("yaml.stringify", obj); } diff --git a/plug-api/types/event.ts b/plug-api/types/event.ts index 75dc10a1..9b172f79 100644 --- a/plug-api/types/event.ts +++ b/plug-api/types/event.ts @@ -21,7 +21,9 @@ export type EventSubscription = EventSubscriptionDef & { run: (...args: any[]) => Promise; }; -export type ResolvedPlug = { - code: string; - name?: string; // This will only dictate the filename -} | string; +export type ResolvedPlug = + | { + code: string; + name?: string; // This will only dictate the filename + } + | string; diff --git a/plug-api/types/manifest.ts b/plug-api/types/manifest.ts index 61ec8c96..2e5a4d67 100644 --- a/plug-api/types/manifest.ts +++ b/plug-api/types/manifest.ts @@ -81,15 +81,14 @@ export type DocumentEditorT = { * * related: plugos/ui_types.ts#FunctionDef */ -export type SilverBulletHooks = - & CommandHookT - & SlashCommandHookT - & MQHookT - & EventHookT - & CodeWidgetT - & PlugNamespaceHookT - & DocumentEditorT - & SyscallHookT; +export type SilverBulletHooks = CommandHookT & + SlashCommandHookT & + MQHookT & + EventHookT & + CodeWidgetT & + PlugNamespaceHookT & + DocumentEditorT & + SyscallHookT; /** A plug manifest configures hooks, declares syntax extensions, and describes plug metadata. * diff --git a/plugs/builtin_plugs.ts b/plugs/builtin_plugs.ts index c348b196..ac33cf71 100644 --- a/plugs/builtin_plugs.ts +++ b/plugs/builtin_plugs.ts @@ -9,6 +9,6 @@ export const builtinPlugNames = [ "image-viewer", ]; -export const builtinPlugPaths = builtinPlugNames.map((name) => - `Library/Std/Plugs/${name}.plug.js` +export const builtinPlugPaths = builtinPlugNames.map( + (name) => `Library/Std/Plugs/${name}.plug.js`, ); diff --git a/plugs/editor/complete.ts b/plugs/editor/complete.ts index 29c63c84..2247dab9 100644 --- a/plugs/editor/complete.ts +++ b/plugs/editor/complete.ts @@ -54,31 +54,34 @@ export async function pageComplete(completeEvent: CompleteEvent) { })); } else { // This is the most common case, we're combining three types of completions here: - allPages = (await Promise.all([ - // All non-meta pages - index.queryLuaObjects("page", isntMetaPageQuery), - // All documents - index.queryLuaObjects("document", isDocumentQuery), - // And all links to non-existing pages (to augment the existing ones) - index.queryLuaObjects( - "aspiring-page", - { - select: { type: "Variable", name: "name", ctx: {} as any }, - distinct: true, - }, - ).then((aspiringPages) => - // Rewrite them to PageMeta shaped objects - aspiringPages.map((aspiringPage: string): PageMeta => ({ - ref: aspiringPage, - tag: "page", - tags: ["non-existing"], // Picked up later in completion - name: aspiringPage, - created: "", - lastModified: "", - perm: "rw", - })) - ), - ])).flat(); + allPages = ( + await Promise.all([ + // All non-meta pages + index.queryLuaObjects("page", isntMetaPageQuery), + // All documents + index.queryLuaObjects("document", isDocumentQuery), + // And all links to non-existing pages (to augment the existing ones) + index + .queryLuaObjects("aspiring-page", { + select: { type: "Variable", name: "name", ctx: {} as any }, + distinct: true, + }) + .then((aspiringPages) => + // Rewrite them to PageMeta shaped objects + aspiringPages.map( + (aspiringPage: string): PageMeta => ({ + ref: aspiringPage, + tag: "page", + tags: ["non-existing"], // Picked up later in completion + name: aspiringPage, + created: "", + lastModified: "", + perm: "rw", + }), + ), + ), + ]) + ).flat(); } // Don't complete hidden pages @@ -92,7 +95,8 @@ export async function pageComplete(completeEvent: CompleteEvent) { const completions: any[] = []; const namePrefix = (pageMeta as PageMeta).pageDecoration?.prefix || ""; const cssClass = ((pageMeta as PageMeta).pageDecoration?.cssClasses || []) - .join(" ").replaceAll(/[^a-zA-Z0-9-_ ]/g, ""); + .join(" ") + .replaceAll(/[^a-zA-Z0-9-_ ]/g, ""); if (isWikilink) { // A [[wikilink]] @@ -107,9 +111,10 @@ export async function pageComplete(completeEvent: CompleteEvent) { label: linkAlias, displayLabel: decoratedName, boost, - apply: pageMeta.tag === "template" - ? pageMeta.name - : `${pageMeta.name}|${linkAlias}`, + apply: + pageMeta.tag === "template" + ? pageMeta.name + : `${pageMeta.name}|${linkAlias}`, detail: pageMeta.linkName ? `linkName for: ${pageMeta.name}` : `displayName for: ${pageMeta.name}`, @@ -124,9 +129,10 @@ export async function pageComplete(completeEvent: CompleteEvent) { label: `${alias}`, displayLabel: decoratedName, boost: new Date(pageMeta.lastModified).getTime(), - apply: pageMeta.tag === "template" - ? pageMeta.name - : `${pageMeta.name}|${alias}`, + apply: + pageMeta.tag === "template" + ? pageMeta.name + : `${pageMeta.name}|${alias}`, detail: `alias to: ${pageMeta.name}`, type: "page", cssClass, @@ -171,9 +177,7 @@ export async function pageComplete(completeEvent: CompleteEvent) { } export async function languageComplete(completeEvent: CompleteEvent) { - const languagePrefix = /^(?:```+|~~~+)(\w*)$/.exec( - completeEvent.linePrefix, - ); + const languagePrefix = /^(?:```+|~~~+)(\w*)$/.exec(completeEvent.linePrefix); if (!languagePrefix) { return null; } @@ -181,11 +185,9 @@ export async function languageComplete(completeEvent: CompleteEvent) { const allLanguages = await language.listLanguages(); return { from: completeEvent.pos - languagePrefix[1].length, - options: allLanguages.map( - (lang) => ({ - label: lang, - type: "language", - }), - ), + options: allLanguages.map((lang) => ({ + label: lang, + type: "language", + })), }; } diff --git a/plugs/editor/document.ts b/plugs/editor/document.ts index 1e09c2dd..bac5a389 100644 --- a/plugs/editor/document.ts +++ b/plugs/editor/document.ts @@ -3,9 +3,9 @@ import { editor, space } from "@silverbulletmd/silverbullet/syscalls"; export async function deleteDocument() { const documentName = await editor.getCurrentPath(); if ( - !await editor.confirm( + !(await editor.confirm( `Are you sure you would like to delete ${documentName}?`, - ) + )) ) { return; } diff --git a/plugs/editor/editor.ts b/plugs/editor/editor.ts index edd2a8cb..f15e47b0 100644 --- a/plugs/editor/editor.ts +++ b/plugs/editor/editor.ts @@ -12,8 +12,8 @@ export async function setEditorMode() { // TODO: Remove at some point: temporary upgrade code const allSyscalls = await system.listSyscalls(); // console.log("All syscalls", allSyscalls); - const queryLuaObjects = allSyscalls.find((sc) => - sc.name === "index.queryLuaObjects" + const queryLuaObjects = allSyscalls.find( + (sc) => sc.name === "index.queryLuaObjects", ); // console.log(readPageWithMetaCall); @@ -61,10 +61,12 @@ export async function openMetaNavigator() { export async function openTagNavigator() { // Query all tags with a matching parent - const allTags: FilterOption[] = (await index.queryLuaObjects("tag", { - select: { type: "Variable", name: "name", ctx: {} as any }, - distinct: true, - })).map((name) => ({ name })); + const allTags: FilterOption[] = ( + await index.queryLuaObjects("tag", { + select: { type: "Variable", name: "name", ctx: {} as any }, + distinct: true, + }) + ).map((name) => ({ name })); const selectedTag = await editor.filterBox( "Open", @@ -94,9 +96,7 @@ export async function toggleDarkMode() { } export async function toggleMarkdownSyntaxRendering() { - let renderingSyntax = await editor.getUiOption( - "markdownSyntaxRendering", - ); + let renderingSyntax = await editor.getUiOption("markdownSyntaxRendering"); renderingSyntax = !renderingSyntax; await clientStore.set("markdownSyntaxRendering", renderingSyntax); await editor.setUiOption("markdownSyntaxRendering", renderingSyntax); @@ -137,9 +137,7 @@ export async function copyLinkCommand() { } export async function moveToLineCommand() { - let lineString = await editor.prompt( - "Move to line (and optionally column):", - ); + let lineString = await editor.prompt("Move to line (and optionally column):"); if (lineString === undefined) { return; } diff --git a/plugs/editor/navigate.ts b/plugs/editor/navigate.ts index fd6c5820..51fa5f00 100644 --- a/plugs/editor/navigate.ts +++ b/plugs/editor/navigate.ts @@ -23,17 +23,9 @@ async function actionClickOrActionEnter( return; } const navigationNodeFinder = (t: ParseTree) => - [ - "WikiLink", - "Link", - "Image", - "Autolink", - "NakedURL", - "Hashtag", - ] - .includes( - t.type!, - ); + ["WikiLink", "Link", "Image", "Autolink", "NakedURL", "Hashtag"].includes( + t.type!, + ); if (!navigationNodeFinder(mdTree)) { mdTree = findParentMatching(mdTree, navigationNodeFinder); if (!mdTree) { @@ -109,11 +101,7 @@ async function actionClickOrActionEnter( } case "Hashtag": { const hashtag = extractHashtag(mdTree.children![0].text!); - await editor.navigate( - `${tagPrefix}${hashtag}`, - false, - inNewWindow, - ); + await editor.navigate(`${tagPrefix}${hashtag}`, false, inNewWindow); break; } } diff --git a/plugs/editor/outline.test.ts b/plugs/editor/outline.test.ts index 08e816db..aa8201e3 100644 --- a/plugs/editor/outline.test.ts +++ b/plugs/editor/outline.test.ts @@ -22,8 +22,11 @@ function applyOp( if (result === null || result === "blocked") { return input; // no-op returns unchanged } - return result.text.slice(0, result.cursor) + CURSOR + - result.text.slice(result.cursor); + return ( + result.text.slice(0, result.cursor) + + CURSOR + + result.text.slice(result.cursor) + ); } function detect(marked: string): CursorContext | null { @@ -36,22 +39,26 @@ function detect(marked: string): CursorContext | null { // Bullet Lists: Move Up/Down describe("Bullet list move up/down", () => { test("swap two flat bullets", () => { - expect(applyOp( + expect( + applyOp( moveUp, `- first - sec|^|ond `, - )).toEqual(`- sec|^|ond + ), + ).toEqual(`- sec|^|ond - first -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `- fir|^|st - second `, - )).toEqual(`- second + ), + ).toEqual(`- second - fir|^|st -`,); +`); }); test("boundary bullet is no-op", () => { @@ -66,26 +73,30 @@ describe("Bullet list move up/down", () => { }); test("item with children moves as unit", () => { - expect(applyOp( + expect( + applyOp( moveUp, `- first - sec|^|ond - child `, - )).toEqual(`- sec|^|ond + ), + ).toEqual(`- sec|^|ond - child - first -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `- fir|^|st - child - second `, - )).toEqual(`- second + ), + ).toEqual(`- second - fir|^|st - child -`,); +`); }); test("nested child: boundary no-op and swap", () => { @@ -100,49 +111,57 @@ describe("Bullet list move up/down", () => { `; expect(applyOp(moveDown, downBoundary)).toEqual(downBoundary); - expect(applyOp( + expect( + applyOp( moveUp, `- parent - child one - child |^|two `, - )).toEqual(`- parent + ), + ).toEqual(`- parent - child |^|two - child one -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `- parent - child |^|one - child two `, - )).toEqual(`- parent + ), + ).toEqual(`- parent - child two - child |^|one -`,); +`); }); test("three items, swap adjacent", () => { - expect(applyOp( + expect( + applyOp( moveUp, `- first - second - th|^|ird `, - )).toEqual(`- first + ), + ).toEqual(`- first - th|^|ird - second -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `- fir|^|st - second - third `, - )).toEqual(`- second + ), + ).toEqual(`- second - fir|^|st - third -`,); +`); }); }); @@ -181,57 +200,65 @@ describe("Bullet list indent/outdent", () => { }); test("already nested item indents further", () => { - expect(applyOp( + expect( + applyOp( indent, `- first - sec|^|ond `, - )).toEqual(`- first + ), + ).toEqual(`- first - sec|^|ond -`,); +`); }); test("deeply nested item outdents one level", () => { - expect(applyOp( + expect( + applyOp( outdent, `- first - second - th|^|ird `, - )).toEqual(`- first + ), + ).toEqual(`- first - second - th|^|ird -`,); +`); }); }); // Headers: Move Up/Down describe("Heading move up/down", () => { test("swap two h2 sections", () => { - expect(applyOp( + expect( + applyOp( moveUp, `## First Content one ## Sec|^|ond Content two `, - )).toEqual(`## Sec|^|ond + ), + ).toEqual(`## Sec|^|ond Content two ## First Content one -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `## Fir|^|st Content one ## Second Content two `, - )).toEqual(`## Second + ), + ).toEqual(`## Second Content two ## Fir|^|st Content one -`,); +`); }); test("boundary heading is no-op", () => { @@ -247,101 +274,117 @@ Content }); test("h2 with sub-headings moves entire section", () => { - expect(applyOp( + expect( + applyOp( moveUp, `## First ### Sub one ## Sec|^|ond ### Sub two `, - )).toEqual(`## Sec|^|ond + ), + ).toEqual(`## Sec|^|ond ### Sub two ## First ### Sub one -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `## Fir|^|st ### Sub one ## Second ### Sub two `, - )).toEqual(`## Second + ), + ).toEqual(`## Second ### Sub two ## Fir|^|st ### Sub one -`,); +`); }); test("h3 swaps only within parent h2 scope", () => { - expect(applyOp( + expect( + applyOp( moveUp, `## Parent ### Sub one ### Sub |^|two `, - )).toEqual(`## Parent + ), + ).toEqual(`## Parent ### Sub |^|two ### Sub one -`,); +`); }); test("headings with no body text", () => { - expect(applyOp( + expect( + applyOp( moveUp, `## First ## Sec|^|ond `, - )).toEqual(`## Sec|^|ond + ), + ).toEqual(`## Sec|^|ond ## First -`,); +`); }); test("no trailing newline preserves content", () => { - expect(applyOp( + expect( + applyOp( moveUp, `## First Body ## Sec|^|ond End`, - )).toEqual(`## Sec|^|ond + ), + ).toEqual(`## Sec|^|ond End ## First -Body`,); - expect(applyOp( +Body`); + expect( + applyOp( moveDown, `## Fir|^|st Body ## Second End`, - )).toEqual(`## Second + ), + ).toEqual(`## Second End ## Fir|^|st -Body`,); +Body`); }); test("empty body section swap preserves structure", () => { - expect(applyOp( + expect( + applyOp( moveDown, `## |^|A ## B Body B `, - )).toEqual(`## B + ), + ).toEqual(`## B Body B ## |^|A -`,); +`); }); test("no trailing newline, first has no body", () => { - expect(applyOp( + expect( + applyOp( moveDown, `## |^|A ## B Body`, - )).toEqual(`## B + ), + ).toEqual(`## B Body -## |^|A`,); +## |^|A`); }); test("roundtrip: move down then up returns to original", () => { @@ -398,16 +441,18 @@ describe("Heading indent/outdent", () => { // Paragraphs: Move Up/Down describe("Paragraph operations", () => { test("swap two consecutive paragraphs", () => { - expect(applyOp( + expect( + applyOp( moveUp, `First para. Second |^|para. `, - )).toEqual(`Second |^|para. + ), + ).toEqual(`Second |^|para. First para. -`,); +`); }); test("boundary no-ops", () => { @@ -424,65 +469,75 @@ Second |^|para. }); test("moves past an adjacent list block", () => { - expect(applyOp( + expect( + applyOp( moveDown, `Some |^|text. - item one - item two `, - )).toEqual(`- item one + ), + ).toEqual(`- item one - item two Some |^|text. -`,); +`); }); }); // Ordered Lists: all operations describe("Ordered list operations", () => { test("move up", () => { - expect(applyOp( + expect( + applyOp( moveUp, `1. first 2. sec|^|ond `, - )).toEqual(`1. sec|^|ond + ), + ).toEqual(`1. sec|^|ond 2. first -`,); +`); }); test("move down", () => { - expect(applyOp( + expect( + applyOp( moveDown, `1. fir|^|st 2. second `, - )).toEqual(`1. second + ), + ).toEqual(`1. second 2. fir|^|st -`,); +`); }); test("indent", () => { - expect(applyOp( + expect( + applyOp( indent, `1. first 2. sec|^|ond `, - )).toEqual(`1. first + ), + ).toEqual(`1. first 2. sec|^|ond -`,); +`); }); test("outdent", () => { - expect(applyOp( + expect( + applyOp( outdent, `1. first 2. sec|^|ond `, - )).toEqual(`1. first + ), + ).toEqual(`1. first 2. sec|^|ond -`,); +`); }); }); @@ -529,7 +584,9 @@ describe("Edge cases", () => { }); test("cursor at end of nested item moves within nested list", () => { - expect(applyOp(moveDown, "- a\n- b\n - sub1|^|\n - sub2\n- c")).toEqual("- a\n- b\n - sub2\n - sub1|^|\n- c",); + expect(applyOp(moveDown, "- a\n- b\n - sub1|^|\n - sub2\n- c")).toEqual( + "- a\n- b\n - sub2\n - sub1|^|\n- c", + ); }); }); @@ -599,8 +656,10 @@ describe("Context detection edge cases", () => { expect(ctx?.type).toEqual("heading"); if (ctx?.type === "heading") { expect(ctx.level).toEqual(2); - const types = ctx.doc.children!.slice(ctx.sectionStart, ctx.sectionEnd) - .filter((c) => c.type).map((c) => c.type); + const types = ctx.doc + .children!.slice(ctx.sectionStart, ctx.sectionEnd) + .filter((c) => c.type) + .map((c) => c.type); expect(types).toEqual([ "ATXHeading2", "Paragraph", @@ -620,7 +679,9 @@ describe("Context detection edge cases", () => { }); test("paragraph after heading detects paragraph", () => { - expect(detect("## Heading\nBody|^| text here.\n")?.type).toEqual("paragraph",); + expect(detect("## Heading\nBody|^| text here.\n")?.type).toEqual( + "paragraph", + ); }); test("single heading with no following content", () => { @@ -654,30 +715,34 @@ describe("List marker variants", () => { // Table Rows: Move Up/Down describe("Table row move up/down", () => { test("swap two data rows", () => { - expect(applyOp( + expect( + applyOp( moveUp, `| A | B | | --- | --- | | 1 | 2 | | 3|^| | 4 | `, - )).toEqual(`| A | B | + ), + ).toEqual(`| A | B | | --- | --- | | 3|^| | 4 | | 1 | 2 | -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `| A | B | | --- | --- | | 1|^| | 2 | | 3 | 4 | `, - )).toEqual(`| A | B | + ), + ).toEqual(`| A | B | | --- | --- | | 3 | 4 | | 1|^| | 2 | -`,); +`); }); test("first data row can't move up", () => { @@ -708,7 +773,8 @@ describe("Table row move up/down", () => { }); test("three rows, swap middle", () => { - expect(applyOp( + expect( + applyOp( moveUp, `| H1 | H2 | | --- | --- | @@ -716,13 +782,15 @@ describe("Table row move up/down", () => { | c|^| | d | | e | f | `, - )).toEqual(`| H1 | H2 | + ), + ).toEqual(`| H1 | H2 | | --- | --- | | c|^| | d | | a | b | | e | f | -`,); - expect(applyOp( +`); + expect( + applyOp( moveDown, `| H1 | H2 | | --- | --- | @@ -730,12 +798,13 @@ describe("Table row move up/down", () => { | c|^| | d | | e | f | `, - )).toEqual(`| H1 | H2 | + ), + ).toEqual(`| H1 | H2 | | --- | --- | | a | b | | e | f | | c|^| | d | -`,); +`); }); test("indent/outdent on table row is no-op", () => { @@ -770,9 +839,11 @@ describe("Table row context detection", () => { }); test("cursor on delimiter row returns null", () => { - expect(detect(`| A | B | + expect( + detect(`| A | B | | --|^|- | --- | | 1 | 2 | -`)).toEqual(null,); +`), + ).toEqual(null); }); }); diff --git a/plugs/editor/outline_ops.ts b/plugs/editor/outline_ops.ts index 1e838ca6..ece913ab 100644 --- a/plugs/editor/outline_ops.ts +++ b/plugs/editor/outline_ops.ts @@ -7,10 +7,7 @@ import { renderToText, } from "@silverbulletmd/silverbullet/lib/tree"; -export type OutlineResult = - | { text: string; cursor: number } - | "blocked" - | null; +export type OutlineResult = { text: string; cursor: number } | "blocked" | null; export type ListContext = { type: "listItem"; @@ -89,17 +86,15 @@ export function detectContext( // Check if we're in a list item // If nodeAtPos landed on a BulletList/OrderedList itself (i.e. on separator // whitespace between items), don't walk up โ€” cursor isn't on any item. - const listItem = node.type === "ListItem" - ? node - : (node.type === "BulletList" || node.type === "OrderedList") - ? null - : findParentMatching(node, (n) => n.type === "ListItem"); + const listItem = + node.type === "ListItem" + ? node + : node.type === "BulletList" || node.type === "OrderedList" + ? null + : findParentMatching(node, (n) => n.type === "ListItem"); if (listItem) { const list = listItem.parent; - if ( - list && - (list.type === "BulletList" || list.type === "OrderedList") - ) { + if (list && (list.type === "BulletList" || list.type === "OrderedList")) { const itemIndex = list.children!.indexOf(listItem); return { type: "listItem", item: listItem, itemIndex, list }; } @@ -109,9 +104,9 @@ export function detectContext( const heading = node.type?.startsWith("ATXHeading") ? node : findParentMatching( - node, - (n) => n.type?.startsWith("ATXHeading") ?? false, - ); + node, + (n) => n.type?.startsWith("ATXHeading") ?? false, + ); if (heading?.type) { const level = parseInt(heading.type.replace("ATXHeading", ""), 10); const doc = heading.parent!; @@ -122,9 +117,7 @@ export function detectContext( for (let i = headingIndex + 1; i < doc.children.length; i++) { const child = doc.children[i]; if (child.type?.startsWith("ATXHeading")) { - const childLevel = parseInt( - child.type.replace("ATXHeading", ""), 10 - ); + const childLevel = parseInt(child.type.replace("ATXHeading", ""), 10); if (childLevel <= level) { sectionEnd = i; break; @@ -136,12 +129,13 @@ export function detectContext( } // Check if we're in a table row or header - const tableRow = node.type === "TableRow" || node.type === "TableHeader" - ? node - : findParentMatching( - node, - (n) => n.type === "TableRow" || n.type === "TableHeader", - ); + const tableRow = + node.type === "TableRow" || node.type === "TableHeader" + ? node + : findParentMatching( + node, + (n) => n.type === "TableRow" || n.type === "TableHeader", + ); if (tableRow && tableRow.parent?.type === "Table") { const table = tableRow.parent; const rowIndex = table.children!.indexOf(tableRow); @@ -150,9 +144,10 @@ export function detectContext( } // Check if we're in a paragraph at the Document level - const para = node.type === "Paragraph" - ? node - : findParentMatching(node, (n) => n.type === "Paragraph"); + const para = + node.type === "Paragraph" + ? node + : findParentMatching(node, (n) => n.type === "Paragraph"); if (para && para.parent?.type === "Document") { const doc = para.parent; const blockIndex = doc.children!.indexOf(para); @@ -196,12 +191,17 @@ function swapRegions( const firstContent = text.slice(firstFrom, firstTo); const separator = text.slice(firstTo, secondFrom); const secondContent = text.slice(secondFrom, secondTo); - const newText = text.slice(0, firstFrom) + secondContent + separator + - firstContent + text.slice(secondTo); + const newText = + text.slice(0, firstFrom) + + secondContent + + separator + + firstContent + + text.slice(secondTo); const offsetInCurr = cursor - currFrom; - const newCursor = direction === "up" - ? firstFrom + offsetInCurr - : firstFrom + secondContent.length + separator.length + offsetInCurr; + const newCursor = + direction === "up" + ? firstFrom + offsetInCurr + : firstFrom + secondContent.length + separator.length + offsetInCurr; return { text: newText, cursor: newCursor }; } @@ -216,12 +216,9 @@ function renumberOrderedList( ): string { const listText = text.slice(listFrom, listTo); let num = 1; - const renumbered = listText.replace( - /^(\s*)(\d+)\./gm, - (_match, indent) => { - return `${indent}${num++}.`; - }, - ); + const renumbered = listText.replace(/^(\s*)(\d+)\./gm, (_match, indent) => { + return `${indent}${num++}.`; + }); return text.slice(0, listFrom) + renumbered + text.slice(listTo); } @@ -272,9 +269,11 @@ function adjustLevel( } switch (ctx.type) { case "listItem": - return (delta === 1 - ? indentListItem(text, cursor, ctx) - : outdentListItem(text, cursor, ctx)) ?? "blocked"; + return ( + (delta === 1 + ? indentListItem(text, cursor, ctx) + : outdentListItem(text, cursor, ctx)) ?? "blocked" + ); case "heading": return adjustHeadingLevel(text, cursor, ctx, delta) ?? "blocked"; case "paragraph": @@ -392,9 +391,10 @@ function indentListItem( const lineTo = item.to!; const itemLines = text.slice(lineFrom, lineTo); - const indented = itemLines.split("\n").map((line) => - line ? indentStr + line : line - ).join("\n"); + const indented = itemLines + .split("\n") + .map((line) => (line ? indentStr + line : line)) + .join("\n"); const newText = text.slice(0, lineFrom) + indented + text.slice(lineTo); @@ -425,16 +425,19 @@ function outdentListItem( const indentWidth = listIndentWidth(list); - const outdented = itemLines.split("\n").map((line) => { - if (line.startsWith(" ".repeat(indentWidth))) { - return line.substring(indentWidth); - } - let removed = 0; - while (removed < indentWidth && line[removed] === " ") { - removed++; - } - return line.substring(removed); - }).join("\n"); + const outdented = itemLines + .split("\n") + .map((line) => { + if (line.startsWith(" ".repeat(indentWidth))) { + return line.substring(indentWidth); + } + let removed = 0; + while (removed < indentWidth && line[removed] === " ") { + removed++; + } + return line.substring(removed); + }) + .join("\n"); const newText = text.slice(0, lineFrom) + outdented + text.slice(lineTo); @@ -517,9 +520,10 @@ function moveHeading( } // Normalize to [first, second] order - const [firstStart, firstEnd, secondStart, secondEnd] = direction === "up" - ? [adjSectionStart, adjSectionEnd, sectionStart, sectionEnd] - : [sectionStart, sectionEnd, adjSectionStart, adjSectionEnd]; + const [firstStart, firstEnd, secondStart, secondEnd] = + direction === "up" + ? [adjSectionStart, adjSectionEnd, sectionStart, sectionEnd] + : [sectionStart, sectionEnd, adjSectionStart, adjSectionEnd]; return swapRegions( text, @@ -585,8 +589,8 @@ function adjustHeadingLevel( } newSectionText += sectionText.slice(pos); - const newText = text.slice(0, sectionFrom) + newSectionText + - text.slice(sectionTo); + const newText = + text.slice(0, sectionFrom) + newSectionText + text.slice(sectionTo); return { text: newText, cursor: cursor + cursorAdjust }; } diff --git a/plugs/editor/page.ts b/plugs/editor/page.ts index 54fd94ab..7c1dcb58 100644 --- a/plugs/editor/page.ts +++ b/plugs/editor/page.ts @@ -4,7 +4,9 @@ import { notFoundError } from "@silverbulletmd/silverbullet/constants"; export async function deletePage() { const pageName = await editor.getCurrentPage(); if ( - !await editor.confirm(`Are you sure you would like to delete ${pageName}?`) + !(await editor.confirm( + `Are you sure you would like to delete ${pageName}?`, + )) ) { return; } @@ -37,7 +39,10 @@ export async function copyPage( } newName = newName.trim(); if (newName === "") { - void editor.flashNotification("Must provide a non-empty page name.", "error"); + void editor.flashNotification( + "Must provide a non-empty page name.", + "error", + ); return; } diff --git a/plugs/editor/system.ts b/plugs/editor/system.ts index fca22872..8b86c75f 100644 --- a/plugs/editor/system.ts +++ b/plugs/editor/system.ts @@ -17,9 +17,9 @@ export async function parsePageCommand() { export async function wipeClientCommand() { if ( - !await editor.confirm( + !(await editor.confirm( "Are you sure you want to wipe the client? This will clean the local cache.", - ) + )) ) { return; } diff --git a/plugs/editor/text.ts b/plugs/editor/text.ts index b241f355..c793ab2a 100644 --- a/plugs/editor/text.ts +++ b/plugs/editor/text.ts @@ -53,12 +53,10 @@ export async function numberListifySelection() { from++; text = text.slice(from, selection.to); let counter = 1; - text = `1. ${ - text.replaceAll(/\n(?!\n)/g, () => { - counter++; - return `\n${counter}. `; - }) - }`; + text = `1. ${text.replaceAll(/\n(?!\n)/g, () => { + counter++; + return `\n${counter}. `; + })}`; await editor.replaceRange(from, selection.to, text); } diff --git a/plugs/editor/upload.ts b/plugs/editor/upload.ts index 32589e49..dd400101 100644 --- a/plugs/editor/upload.ts +++ b/plugs/editor/upload.ts @@ -98,7 +98,7 @@ export async function saveFile(file: UploadFile) { await space.writeDocument(finalFilePath, file.content); - if (await editor.getCurrentEditor() === "page") { + if ((await editor.getCurrentEditor()) === "page") { const linkStyle = await system.getConfig( "defaultLinkStyle", defaultLinkStyle, diff --git a/plugs/emoji/emoji.json b/plugs/emoji/emoji.json index 45416a75..f57d2e6d 100644 --- a/plugs/emoji/emoji.json +++ b/plugs/emoji/emoji.json @@ -1 +1 @@ -"grinning_face ๐Ÿ˜€|grinning_face_with_big_eyes ๐Ÿ˜ƒ|grinning_face_with_smiling_eyes ๐Ÿ˜„|beaming_face_with_smiling_eyes ๐Ÿ˜|grinning_squinting_face ๐Ÿ˜†|grinning_face_with_sweat ๐Ÿ˜…|rolling_on_the_floor_laughing ๐Ÿคฃ|face_with_tears_of_joy ๐Ÿ˜‚|slightly_smiling_face ๐Ÿ™‚|upside_down_face ๐Ÿ™ƒ|melting_face ๐Ÿซ |winking_face ๐Ÿ˜‰|smiling_face_with_smiling_eyes ๐Ÿ˜Š|smiling_face_with_halo ๐Ÿ˜‡|smiling_face_with_hearts ๐Ÿฅฐ|smiling_face_with_heart_eyes ๐Ÿ˜|star_struck ๐Ÿคฉ|face_blowing_a_kiss ๐Ÿ˜˜|kissing_face ๐Ÿ˜—|smiling_face โ˜บ๏ธ|smiling_face โ˜บ|kissing_face_with_closed_eyes ๐Ÿ˜š|kissing_face_with_smiling_eyes ๐Ÿ˜™|smiling_face_with_tear ๐Ÿฅฒ|face_savoring_food ๐Ÿ˜‹|face_with_tongue ๐Ÿ˜›|winking_face_with_tongue ๐Ÿ˜œ|zany_face ๐Ÿคช|squinting_face_with_tongue ๐Ÿ˜|money_mouth_face ๐Ÿค‘|smiling_face_with_open_hands ๐Ÿค—|face_with_hand_over_mouth ๐Ÿคญ|face_with_open_eyes_and_hand_over_mouth ๐Ÿซข|face_with_peeking_eye ๐Ÿซฃ|shushing_face ๐Ÿคซ|thinking_face ๐Ÿค”|saluting_face ๐Ÿซก|zipper_mouth_face ๐Ÿค|face_with_raised_eyebrow ๐Ÿคจ|neutral_face ๐Ÿ˜|expressionless_face ๐Ÿ˜‘|face_without_mouth ๐Ÿ˜ถ|dotted_line_face ๐Ÿซฅ|face_in_clouds ๐Ÿ˜ถโ€๐ŸŒซ๏ธ|face_in_clouds ๐Ÿ˜ถโ€๐ŸŒซ|smirking_face ๐Ÿ˜|unamused_face ๐Ÿ˜’|face_with_rolling_eyes ๐Ÿ™„|grimacing_face ๐Ÿ˜ฌ|face_exhaling ๐Ÿ˜ฎโ€๐Ÿ’จ|lying_face ๐Ÿคฅ|shaking_face ๐Ÿซจ|head_shaking_horizontally ๐Ÿ™‚โ€โ†”๏ธ|head_shaking_horizontally ๐Ÿ™‚โ€โ†”|head_shaking_vertically ๐Ÿ™‚โ€โ†•๏ธ|head_shaking_vertically ๐Ÿ™‚โ€โ†•|relieved_face ๐Ÿ˜Œ|pensive_face ๐Ÿ˜”|sleepy_face ๐Ÿ˜ช|drooling_face ๐Ÿคค|sleeping_face ๐Ÿ˜ด|face_with_medical_mask ๐Ÿ˜ท|face_with_thermometer ๐Ÿค’|face_with_head_bandage ๐Ÿค•|nauseated_face ๐Ÿคข|face_vomiting ๐Ÿคฎ|sneezing_face ๐Ÿคง|hot_face ๐Ÿฅต|cold_face ๐Ÿฅถ|woozy_face ๐Ÿฅด|face_with_crossed_out_eyes ๐Ÿ˜ต|face_with_spiral_eyes ๐Ÿ˜ตโ€๐Ÿ’ซ|exploding_head ๐Ÿคฏ|cowboy_hat_face ๐Ÿค |partying_face ๐Ÿฅณ|disguised_face ๐Ÿฅธ|smiling_face_with_sunglasses ๐Ÿ˜Ž|nerd_face ๐Ÿค“|face_with_monocle ๐Ÿง|confused_face ๐Ÿ˜•|face_with_diagonal_mouth ๐Ÿซค|worried_face ๐Ÿ˜Ÿ|slightly_frowning_face ๐Ÿ™|frowning_face โ˜น๏ธ|frowning_face โ˜น|face_with_open_mouth ๐Ÿ˜ฎ|hushed_face ๐Ÿ˜ฏ|astonished_face ๐Ÿ˜ฒ|flushed_face ๐Ÿ˜ณ|pleading_face ๐Ÿฅบ|face_holding_back_tears ๐Ÿฅน|frowning_face_with_open_mouth ๐Ÿ˜ฆ|anguished_face ๐Ÿ˜ง|fearful_face ๐Ÿ˜จ|anxious_face_with_sweat ๐Ÿ˜ฐ|sad_but_relieved_face ๐Ÿ˜ฅ|crying_face ๐Ÿ˜ข|loudly_crying_face ๐Ÿ˜ญ|face_screaming_in_fear ๐Ÿ˜ฑ|confounded_face ๐Ÿ˜–|persevering_face ๐Ÿ˜ฃ|disappointed_face ๐Ÿ˜ž|downcast_face_with_sweat ๐Ÿ˜“|weary_face ๐Ÿ˜ฉ|tired_face ๐Ÿ˜ซ|yawning_face ๐Ÿฅฑ|face_with_steam_from_nose ๐Ÿ˜ค|enraged_face ๐Ÿ˜ก|angry_face ๐Ÿ˜ |face_with_symbols_on_mouth ๐Ÿคฌ|smiling_face_with_horns ๐Ÿ˜ˆ|angry_face_with_horns ๐Ÿ‘ฟ|skull ๐Ÿ’€|skull_and_crossbones โ˜ ๏ธ|skull_and_crossbones โ˜ |pile_of_poo ๐Ÿ’ฉ|clown_face ๐Ÿคก|ogre ๐Ÿ‘น|goblin ๐Ÿ‘บ|ghost ๐Ÿ‘ป|alien ๐Ÿ‘ฝ|alien_monster ๐Ÿ‘พ|robot ๐Ÿค–|grinning_cat ๐Ÿ˜บ|grinning_cat_with_smiling_eyes ๐Ÿ˜ธ|cat_with_tears_of_joy ๐Ÿ˜น|smiling_cat_with_heart_eyes ๐Ÿ˜ป|cat_with_wry_smile ๐Ÿ˜ผ|kissing_cat ๐Ÿ˜ฝ|weary_cat ๐Ÿ™€|crying_cat ๐Ÿ˜ฟ|pouting_cat ๐Ÿ˜พ|see_no_evil_monkey ๐Ÿ™ˆ|hear_no_evil_monkey ๐Ÿ™‰|speak_no_evil_monkey ๐Ÿ™Š|love_letter ๐Ÿ’Œ|heart_with_arrow ๐Ÿ’˜|heart_with_ribbon ๐Ÿ’|sparkling_heart ๐Ÿ’–|growing_heart ๐Ÿ’—|beating_heart ๐Ÿ’“|revolving_hearts ๐Ÿ’ž|two_hearts ๐Ÿ’•|heart_decoration ๐Ÿ’Ÿ|heart_exclamation โฃ๏ธ|heart_exclamation โฃ|broken_heart ๐Ÿ’”|heart_on_fire โค๏ธโ€๐Ÿ”ฅ|heart_on_fire โคโ€๐Ÿ”ฅ|mending_heart โค๏ธโ€๐Ÿฉน|mending_heart โคโ€๐Ÿฉน|red_heart โค๏ธ|red_heart โค|pink_heart ๐Ÿฉท|orange_heart ๐Ÿงก|yellow_heart ๐Ÿ’›|green_heart ๐Ÿ’š|blue_heart ๐Ÿ’™|light_blue_heart ๐Ÿฉต|purple_heart ๐Ÿ’œ|brown_heart ๐ŸคŽ|black_heart ๐Ÿ–ค|grey_heart ๐Ÿฉถ|white_heart ๐Ÿค|kiss_mark ๐Ÿ’‹|hundred_points ๐Ÿ’ฏ|anger_symbol ๐Ÿ’ข|collision ๐Ÿ’ฅ|dizzy ๐Ÿ’ซ|sweat_droplets ๐Ÿ’ฆ|dashing_away ๐Ÿ’จ|hole ๐Ÿ•ณ๏ธ|hole ๐Ÿ•ณ|speech_balloon ๐Ÿ’ฌ|eye_in_speech_bubble ๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ|eye_in_speech_bubble ๐Ÿ‘โ€๐Ÿ—จ๏ธ|eye_in_speech_bubble ๐Ÿ‘๏ธโ€๐Ÿ—จ|eye_in_speech_bubble ๐Ÿ‘โ€๐Ÿ—จ|left_speech_bubble ๐Ÿ—จ๏ธ|left_speech_bubble ๐Ÿ—จ|right_anger_bubble ๐Ÿ—ฏ๏ธ|right_anger_bubble ๐Ÿ—ฏ|thought_balloon ๐Ÿ’ญ|zzz ๐Ÿ’ค|waving_hand ๐Ÿ‘‹|waving_hand_light_skin_tone ๐Ÿ‘‹๐Ÿป|waving_hand_medium_light_skin_tone ๐Ÿ‘‹๐Ÿผ|waving_hand_medium_skin_tone ๐Ÿ‘‹๐Ÿฝ|waving_hand_medium_dark_skin_tone ๐Ÿ‘‹๐Ÿพ|waving_hand_dark_skin_tone ๐Ÿ‘‹๐Ÿฟ|raised_back_of_hand ๐Ÿคš|raised_back_of_hand_light_skin_tone ๐Ÿคš๐Ÿป|raised_back_of_hand_medium_light_skin_tone ๐Ÿคš๐Ÿผ|raised_back_of_hand_medium_skin_tone ๐Ÿคš๐Ÿฝ|raised_back_of_hand_medium_dark_skin_tone ๐Ÿคš๐Ÿพ|raised_back_of_hand_dark_skin_tone ๐Ÿคš๐Ÿฟ|hand_with_fingers_splayed ๐Ÿ–๏ธ|hand_with_fingers_splayed ๐Ÿ–|hand_with_fingers_splayed_light_skin_tone ๐Ÿ–๐Ÿป|hand_with_fingers_splayed_medium_light_skin_tone ๐Ÿ–๐Ÿผ|hand_with_fingers_splayed_medium_skin_tone ๐Ÿ–๐Ÿฝ|hand_with_fingers_splayed_medium_dark_skin_tone ๐Ÿ–๐Ÿพ|hand_with_fingers_splayed_dark_skin_tone ๐Ÿ–๐Ÿฟ|raised_hand โœ‹|raised_hand_light_skin_tone โœ‹๐Ÿป|raised_hand_medium_light_skin_tone โœ‹๐Ÿผ|raised_hand_medium_skin_tone โœ‹๐Ÿฝ|raised_hand_medium_dark_skin_tone โœ‹๐Ÿพ|raised_hand_dark_skin_tone โœ‹๐Ÿฟ|vulcan_salute ๐Ÿ––|vulcan_salute_light_skin_tone ๐Ÿ––๐Ÿป|vulcan_salute_medium_light_skin_tone ๐Ÿ––๐Ÿผ|vulcan_salute_medium_skin_tone ๐Ÿ––๐Ÿฝ|vulcan_salute_medium_dark_skin_tone ๐Ÿ––๐Ÿพ|vulcan_salute_dark_skin_tone ๐Ÿ––๐Ÿฟ|rightwards_hand ๐Ÿซฑ|rightwards_hand_light_skin_tone ๐Ÿซฑ๐Ÿป|rightwards_hand_medium_light_skin_tone ๐Ÿซฑ๐Ÿผ|rightwards_hand_medium_skin_tone ๐Ÿซฑ๐Ÿฝ|rightwards_hand_medium_dark_skin_tone ๐Ÿซฑ๐Ÿพ|rightwards_hand_dark_skin_tone ๐Ÿซฑ๐Ÿฟ|leftwards_hand ๐Ÿซฒ|leftwards_hand_light_skin_tone ๐Ÿซฒ๐Ÿป|leftwards_hand_medium_light_skin_tone ๐Ÿซฒ๐Ÿผ|leftwards_hand_medium_skin_tone ๐Ÿซฒ๐Ÿฝ|leftwards_hand_medium_dark_skin_tone ๐Ÿซฒ๐Ÿพ|leftwards_hand_dark_skin_tone ๐Ÿซฒ๐Ÿฟ|palm_down_hand ๐Ÿซณ|palm_down_hand_light_skin_tone ๐Ÿซณ๐Ÿป|palm_down_hand_medium_light_skin_tone ๐Ÿซณ๐Ÿผ|palm_down_hand_medium_skin_tone ๐Ÿซณ๐Ÿฝ|palm_down_hand_medium_dark_skin_tone ๐Ÿซณ๐Ÿพ|palm_down_hand_dark_skin_tone ๐Ÿซณ๐Ÿฟ|palm_up_hand ๐Ÿซด|palm_up_hand_light_skin_tone ๐Ÿซด๐Ÿป|palm_up_hand_medium_light_skin_tone ๐Ÿซด๐Ÿผ|palm_up_hand_medium_skin_tone ๐Ÿซด๐Ÿฝ|palm_up_hand_medium_dark_skin_tone ๐Ÿซด๐Ÿพ|palm_up_hand_dark_skin_tone ๐Ÿซด๐Ÿฟ|leftwards_pushing_hand ๐Ÿซท|leftwards_pushing_hand_light_skin_tone ๐Ÿซท๐Ÿป|leftwards_pushing_hand_medium_light_skin_tone ๐Ÿซท๐Ÿผ|leftwards_pushing_hand_medium_skin_tone ๐Ÿซท๐Ÿฝ|leftwards_pushing_hand_medium_dark_skin_tone ๐Ÿซท๐Ÿพ|leftwards_pushing_hand_dark_skin_tone ๐Ÿซท๐Ÿฟ|rightwards_pushing_hand ๐Ÿซธ|rightwards_pushing_hand_light_skin_tone ๐Ÿซธ๐Ÿป|rightwards_pushing_hand_medium_light_skin_tone ๐Ÿซธ๐Ÿผ|rightwards_pushing_hand_medium_skin_tone ๐Ÿซธ๐Ÿฝ|rightwards_pushing_hand_medium_dark_skin_tone ๐Ÿซธ๐Ÿพ|rightwards_pushing_hand_dark_skin_tone ๐Ÿซธ๐Ÿฟ|ok_hand ๐Ÿ‘Œ|ok_hand_light_skin_tone ๐Ÿ‘Œ๐Ÿป|ok_hand_medium_light_skin_tone ๐Ÿ‘Œ๐Ÿผ|ok_hand_medium_skin_tone ๐Ÿ‘Œ๐Ÿฝ|ok_hand_medium_dark_skin_tone ๐Ÿ‘Œ๐Ÿพ|ok_hand_dark_skin_tone ๐Ÿ‘Œ๐Ÿฟ|pinched_fingers ๐ŸคŒ|pinched_fingers_light_skin_tone ๐ŸคŒ๐Ÿป|pinched_fingers_medium_light_skin_tone ๐ŸคŒ๐Ÿผ|pinched_fingers_medium_skin_tone ๐ŸคŒ๐Ÿฝ|pinched_fingers_medium_dark_skin_tone ๐ŸคŒ๐Ÿพ|pinched_fingers_dark_skin_tone ๐ŸคŒ๐Ÿฟ|pinching_hand ๐Ÿค|pinching_hand_light_skin_tone ๐Ÿค๐Ÿป|pinching_hand_medium_light_skin_tone ๐Ÿค๐Ÿผ|pinching_hand_medium_skin_tone ๐Ÿค๐Ÿฝ|pinching_hand_medium_dark_skin_tone ๐Ÿค๐Ÿพ|pinching_hand_dark_skin_tone ๐Ÿค๐Ÿฟ|victory_hand โœŒ๏ธ|victory_hand โœŒ|victory_hand_light_skin_tone โœŒ๐Ÿป|victory_hand_medium_light_skin_tone โœŒ๐Ÿผ|victory_hand_medium_skin_tone โœŒ๐Ÿฝ|victory_hand_medium_dark_skin_tone โœŒ๐Ÿพ|victory_hand_dark_skin_tone โœŒ๐Ÿฟ|crossed_fingers ๐Ÿคž|crossed_fingers_light_skin_tone ๐Ÿคž๐Ÿป|crossed_fingers_medium_light_skin_tone ๐Ÿคž๐Ÿผ|crossed_fingers_medium_skin_tone ๐Ÿคž๐Ÿฝ|crossed_fingers_medium_dark_skin_tone ๐Ÿคž๐Ÿพ|crossed_fingers_dark_skin_tone ๐Ÿคž๐Ÿฟ|hand_with_index_finger_and_thumb_crossed ๐Ÿซฐ|hand_with_index_finger_and_thumb_crossed_light_skin_tone ๐Ÿซฐ๐Ÿป|hand_with_index_finger_and_thumb_crossed_medium_light_skin_tone ๐Ÿซฐ๐Ÿผ|hand_with_index_finger_and_thumb_crossed_medium_skin_tone ๐Ÿซฐ๐Ÿฝ|hand_with_index_finger_and_thumb_crossed_medium_dark_skin_tone ๐Ÿซฐ๐Ÿพ|hand_with_index_finger_and_thumb_crossed_dark_skin_tone ๐Ÿซฐ๐Ÿฟ|love_you_gesture ๐ŸคŸ|love_you_gesture_light_skin_tone ๐ŸคŸ๐Ÿป|love_you_gesture_medium_light_skin_tone ๐ŸคŸ๐Ÿผ|love_you_gesture_medium_skin_tone ๐ŸคŸ๐Ÿฝ|love_you_gesture_medium_dark_skin_tone ๐ŸคŸ๐Ÿพ|love_you_gesture_dark_skin_tone ๐ŸคŸ๐Ÿฟ|sign_of_the_horns ๐Ÿค˜|sign_of_the_horns_light_skin_tone ๐Ÿค˜๐Ÿป|sign_of_the_horns_medium_light_skin_tone ๐Ÿค˜๐Ÿผ|sign_of_the_horns_medium_skin_tone ๐Ÿค˜๐Ÿฝ|sign_of_the_horns_medium_dark_skin_tone ๐Ÿค˜๐Ÿพ|sign_of_the_horns_dark_skin_tone ๐Ÿค˜๐Ÿฟ|call_me_hand ๐Ÿค™|call_me_hand_light_skin_tone ๐Ÿค™๐Ÿป|call_me_hand_medium_light_skin_tone ๐Ÿค™๐Ÿผ|call_me_hand_medium_skin_tone ๐Ÿค™๐Ÿฝ|call_me_hand_medium_dark_skin_tone ๐Ÿค™๐Ÿพ|call_me_hand_dark_skin_tone ๐Ÿค™๐Ÿฟ|backhand_index_pointing_left ๐Ÿ‘ˆ|backhand_index_pointing_left_light_skin_tone ๐Ÿ‘ˆ๐Ÿป|backhand_index_pointing_left_medium_light_skin_tone ๐Ÿ‘ˆ๐Ÿผ|backhand_index_pointing_left_medium_skin_tone ๐Ÿ‘ˆ๐Ÿฝ|backhand_index_pointing_left_medium_dark_skin_tone ๐Ÿ‘ˆ๐Ÿพ|backhand_index_pointing_left_dark_skin_tone ๐Ÿ‘ˆ๐Ÿฟ|backhand_index_pointing_right ๐Ÿ‘‰|backhand_index_pointing_right_light_skin_tone ๐Ÿ‘‰๐Ÿป|backhand_index_pointing_right_medium_light_skin_tone ๐Ÿ‘‰๐Ÿผ|backhand_index_pointing_right_medium_skin_tone ๐Ÿ‘‰๐Ÿฝ|backhand_index_pointing_right_medium_dark_skin_tone ๐Ÿ‘‰๐Ÿพ|backhand_index_pointing_right_dark_skin_tone ๐Ÿ‘‰๐Ÿฟ|backhand_index_pointing_up ๐Ÿ‘†|backhand_index_pointing_up_light_skin_tone ๐Ÿ‘†๐Ÿป|backhand_index_pointing_up_medium_light_skin_tone ๐Ÿ‘†๐Ÿผ|backhand_index_pointing_up_medium_skin_tone ๐Ÿ‘†๐Ÿฝ|backhand_index_pointing_up_medium_dark_skin_tone ๐Ÿ‘†๐Ÿพ|backhand_index_pointing_up_dark_skin_tone ๐Ÿ‘†๐Ÿฟ|middle_finger ๐Ÿ–•|middle_finger_light_skin_tone ๐Ÿ–•๐Ÿป|middle_finger_medium_light_skin_tone ๐Ÿ–•๐Ÿผ|middle_finger_medium_skin_tone ๐Ÿ–•๐Ÿฝ|middle_finger_medium_dark_skin_tone ๐Ÿ–•๐Ÿพ|middle_finger_dark_skin_tone ๐Ÿ–•๐Ÿฟ|backhand_index_pointing_down ๐Ÿ‘‡|backhand_index_pointing_down_light_skin_tone ๐Ÿ‘‡๐Ÿป|backhand_index_pointing_down_medium_light_skin_tone ๐Ÿ‘‡๐Ÿผ|backhand_index_pointing_down_medium_skin_tone ๐Ÿ‘‡๐Ÿฝ|backhand_index_pointing_down_medium_dark_skin_tone ๐Ÿ‘‡๐Ÿพ|backhand_index_pointing_down_dark_skin_tone ๐Ÿ‘‡๐Ÿฟ|index_pointing_up โ˜๏ธ|index_pointing_up โ˜|index_pointing_up_light_skin_tone โ˜๐Ÿป|index_pointing_up_medium_light_skin_tone โ˜๐Ÿผ|index_pointing_up_medium_skin_tone โ˜๐Ÿฝ|index_pointing_up_medium_dark_skin_tone โ˜๐Ÿพ|index_pointing_up_dark_skin_tone โ˜๐Ÿฟ|index_pointing_at_the_viewer ๐Ÿซต|index_pointing_at_the_viewer_light_skin_tone ๐Ÿซต๐Ÿป|index_pointing_at_the_viewer_medium_light_skin_tone ๐Ÿซต๐Ÿผ|index_pointing_at_the_viewer_medium_skin_tone ๐Ÿซต๐Ÿฝ|index_pointing_at_the_viewer_medium_dark_skin_tone ๐Ÿซต๐Ÿพ|index_pointing_at_the_viewer_dark_skin_tone ๐Ÿซต๐Ÿฟ|thumbs_up ๐Ÿ‘|thumbs_up_light_skin_tone ๐Ÿ‘๐Ÿป|thumbs_up_medium_light_skin_tone ๐Ÿ‘๐Ÿผ|thumbs_up_medium_skin_tone ๐Ÿ‘๐Ÿฝ|thumbs_up_medium_dark_skin_tone ๐Ÿ‘๐Ÿพ|thumbs_up_dark_skin_tone ๐Ÿ‘๐Ÿฟ|thumbs_down ๐Ÿ‘Ž|thumbs_down_light_skin_tone ๐Ÿ‘Ž๐Ÿป|thumbs_down_medium_light_skin_tone ๐Ÿ‘Ž๐Ÿผ|thumbs_down_medium_skin_tone ๐Ÿ‘Ž๐Ÿฝ|thumbs_down_medium_dark_skin_tone ๐Ÿ‘Ž๐Ÿพ|thumbs_down_dark_skin_tone ๐Ÿ‘Ž๐Ÿฟ|raised_fist โœŠ|raised_fist_light_skin_tone โœŠ๐Ÿป|raised_fist_medium_light_skin_tone โœŠ๐Ÿผ|raised_fist_medium_skin_tone โœŠ๐Ÿฝ|raised_fist_medium_dark_skin_tone โœŠ๐Ÿพ|raised_fist_dark_skin_tone โœŠ๐Ÿฟ|oncoming_fist ๐Ÿ‘Š|oncoming_fist_light_skin_tone ๐Ÿ‘Š๐Ÿป|oncoming_fist_medium_light_skin_tone ๐Ÿ‘Š๐Ÿผ|oncoming_fist_medium_skin_tone ๐Ÿ‘Š๐Ÿฝ|oncoming_fist_medium_dark_skin_tone ๐Ÿ‘Š๐Ÿพ|oncoming_fist_dark_skin_tone ๐Ÿ‘Š๐Ÿฟ|left_facing_fist ๐Ÿค›|left_facing_fist_light_skin_tone ๐Ÿค›๐Ÿป|left_facing_fist_medium_light_skin_tone ๐Ÿค›๐Ÿผ|left_facing_fist_medium_skin_tone ๐Ÿค›๐Ÿฝ|left_facing_fist_medium_dark_skin_tone ๐Ÿค›๐Ÿพ|left_facing_fist_dark_skin_tone ๐Ÿค›๐Ÿฟ|right_facing_fist ๐Ÿคœ|right_facing_fist_light_skin_tone ๐Ÿคœ๐Ÿป|right_facing_fist_medium_light_skin_tone ๐Ÿคœ๐Ÿผ|right_facing_fist_medium_skin_tone ๐Ÿคœ๐Ÿฝ|right_facing_fist_medium_dark_skin_tone ๐Ÿคœ๐Ÿพ|right_facing_fist_dark_skin_tone ๐Ÿคœ๐Ÿฟ|clapping_hands ๐Ÿ‘|clapping_hands_light_skin_tone ๐Ÿ‘๐Ÿป|clapping_hands_medium_light_skin_tone ๐Ÿ‘๐Ÿผ|clapping_hands_medium_skin_tone ๐Ÿ‘๐Ÿฝ|clapping_hands_medium_dark_skin_tone ๐Ÿ‘๐Ÿพ|clapping_hands_dark_skin_tone ๐Ÿ‘๐Ÿฟ|raising_hands ๐Ÿ™Œ|raising_hands_light_skin_tone ๐Ÿ™Œ๐Ÿป|raising_hands_medium_light_skin_tone ๐Ÿ™Œ๐Ÿผ|raising_hands_medium_skin_tone ๐Ÿ™Œ๐Ÿฝ|raising_hands_medium_dark_skin_tone ๐Ÿ™Œ๐Ÿพ|raising_hands_dark_skin_tone ๐Ÿ™Œ๐Ÿฟ|heart_hands ๐Ÿซถ|heart_hands_light_skin_tone ๐Ÿซถ๐Ÿป|heart_hands_medium_light_skin_tone ๐Ÿซถ๐Ÿผ|heart_hands_medium_skin_tone ๐Ÿซถ๐Ÿฝ|heart_hands_medium_dark_skin_tone ๐Ÿซถ๐Ÿพ|heart_hands_dark_skin_tone ๐Ÿซถ๐Ÿฟ|open_hands ๐Ÿ‘|open_hands_light_skin_tone ๐Ÿ‘๐Ÿป|open_hands_medium_light_skin_tone ๐Ÿ‘๐Ÿผ|open_hands_medium_skin_tone ๐Ÿ‘๐Ÿฝ|open_hands_medium_dark_skin_tone ๐Ÿ‘๐Ÿพ|open_hands_dark_skin_tone ๐Ÿ‘๐Ÿฟ|palms_up_together ๐Ÿคฒ|palms_up_together_light_skin_tone ๐Ÿคฒ๐Ÿป|palms_up_together_medium_light_skin_tone ๐Ÿคฒ๐Ÿผ|palms_up_together_medium_skin_tone ๐Ÿคฒ๐Ÿฝ|palms_up_together_medium_dark_skin_tone ๐Ÿคฒ๐Ÿพ|palms_up_together_dark_skin_tone ๐Ÿคฒ๐Ÿฟ|handshake ๐Ÿค|handshake_light_skin_tone ๐Ÿค๐Ÿป|handshake_medium_light_skin_tone ๐Ÿค๐Ÿผ|handshake_medium_skin_tone ๐Ÿค๐Ÿฝ|handshake_medium_dark_skin_tone ๐Ÿค๐Ÿพ|handshake_dark_skin_tone ๐Ÿค๐Ÿฟ|handshake_light_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿผ|handshake_light_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฝ|handshake_light_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿพ|handshake_light_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฟ|handshake_medium_light_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿป|handshake_medium_light_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฝ|handshake_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿพ|handshake_medium_light_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฟ|handshake_medium_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿป|handshake_medium_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿผ|handshake_medium_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿพ|handshake_medium_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿฟ|handshake_medium_dark_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿป|handshake_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿผ|handshake_medium_dark_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฝ|handshake_medium_dark_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฟ|handshake_dark_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿป|handshake_dark_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿผ|handshake_dark_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿฝ|handshake_dark_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿพ|folded_hands ๐Ÿ™|folded_hands_light_skin_tone ๐Ÿ™๐Ÿป|folded_hands_medium_light_skin_tone ๐Ÿ™๐Ÿผ|folded_hands_medium_skin_tone ๐Ÿ™๐Ÿฝ|folded_hands_medium_dark_skin_tone ๐Ÿ™๐Ÿพ|folded_hands_dark_skin_tone ๐Ÿ™๐Ÿฟ|writing_hand โœ๏ธ|writing_hand โœ|writing_hand_light_skin_tone โœ๐Ÿป|writing_hand_medium_light_skin_tone โœ๐Ÿผ|writing_hand_medium_skin_tone โœ๐Ÿฝ|writing_hand_medium_dark_skin_tone โœ๐Ÿพ|writing_hand_dark_skin_tone โœ๐Ÿฟ|nail_polish ๐Ÿ’…|nail_polish_light_skin_tone ๐Ÿ’…๐Ÿป|nail_polish_medium_light_skin_tone ๐Ÿ’…๐Ÿผ|nail_polish_medium_skin_tone ๐Ÿ’…๐Ÿฝ|nail_polish_medium_dark_skin_tone ๐Ÿ’…๐Ÿพ|nail_polish_dark_skin_tone ๐Ÿ’…๐Ÿฟ|selfie ๐Ÿคณ|selfie_light_skin_tone ๐Ÿคณ๐Ÿป|selfie_medium_light_skin_tone ๐Ÿคณ๐Ÿผ|selfie_medium_skin_tone ๐Ÿคณ๐Ÿฝ|selfie_medium_dark_skin_tone ๐Ÿคณ๐Ÿพ|selfie_dark_skin_tone ๐Ÿคณ๐Ÿฟ|flexed_biceps ๐Ÿ’ช|flexed_biceps_light_skin_tone ๐Ÿ’ช๐Ÿป|flexed_biceps_medium_light_skin_tone ๐Ÿ’ช๐Ÿผ|flexed_biceps_medium_skin_tone ๐Ÿ’ช๐Ÿฝ|flexed_biceps_medium_dark_skin_tone ๐Ÿ’ช๐Ÿพ|flexed_biceps_dark_skin_tone ๐Ÿ’ช๐Ÿฟ|mechanical_arm ๐Ÿฆพ|mechanical_leg ๐Ÿฆฟ|leg ๐Ÿฆต|leg_light_skin_tone ๐Ÿฆต๐Ÿป|leg_medium_light_skin_tone ๐Ÿฆต๐Ÿผ|leg_medium_skin_tone ๐Ÿฆต๐Ÿฝ|leg_medium_dark_skin_tone ๐Ÿฆต๐Ÿพ|leg_dark_skin_tone ๐Ÿฆต๐Ÿฟ|foot ๐Ÿฆถ|foot_light_skin_tone ๐Ÿฆถ๐Ÿป|foot_medium_light_skin_tone ๐Ÿฆถ๐Ÿผ|foot_medium_skin_tone ๐Ÿฆถ๐Ÿฝ|foot_medium_dark_skin_tone ๐Ÿฆถ๐Ÿพ|foot_dark_skin_tone ๐Ÿฆถ๐Ÿฟ|ear ๐Ÿ‘‚|ear_light_skin_tone ๐Ÿ‘‚๐Ÿป|ear_medium_light_skin_tone ๐Ÿ‘‚๐Ÿผ|ear_medium_skin_tone ๐Ÿ‘‚๐Ÿฝ|ear_medium_dark_skin_tone ๐Ÿ‘‚๐Ÿพ|ear_dark_skin_tone ๐Ÿ‘‚๐Ÿฟ|ear_with_hearing_aid ๐Ÿฆป|ear_with_hearing_aid_light_skin_tone ๐Ÿฆป๐Ÿป|ear_with_hearing_aid_medium_light_skin_tone ๐Ÿฆป๐Ÿผ|ear_with_hearing_aid_medium_skin_tone ๐Ÿฆป๐Ÿฝ|ear_with_hearing_aid_medium_dark_skin_tone ๐Ÿฆป๐Ÿพ|ear_with_hearing_aid_dark_skin_tone ๐Ÿฆป๐Ÿฟ|nose ๐Ÿ‘ƒ|nose_light_skin_tone ๐Ÿ‘ƒ๐Ÿป|nose_medium_light_skin_tone ๐Ÿ‘ƒ๐Ÿผ|nose_medium_skin_tone ๐Ÿ‘ƒ๐Ÿฝ|nose_medium_dark_skin_tone ๐Ÿ‘ƒ๐Ÿพ|nose_dark_skin_tone ๐Ÿ‘ƒ๐Ÿฟ|brain ๐Ÿง |anatomical_heart ๐Ÿซ€|lungs ๐Ÿซ|tooth ๐Ÿฆท|bone ๐Ÿฆด|eyes ๐Ÿ‘€|eye ๐Ÿ‘๏ธ|eye ๐Ÿ‘|tongue ๐Ÿ‘…|mouth ๐Ÿ‘„|biting_lip ๐Ÿซฆ|baby ๐Ÿ‘ถ|baby_light_skin_tone ๐Ÿ‘ถ๐Ÿป|baby_medium_light_skin_tone ๐Ÿ‘ถ๐Ÿผ|baby_medium_skin_tone ๐Ÿ‘ถ๐Ÿฝ|baby_medium_dark_skin_tone ๐Ÿ‘ถ๐Ÿพ|baby_dark_skin_tone ๐Ÿ‘ถ๐Ÿฟ|child ๐Ÿง’|child_light_skin_tone ๐Ÿง’๐Ÿป|child_medium_light_skin_tone ๐Ÿง’๐Ÿผ|child_medium_skin_tone ๐Ÿง’๐Ÿฝ|child_medium_dark_skin_tone ๐Ÿง’๐Ÿพ|child_dark_skin_tone ๐Ÿง’๐Ÿฟ|boy ๐Ÿ‘ฆ|boy_light_skin_tone ๐Ÿ‘ฆ๐Ÿป|boy_medium_light_skin_tone ๐Ÿ‘ฆ๐Ÿผ|boy_medium_skin_tone ๐Ÿ‘ฆ๐Ÿฝ|boy_medium_dark_skin_tone ๐Ÿ‘ฆ๐Ÿพ|boy_dark_skin_tone ๐Ÿ‘ฆ๐Ÿฟ|girl ๐Ÿ‘ง|girl_light_skin_tone ๐Ÿ‘ง๐Ÿป|girl_medium_light_skin_tone ๐Ÿ‘ง๐Ÿผ|girl_medium_skin_tone ๐Ÿ‘ง๐Ÿฝ|girl_medium_dark_skin_tone ๐Ÿ‘ง๐Ÿพ|girl_dark_skin_tone ๐Ÿ‘ง๐Ÿฟ|person ๐Ÿง‘|person_light_skin_tone ๐Ÿง‘๐Ÿป|person_medium_light_skin_tone ๐Ÿง‘๐Ÿผ|person_medium_skin_tone ๐Ÿง‘๐Ÿฝ|person_medium_dark_skin_tone ๐Ÿง‘๐Ÿพ|person_dark_skin_tone ๐Ÿง‘๐Ÿฟ|person_blond_hair ๐Ÿ‘ฑ|person_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿป|person_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผ|person_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝ|person_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพ|person_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟ|man ๐Ÿ‘จ|man_light_skin_tone ๐Ÿ‘จ๐Ÿป|man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผ|man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝ|man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพ|man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟ|person_beard ๐Ÿง”|person_light_skin_tone_beard ๐Ÿง”๐Ÿป|person_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผ|person_medium_skin_tone_beard ๐Ÿง”๐Ÿฝ|person_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพ|person_dark_skin_tone_beard ๐Ÿง”๐Ÿฟ|man_beard ๐Ÿง”โ€โ™‚๏ธ|man_beard ๐Ÿง”โ€โ™‚|man_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™‚๏ธ|man_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™‚|man_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™‚๏ธ|man_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™‚|man_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™‚๏ธ|man_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™‚|man_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™‚๏ธ|man_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™‚|man_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™‚๏ธ|man_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™‚|woman_beard ๐Ÿง”โ€โ™€๏ธ|woman_beard ๐Ÿง”โ€โ™€|woman_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™€๏ธ|woman_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™€|woman_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™€๏ธ|woman_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™€|woman_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™€๏ธ|woman_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™€|woman_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™€๏ธ|woman_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™€|woman_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™€๏ธ|woman_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™€|man_red_hair ๐Ÿ‘จโ€๐Ÿฆฐ|man_light_skin_tone_red_hair ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฐ|man_medium_light_skin_tone_red_hair ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฐ|man_medium_skin_tone_red_hair ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฐ|man_medium_dark_skin_tone_red_hair ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฐ|man_dark_skin_tone_red_hair ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฐ|man_curly_hair ๐Ÿ‘จโ€๐Ÿฆฑ|man_light_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฑ|man_medium_light_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฑ|man_medium_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฑ|man_medium_dark_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ|man_dark_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฑ|man_white_hair ๐Ÿ‘จโ€๐Ÿฆณ|man_light_skin_tone_white_hair ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆณ|man_medium_light_skin_tone_white_hair ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆณ|man_medium_skin_tone_white_hair ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆณ|man_medium_dark_skin_tone_white_hair ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆณ|man_dark_skin_tone_white_hair ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆณ|man_bald ๐Ÿ‘จโ€๐Ÿฆฒ|man_light_skin_tone_bald ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฒ|man_medium_light_skin_tone_bald ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฒ|man_medium_skin_tone_bald ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฒ|man_medium_dark_skin_tone_bald ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฒ|man_dark_skin_tone_bald ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฒ|woman ๐Ÿ‘ฉ|woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿป|woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผ|woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝ|woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพ|woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟ|woman_red_hair ๐Ÿ‘ฉโ€๐Ÿฆฐ|woman_light_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฐ|woman_medium_light_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฐ|woman_medium_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฐ|woman_medium_dark_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฐ|woman_dark_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฐ|person_red_hair ๐Ÿง‘โ€๐Ÿฆฐ|person_light_skin_tone_red_hair ๐Ÿง‘๐Ÿปโ€๐Ÿฆฐ|person_medium_light_skin_tone_red_hair ๐Ÿง‘๐Ÿผโ€๐Ÿฆฐ|person_medium_skin_tone_red_hair ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฐ|person_medium_dark_skin_tone_red_hair ๐Ÿง‘๐Ÿพโ€๐Ÿฆฐ|person_dark_skin_tone_red_hair ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฐ|woman_curly_hair ๐Ÿ‘ฉโ€๐Ÿฆฑ|woman_light_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฑ|woman_medium_light_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฑ|woman_medium_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฑ|woman_medium_dark_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฑ|woman_dark_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฑ|person_curly_hair ๐Ÿง‘โ€๐Ÿฆฑ|person_light_skin_tone_curly_hair ๐Ÿง‘๐Ÿปโ€๐Ÿฆฑ|person_medium_light_skin_tone_curly_hair ๐Ÿง‘๐Ÿผโ€๐Ÿฆฑ|person_medium_skin_tone_curly_hair ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฑ|person_medium_dark_skin_tone_curly_hair ๐Ÿง‘๐Ÿพโ€๐Ÿฆฑ|person_dark_skin_tone_curly_hair ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฑ|woman_white_hair ๐Ÿ‘ฉโ€๐Ÿฆณ|woman_light_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆณ|woman_medium_light_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆณ|woman_medium_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆณ|woman_medium_dark_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆณ|woman_dark_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆณ|person_white_hair ๐Ÿง‘โ€๐Ÿฆณ|person_light_skin_tone_white_hair ๐Ÿง‘๐Ÿปโ€๐Ÿฆณ|person_medium_light_skin_tone_white_hair ๐Ÿง‘๐Ÿผโ€๐Ÿฆณ|person_medium_skin_tone_white_hair ๐Ÿง‘๐Ÿฝโ€๐Ÿฆณ|person_medium_dark_skin_tone_white_hair ๐Ÿง‘๐Ÿพโ€๐Ÿฆณ|person_dark_skin_tone_white_hair ๐Ÿง‘๐Ÿฟโ€๐Ÿฆณ|woman_bald ๐Ÿ‘ฉโ€๐Ÿฆฒ|woman_light_skin_tone_bald ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฒ|woman_medium_light_skin_tone_bald ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฒ|woman_medium_skin_tone_bald ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฒ|woman_medium_dark_skin_tone_bald ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฒ|woman_dark_skin_tone_bald ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฒ|person_bald ๐Ÿง‘โ€๐Ÿฆฒ|person_light_skin_tone_bald ๐Ÿง‘๐Ÿปโ€๐Ÿฆฒ|person_medium_light_skin_tone_bald ๐Ÿง‘๐Ÿผโ€๐Ÿฆฒ|person_medium_skin_tone_bald ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฒ|person_medium_dark_skin_tone_bald ๐Ÿง‘๐Ÿพโ€๐Ÿฆฒ|person_dark_skin_tone_bald ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฒ|woman_blond_hair ๐Ÿ‘ฑโ€โ™€๏ธ|woman_blond_hair ๐Ÿ‘ฑโ€โ™€|woman_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ|woman_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™€|woman_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™€๏ธ|woman_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™€|woman_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™€๏ธ|woman_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™€|woman_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™€๏ธ|woman_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™€|woman_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ|woman_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™€|man_blond_hair ๐Ÿ‘ฑโ€โ™‚๏ธ|man_blond_hair ๐Ÿ‘ฑโ€โ™‚|man_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™‚๏ธ|man_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™‚|man_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™‚๏ธ|man_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™‚|man_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™‚๏ธ|man_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™‚|man_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™‚๏ธ|man_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™‚|man_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™‚๏ธ|man_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™‚|older_person ๐Ÿง“|older_person_light_skin_tone ๐Ÿง“๐Ÿป|older_person_medium_light_skin_tone ๐Ÿง“๐Ÿผ|older_person_medium_skin_tone ๐Ÿง“๐Ÿฝ|older_person_medium_dark_skin_tone ๐Ÿง“๐Ÿพ|older_person_dark_skin_tone ๐Ÿง“๐Ÿฟ|old_man ๐Ÿ‘ด|old_man_light_skin_tone ๐Ÿ‘ด๐Ÿป|old_man_medium_light_skin_tone ๐Ÿ‘ด๐Ÿผ|old_man_medium_skin_tone ๐Ÿ‘ด๐Ÿฝ|old_man_medium_dark_skin_tone ๐Ÿ‘ด๐Ÿพ|old_man_dark_skin_tone ๐Ÿ‘ด๐Ÿฟ|old_woman ๐Ÿ‘ต|old_woman_light_skin_tone ๐Ÿ‘ต๐Ÿป|old_woman_medium_light_skin_tone ๐Ÿ‘ต๐Ÿผ|old_woman_medium_skin_tone ๐Ÿ‘ต๐Ÿฝ|old_woman_medium_dark_skin_tone ๐Ÿ‘ต๐Ÿพ|old_woman_dark_skin_tone ๐Ÿ‘ต๐Ÿฟ|person_frowning ๐Ÿ™|person_frowning_light_skin_tone ๐Ÿ™๐Ÿป|person_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผ|person_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝ|person_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพ|person_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟ|man_frowning ๐Ÿ™โ€โ™‚๏ธ|man_frowning ๐Ÿ™โ€โ™‚|man_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™‚๏ธ|man_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™‚|man_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™‚๏ธ|man_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™‚|man_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™‚๏ธ|man_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™‚|man_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™‚๏ธ|man_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™‚|man_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™‚๏ธ|man_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™‚|woman_frowning ๐Ÿ™โ€โ™€๏ธ|woman_frowning ๐Ÿ™โ€โ™€|woman_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™€๏ธ|woman_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™€|woman_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™€๏ธ|woman_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™€|woman_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™€๏ธ|woman_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™€|woman_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™€๏ธ|woman_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™€|woman_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™€๏ธ|woman_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™€|person_pouting ๐Ÿ™Ž|person_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿป|person_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผ|person_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝ|person_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพ|person_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟ|man_pouting ๐Ÿ™Žโ€โ™‚๏ธ|man_pouting ๐Ÿ™Žโ€โ™‚|man_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™‚๏ธ|man_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™‚|man_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™‚๏ธ|man_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™‚|man_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™‚๏ธ|man_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™‚|man_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™‚๏ธ|man_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™‚|man_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™‚๏ธ|man_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™‚|woman_pouting ๐Ÿ™Žโ€โ™€๏ธ|woman_pouting ๐Ÿ™Žโ€โ™€|woman_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™€๏ธ|woman_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™€|woman_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™€๏ธ|woman_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™€|woman_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™€๏ธ|woman_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™€|woman_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™€๏ธ|woman_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™€|woman_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™€๏ธ|woman_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™€|person_gesturing_no ๐Ÿ™…|person_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿป|person_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผ|person_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝ|person_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพ|person_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟ|man_gesturing_no ๐Ÿ™…โ€โ™‚๏ธ|man_gesturing_no ๐Ÿ™…โ€โ™‚|man_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™‚๏ธ|man_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™‚|man_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™‚๏ธ|man_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™‚|man_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™‚๏ธ|man_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™‚|man_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™‚๏ธ|man_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™‚|man_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™‚๏ธ|man_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™‚|woman_gesturing_no ๐Ÿ™…โ€โ™€๏ธ|woman_gesturing_no ๐Ÿ™…โ€โ™€|woman_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™€๏ธ|woman_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™€|woman_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™€๏ธ|woman_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™€|woman_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™€๏ธ|woman_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™€|woman_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™€๏ธ|woman_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™€|woman_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™€๏ธ|woman_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™€|person_gesturing_ok ๐Ÿ™†|person_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿป|person_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผ|person_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝ|person_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพ|person_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟ|man_gesturing_ok ๐Ÿ™†โ€โ™‚๏ธ|man_gesturing_ok ๐Ÿ™†โ€โ™‚|man_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™‚๏ธ|man_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™‚|man_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™‚๏ธ|man_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™‚|man_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™‚๏ธ|man_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™‚|man_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™‚๏ธ|man_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™‚|man_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™‚๏ธ|man_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™‚|woman_gesturing_ok ๐Ÿ™†โ€โ™€๏ธ|woman_gesturing_ok ๐Ÿ™†โ€โ™€|woman_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™€๏ธ|woman_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™€|woman_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™€๏ธ|woman_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™€|woman_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™€๏ธ|woman_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™€|woman_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™€๏ธ|woman_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™€|woman_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™€๏ธ|woman_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™€|person_tipping_hand ๐Ÿ’|person_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿป|person_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผ|person_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝ|person_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพ|person_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟ|man_tipping_hand ๐Ÿ’โ€โ™‚๏ธ|man_tipping_hand ๐Ÿ’โ€โ™‚|man_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™‚๏ธ|man_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™‚|man_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™‚๏ธ|man_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™‚|man_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™‚๏ธ|man_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™‚|man_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™‚๏ธ|man_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™‚|man_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™‚๏ธ|man_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™‚|woman_tipping_hand ๐Ÿ’โ€โ™€๏ธ|woman_tipping_hand ๐Ÿ’โ€โ™€|woman_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™€๏ธ|woman_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™€|woman_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™€๏ธ|woman_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™€|woman_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™€๏ธ|woman_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™€|woman_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™€๏ธ|woman_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™€|woman_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™€๏ธ|woman_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™€|person_raising_hand ๐Ÿ™‹|person_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿป|person_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผ|person_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝ|person_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพ|person_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟ|man_raising_hand ๐Ÿ™‹โ€โ™‚๏ธ|man_raising_hand ๐Ÿ™‹โ€โ™‚|man_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ|man_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™‚|man_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™‚๏ธ|man_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™‚|man_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™‚๏ธ|man_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™‚|man_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™‚๏ธ|man_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™‚|man_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™‚๏ธ|man_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™‚|woman_raising_hand ๐Ÿ™‹โ€โ™€๏ธ|woman_raising_hand ๐Ÿ™‹โ€โ™€|woman_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™€๏ธ|woman_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™€|woman_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™€๏ธ|woman_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™€|woman_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™€๏ธ|woman_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™€|woman_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™€๏ธ|woman_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™€|woman_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™€๏ธ|woman_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™€|deaf_person ๐Ÿง|deaf_person_light_skin_tone ๐Ÿง๐Ÿป|deaf_person_medium_light_skin_tone ๐Ÿง๐Ÿผ|deaf_person_medium_skin_tone ๐Ÿง๐Ÿฝ|deaf_person_medium_dark_skin_tone ๐Ÿง๐Ÿพ|deaf_person_dark_skin_tone ๐Ÿง๐Ÿฟ|deaf_man ๐Ÿงโ€โ™‚๏ธ|deaf_man ๐Ÿงโ€โ™‚|deaf_man_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚๏ธ|deaf_man_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚|deaf_man_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚๏ธ|deaf_man_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚|deaf_man_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚๏ธ|deaf_man_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚|deaf_man_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚๏ธ|deaf_man_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚|deaf_man_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚๏ธ|deaf_man_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚|deaf_woman ๐Ÿงโ€โ™€๏ธ|deaf_woman ๐Ÿงโ€โ™€|deaf_woman_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€๏ธ|deaf_woman_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€|deaf_woman_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€๏ธ|deaf_woman_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€|deaf_woman_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€๏ธ|deaf_woman_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€|deaf_woman_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€๏ธ|deaf_woman_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€|deaf_woman_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€๏ธ|deaf_woman_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€|person_bowing ๐Ÿ™‡|person_bowing_light_skin_tone ๐Ÿ™‡๐Ÿป|person_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผ|person_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝ|person_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพ|person_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟ|man_bowing ๐Ÿ™‡โ€โ™‚๏ธ|man_bowing ๐Ÿ™‡โ€โ™‚|man_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™‚๏ธ|man_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™‚|man_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™‚๏ธ|man_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™‚|man_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™‚๏ธ|man_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™‚|man_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™‚๏ธ|man_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™‚|man_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™‚๏ธ|man_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™‚|woman_bowing ๐Ÿ™‡โ€โ™€๏ธ|woman_bowing ๐Ÿ™‡โ€โ™€|woman_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™€๏ธ|woman_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™€|woman_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™€๏ธ|woman_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™€|woman_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™€๏ธ|woman_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™€|woman_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™€๏ธ|woman_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™€|woman_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™€๏ธ|woman_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™€|person_facepalming ๐Ÿคฆ|person_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿป|person_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผ|person_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝ|person_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพ|person_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟ|man_facepalming ๐Ÿคฆโ€โ™‚๏ธ|man_facepalming ๐Ÿคฆโ€โ™‚|man_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ|man_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™‚|man_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™‚๏ธ|man_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™‚|man_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™‚๏ธ|man_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™‚|man_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™‚๏ธ|man_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™‚|man_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™‚๏ธ|man_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™‚|woman_facepalming ๐Ÿคฆโ€โ™€๏ธ|woman_facepalming ๐Ÿคฆโ€โ™€|woman_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™€๏ธ|woman_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™€|woman_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™€๏ธ|woman_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™€|woman_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™€๏ธ|woman_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™€|woman_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™€๏ธ|woman_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™€|woman_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™€๏ธ|woman_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™€|person_shrugging ๐Ÿคท|person_shrugging_light_skin_tone ๐Ÿคท๐Ÿป|person_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผ|person_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝ|person_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพ|person_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟ|man_shrugging ๐Ÿคทโ€โ™‚๏ธ|man_shrugging ๐Ÿคทโ€โ™‚|man_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™‚๏ธ|man_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™‚|man_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™‚๏ธ|man_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™‚|man_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™‚๏ธ|man_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™‚|man_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™‚๏ธ|man_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™‚|man_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™‚๏ธ|man_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™‚|woman_shrugging ๐Ÿคทโ€โ™€๏ธ|woman_shrugging ๐Ÿคทโ€โ™€|woman_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™€๏ธ|woman_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™€|woman_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™€๏ธ|woman_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™€|woman_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™€๏ธ|woman_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™€|woman_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™€๏ธ|woman_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™€|woman_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™€๏ธ|woman_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™€|health_worker ๐Ÿง‘โ€โš•๏ธ|health_worker ๐Ÿง‘โ€โš•|health_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš•๏ธ|health_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš•|health_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš•๏ธ|health_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš•|health_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš•๏ธ|health_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš•|health_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš•๏ธ|health_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš•|health_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš•๏ธ|health_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš•|man_health_worker ๐Ÿ‘จโ€โš•๏ธ|man_health_worker ๐Ÿ‘จโ€โš•|man_health_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš•๏ธ|man_health_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš•|man_health_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš•๏ธ|man_health_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš•|man_health_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš•๏ธ|man_health_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš•|man_health_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš•๏ธ|man_health_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš•|man_health_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš•๏ธ|man_health_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš•|woman_health_worker ๐Ÿ‘ฉโ€โš•๏ธ|woman_health_worker ๐Ÿ‘ฉโ€โš•|woman_health_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš•๏ธ|woman_health_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš•|woman_health_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš•๏ธ|woman_health_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš•|woman_health_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš•๏ธ|woman_health_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš•|woman_health_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš•๏ธ|woman_health_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš•|woman_health_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ|woman_health_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš•|student ๐Ÿง‘โ€๐ŸŽ“|student_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽ“|student_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽ“|student_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽ“|student_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽ“|student_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽ“|man_student ๐Ÿ‘จโ€๐ŸŽ“|man_student_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŽ“|man_student_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŽ“|man_student_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽ“|man_student_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŽ“|man_student_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽ“|woman_student ๐Ÿ‘ฉโ€๐ŸŽ“|woman_student_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽ“|woman_student_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽ“|woman_student_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽ“|woman_student_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽ“|woman_student_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽ“|teacher ๐Ÿง‘โ€๐Ÿซ|teacher_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿซ|teacher_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿซ|teacher_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿซ|teacher_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿซ|teacher_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿซ|man_teacher ๐Ÿ‘จโ€๐Ÿซ|man_teacher_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿซ|man_teacher_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿซ|man_teacher_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ|man_teacher_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿซ|man_teacher_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿซ|woman_teacher ๐Ÿ‘ฉโ€๐Ÿซ|woman_teacher_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ|woman_teacher_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿซ|woman_teacher_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ|woman_teacher_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿซ|woman_teacher_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿซ|judge ๐Ÿง‘โ€โš–๏ธ|judge ๐Ÿง‘โ€โš–|judge_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš–๏ธ|judge_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš–|judge_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš–๏ธ|judge_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš–|judge_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš–๏ธ|judge_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš–|judge_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš–๏ธ|judge_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš–|judge_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš–๏ธ|judge_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš–|man_judge ๐Ÿ‘จโ€โš–๏ธ|man_judge ๐Ÿ‘จโ€โš–|man_judge_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš–๏ธ|man_judge_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš–|man_judge_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš–๏ธ|man_judge_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš–|man_judge_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš–๏ธ|man_judge_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš–|man_judge_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš–๏ธ|man_judge_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš–|man_judge_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš–๏ธ|man_judge_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš–|woman_judge ๐Ÿ‘ฉโ€โš–๏ธ|woman_judge ๐Ÿ‘ฉโ€โš–|woman_judge_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš–๏ธ|woman_judge_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš–|woman_judge_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš–๏ธ|woman_judge_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš–|woman_judge_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš–๏ธ|woman_judge_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš–|woman_judge_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš–๏ธ|woman_judge_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš–|woman_judge_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš–๏ธ|woman_judge_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš–|farmer ๐Ÿง‘โ€๐ŸŒพ|farmer_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŒพ|farmer_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŒพ|farmer_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŒพ|farmer_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŒพ|farmer_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŒพ|man_farmer ๐Ÿ‘จโ€๐ŸŒพ|man_farmer_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŒพ|man_farmer_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŒพ|man_farmer_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŒพ|man_farmer_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŒพ|man_farmer_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŒพ|woman_farmer ๐Ÿ‘ฉโ€๐ŸŒพ|woman_farmer_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŒพ|woman_farmer_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŒพ|woman_farmer_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŒพ|woman_farmer_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŒพ|woman_farmer_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŒพ|cook ๐Ÿง‘โ€๐Ÿณ|cook_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿณ|cook_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿณ|cook_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿณ|cook_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿณ|cook_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿณ|man_cook ๐Ÿ‘จโ€๐Ÿณ|man_cook_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿณ|man_cook_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿณ|man_cook_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿณ|man_cook_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿณ|man_cook_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿณ|woman_cook ๐Ÿ‘ฉโ€๐Ÿณ|woman_cook_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿณ|woman_cook_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿณ|woman_cook_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿณ|woman_cook_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿณ|woman_cook_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿณ|mechanic ๐Ÿง‘โ€๐Ÿ”ง|mechanic_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ”ง|mechanic_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ”ง|mechanic_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ”ง|mechanic_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ”ง|mechanic_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ”ง|man_mechanic ๐Ÿ‘จโ€๐Ÿ”ง|man_mechanic_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ง|man_mechanic_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ง|man_mechanic_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ง|man_mechanic_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ง|man_mechanic_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ง|woman_mechanic ๐Ÿ‘ฉโ€๐Ÿ”ง|woman_mechanic_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ง|woman_mechanic_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ง|woman_mechanic_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ง|woman_mechanic_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ง|woman_mechanic_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ง|factory_worker ๐Ÿง‘โ€๐Ÿญ|factory_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿญ|factory_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿญ|factory_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿญ|factory_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿญ|factory_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿญ|man_factory_worker ๐Ÿ‘จโ€๐Ÿญ|man_factory_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿญ|man_factory_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿญ|man_factory_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿญ|man_factory_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿญ|man_factory_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿญ|woman_factory_worker ๐Ÿ‘ฉโ€๐Ÿญ|woman_factory_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿญ|woman_factory_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿญ|woman_factory_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿญ|woman_factory_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿญ|woman_factory_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿญ|office_worker ๐Ÿง‘โ€๐Ÿ’ผ|office_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ’ผ|office_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ’ผ|office_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ’ผ|office_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ’ผ|office_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ’ผ|man_office_worker ๐Ÿ‘จโ€๐Ÿ’ผ|man_office_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ|man_office_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ผ|man_office_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ผ|man_office_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ผ|man_office_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ผ|woman_office_worker ๐Ÿ‘ฉโ€๐Ÿ’ผ|woman_office_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ผ|woman_office_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ผ|woman_office_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ผ|woman_office_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ผ|woman_office_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ผ|scientist ๐Ÿง‘โ€๐Ÿ”ฌ|scientist_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ”ฌ|scientist_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ”ฌ|scientist_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ”ฌ|scientist_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ”ฌ|scientist_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ”ฌ|man_scientist ๐Ÿ‘จโ€๐Ÿ”ฌ|man_scientist_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ฌ|man_scientist_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ฌ|man_scientist_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ฌ|man_scientist_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ฌ|man_scientist_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ฌ|woman_scientist ๐Ÿ‘ฉโ€๐Ÿ”ฌ|woman_scientist_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ฌ|woman_scientist_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ|woman_scientist_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ฌ|woman_scientist_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ฌ|woman_scientist_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ฌ|technologist ๐Ÿง‘โ€๐Ÿ’ป|technologist_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป|technologist_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ’ป|technologist_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ’ป|technologist_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ’ป|technologist_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ’ป|man_technologist ๐Ÿ‘จโ€๐Ÿ’ป|man_technologist_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป|man_technologist_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป|man_technologist_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป|man_technologist_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป|man_technologist_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ป|woman_technologist ๐Ÿ‘ฉโ€๐Ÿ’ป|woman_technologist_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป|woman_technologist_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ป|woman_technologist_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป|woman_technologist_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ป|woman_technologist_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ป|singer ๐Ÿง‘โ€๐ŸŽค|singer_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽค|singer_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽค|singer_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽค|singer_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽค|singer_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽค|man_singer ๐Ÿ‘จโ€๐ŸŽค|man_singer_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŽค|man_singer_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŽค|man_singer_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽค|man_singer_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŽค|man_singer_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽค|woman_singer ๐Ÿ‘ฉโ€๐ŸŽค|woman_singer_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽค|woman_singer_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค|woman_singer_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽค|woman_singer_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽค|woman_singer_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค|artist ๐Ÿง‘โ€๐ŸŽจ|artist_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽจ|artist_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽจ|artist_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽจ|artist_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽจ|artist_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽจ|man_artist ๐Ÿ‘จโ€๐ŸŽจ|man_artist_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŽจ|man_artist_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŽจ|man_artist_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽจ|man_artist_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŽจ|man_artist_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽจ|woman_artist ๐Ÿ‘ฉโ€๐ŸŽจ|woman_artist_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽจ|woman_artist_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽจ|woman_artist_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽจ|woman_artist_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽจ|woman_artist_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽจ|pilot ๐Ÿง‘โ€โœˆ๏ธ|pilot ๐Ÿง‘โ€โœˆ|pilot_light_skin_tone ๐Ÿง‘๐Ÿปโ€โœˆ๏ธ|pilot_light_skin_tone ๐Ÿง‘๐Ÿปโ€โœˆ|pilot_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โœˆ๏ธ|pilot_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โœˆ|pilot_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โœˆ๏ธ|pilot_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โœˆ|pilot_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โœˆ๏ธ|pilot_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โœˆ|pilot_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โœˆ๏ธ|pilot_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โœˆ|man_pilot ๐Ÿ‘จโ€โœˆ๏ธ|man_pilot ๐Ÿ‘จโ€โœˆ|man_pilot_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โœˆ๏ธ|man_pilot_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โœˆ|man_pilot_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โœˆ๏ธ|man_pilot_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โœˆ|man_pilot_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โœˆ๏ธ|man_pilot_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โœˆ|man_pilot_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โœˆ๏ธ|man_pilot_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โœˆ|man_pilot_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โœˆ๏ธ|man_pilot_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โœˆ|woman_pilot ๐Ÿ‘ฉโ€โœˆ๏ธ|woman_pilot ๐Ÿ‘ฉโ€โœˆ|woman_pilot_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โœˆ๏ธ|woman_pilot_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โœˆ|woman_pilot_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โœˆ๏ธ|woman_pilot_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โœˆ|woman_pilot_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โœˆ๏ธ|woman_pilot_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โœˆ|woman_pilot_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โœˆ๏ธ|woman_pilot_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โœˆ|woman_pilot_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โœˆ๏ธ|woman_pilot_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โœˆ|astronaut ๐Ÿง‘โ€๐Ÿš€|astronaut_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿš€|astronaut_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿš€|astronaut_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿš€|astronaut_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿš€|astronaut_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿš€|man_astronaut ๐Ÿ‘จโ€๐Ÿš€|man_astronaut_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿš€|man_astronaut_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿš€|man_astronaut_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš€|man_astronaut_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿš€|man_astronaut_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš€|woman_astronaut ๐Ÿ‘ฉโ€๐Ÿš€|woman_astronaut_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš€|woman_astronaut_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš€|woman_astronaut_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš€|woman_astronaut_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš€|woman_astronaut_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš€|firefighter ๐Ÿง‘โ€๐Ÿš’|firefighter_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿš’|firefighter_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿš’|firefighter_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿš’|firefighter_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿš’|firefighter_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿš’|man_firefighter ๐Ÿ‘จโ€๐Ÿš’|man_firefighter_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿš’|man_firefighter_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿš’|man_firefighter_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš’|man_firefighter_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿš’|man_firefighter_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš’|woman_firefighter ๐Ÿ‘ฉโ€๐Ÿš’|woman_firefighter_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš’|woman_firefighter_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš’|woman_firefighter_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’|woman_firefighter_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš’|woman_firefighter_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš’|police_officer ๐Ÿ‘ฎ|police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿป|police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผ|police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝ|police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพ|police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟ|man_police_officer ๐Ÿ‘ฎโ€โ™‚๏ธ|man_police_officer ๐Ÿ‘ฎโ€โ™‚|man_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™‚๏ธ|man_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™‚|man_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™‚๏ธ|man_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™‚|man_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™‚๏ธ|man_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™‚|man_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™‚๏ธ|man_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™‚|man_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™‚๏ธ|man_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™‚|woman_police_officer ๐Ÿ‘ฎโ€โ™€๏ธ|woman_police_officer ๐Ÿ‘ฎโ€โ™€|woman_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™€๏ธ|woman_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™€|woman_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™€๏ธ|woman_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™€|woman_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™€๏ธ|woman_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™€|woman_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™€๏ธ|woman_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™€|woman_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™€๏ธ|woman_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™€|detective ๐Ÿ•ต๏ธ|detective ๐Ÿ•ต|detective_light_skin_tone ๐Ÿ•ต๐Ÿป|detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผ|detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝ|detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพ|detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟ|man_detective ๐Ÿ•ต๏ธโ€โ™‚๏ธ|man_detective ๐Ÿ•ตโ€โ™‚๏ธ|man_detective ๐Ÿ•ต๏ธโ€โ™‚|man_detective ๐Ÿ•ตโ€โ™‚|man_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ|man_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™‚|man_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™‚๏ธ|man_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™‚|man_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™‚๏ธ|man_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™‚|man_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™‚๏ธ|man_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™‚|man_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™‚๏ธ|man_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™‚|woman_detective ๐Ÿ•ต๏ธโ€โ™€๏ธ|woman_detective ๐Ÿ•ตโ€โ™€๏ธ|woman_detective ๐Ÿ•ต๏ธโ€โ™€|woman_detective ๐Ÿ•ตโ€โ™€|woman_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™€๏ธ|woman_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™€|woman_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™€๏ธ|woman_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™€|woman_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™€๏ธ|woman_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™€|woman_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™€๏ธ|woman_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™€|woman_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™€๏ธ|woman_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™€|guard ๐Ÿ’‚|guard_light_skin_tone ๐Ÿ’‚๐Ÿป|guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผ|guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝ|guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพ|guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟ|man_guard ๐Ÿ’‚โ€โ™‚๏ธ|man_guard ๐Ÿ’‚โ€โ™‚|man_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™‚๏ธ|man_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™‚|man_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™‚๏ธ|man_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™‚|man_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™‚๏ธ|man_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™‚|man_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™‚๏ธ|man_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™‚|man_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™‚๏ธ|man_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™‚|woman_guard ๐Ÿ’‚โ€โ™€๏ธ|woman_guard ๐Ÿ’‚โ€โ™€|woman_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™€๏ธ|woman_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™€|woman_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™€๏ธ|woman_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™€|woman_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™€๏ธ|woman_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™€|woman_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™€๏ธ|woman_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™€|woman_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™€๏ธ|woman_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™€|ninja ๐Ÿฅท|ninja_light_skin_tone ๐Ÿฅท๐Ÿป|ninja_medium_light_skin_tone ๐Ÿฅท๐Ÿผ|ninja_medium_skin_tone ๐Ÿฅท๐Ÿฝ|ninja_medium_dark_skin_tone ๐Ÿฅท๐Ÿพ|ninja_dark_skin_tone ๐Ÿฅท๐Ÿฟ|construction_worker ๐Ÿ‘ท|construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿป|construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผ|construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝ|construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพ|construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟ|man_construction_worker ๐Ÿ‘ทโ€โ™‚๏ธ|man_construction_worker ๐Ÿ‘ทโ€โ™‚|man_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™‚๏ธ|man_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™‚|man_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™‚๏ธ|man_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™‚|man_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™‚๏ธ|man_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™‚|man_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™‚๏ธ|man_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™‚|man_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™‚๏ธ|man_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™‚|woman_construction_worker ๐Ÿ‘ทโ€โ™€๏ธ|woman_construction_worker ๐Ÿ‘ทโ€โ™€|woman_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™€๏ธ|woman_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™€|woman_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ|woman_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™€|woman_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™€๏ธ|woman_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™€|woman_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™€๏ธ|woman_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™€|woman_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ|woman_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™€|person_with_crown ๐Ÿซ…|person_with_crown_light_skin_tone ๐Ÿซ…๐Ÿป|person_with_crown_medium_light_skin_tone ๐Ÿซ…๐Ÿผ|person_with_crown_medium_skin_tone ๐Ÿซ…๐Ÿฝ|person_with_crown_medium_dark_skin_tone ๐Ÿซ…๐Ÿพ|person_with_crown_dark_skin_tone ๐Ÿซ…๐Ÿฟ|prince ๐Ÿคด|prince_light_skin_tone ๐Ÿคด๐Ÿป|prince_medium_light_skin_tone ๐Ÿคด๐Ÿผ|prince_medium_skin_tone ๐Ÿคด๐Ÿฝ|prince_medium_dark_skin_tone ๐Ÿคด๐Ÿพ|prince_dark_skin_tone ๐Ÿคด๐Ÿฟ|princess ๐Ÿ‘ธ|princess_light_skin_tone ๐Ÿ‘ธ๐Ÿป|princess_medium_light_skin_tone ๐Ÿ‘ธ๐Ÿผ|princess_medium_skin_tone ๐Ÿ‘ธ๐Ÿฝ|princess_medium_dark_skin_tone ๐Ÿ‘ธ๐Ÿพ|princess_dark_skin_tone ๐Ÿ‘ธ๐Ÿฟ|person_wearing_turban ๐Ÿ‘ณ|person_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿป|person_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผ|person_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝ|person_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพ|person_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟ|man_wearing_turban ๐Ÿ‘ณโ€โ™‚๏ธ|man_wearing_turban ๐Ÿ‘ณโ€โ™‚|man_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™‚๏ธ|man_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™‚|man_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™‚๏ธ|man_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™‚|man_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™‚๏ธ|man_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™‚|man_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™‚๏ธ|man_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™‚|man_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™‚๏ธ|man_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™‚|woman_wearing_turban ๐Ÿ‘ณโ€โ™€๏ธ|woman_wearing_turban ๐Ÿ‘ณโ€โ™€|woman_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™€๏ธ|woman_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™€|woman_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™€๏ธ|woman_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™€|woman_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™€๏ธ|woman_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™€|woman_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™€๏ธ|woman_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™€|woman_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™€๏ธ|woman_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™€|person_with_skullcap ๐Ÿ‘ฒ|person_with_skullcap_light_skin_tone ๐Ÿ‘ฒ๐Ÿป|person_with_skullcap_medium_light_skin_tone ๐Ÿ‘ฒ๐Ÿผ|person_with_skullcap_medium_skin_tone ๐Ÿ‘ฒ๐Ÿฝ|person_with_skullcap_medium_dark_skin_tone ๐Ÿ‘ฒ๐Ÿพ|person_with_skullcap_dark_skin_tone ๐Ÿ‘ฒ๐Ÿฟ|woman_with_headscarf ๐Ÿง•|woman_with_headscarf_light_skin_tone ๐Ÿง•๐Ÿป|woman_with_headscarf_medium_light_skin_tone ๐Ÿง•๐Ÿผ|woman_with_headscarf_medium_skin_tone ๐Ÿง•๐Ÿฝ|woman_with_headscarf_medium_dark_skin_tone ๐Ÿง•๐Ÿพ|woman_with_headscarf_dark_skin_tone ๐Ÿง•๐Ÿฟ|person_in_tuxedo ๐Ÿคต|person_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿป|person_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผ|person_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝ|person_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพ|person_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟ|man_in_tuxedo ๐Ÿคตโ€โ™‚๏ธ|man_in_tuxedo ๐Ÿคตโ€โ™‚|man_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™‚๏ธ|man_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™‚|man_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™‚๏ธ|man_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™‚|man_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™‚๏ธ|man_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™‚|man_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™‚๏ธ|man_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™‚|man_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™‚๏ธ|man_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™‚|woman_in_tuxedo ๐Ÿคตโ€โ™€๏ธ|woman_in_tuxedo ๐Ÿคตโ€โ™€|woman_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™€๏ธ|woman_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™€|woman_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™€๏ธ|woman_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™€|woman_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™€๏ธ|woman_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™€|woman_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™€๏ธ|woman_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™€|woman_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™€๏ธ|woman_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™€|person_with_veil ๐Ÿ‘ฐ|person_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿป|person_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผ|person_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝ|person_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพ|person_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟ|man_with_veil ๐Ÿ‘ฐโ€โ™‚๏ธ|man_with_veil ๐Ÿ‘ฐโ€โ™‚|man_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™‚๏ธ|man_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™‚|man_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™‚๏ธ|man_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™‚|man_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™‚๏ธ|man_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™‚|man_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™‚๏ธ|man_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™‚|man_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™‚๏ธ|man_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™‚|woman_with_veil ๐Ÿ‘ฐโ€โ™€๏ธ|woman_with_veil ๐Ÿ‘ฐโ€โ™€|woman_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™€๏ธ|woman_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™€|woman_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™€๏ธ|woman_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™€|woman_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™€๏ธ|woman_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™€|woman_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™€๏ธ|woman_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™€|woman_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™€๏ธ|woman_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™€|pregnant_woman ๐Ÿคฐ|pregnant_woman_light_skin_tone ๐Ÿคฐ๐Ÿป|pregnant_woman_medium_light_skin_tone ๐Ÿคฐ๐Ÿผ|pregnant_woman_medium_skin_tone ๐Ÿคฐ๐Ÿฝ|pregnant_woman_medium_dark_skin_tone ๐Ÿคฐ๐Ÿพ|pregnant_woman_dark_skin_tone ๐Ÿคฐ๐Ÿฟ|pregnant_man ๐Ÿซƒ|pregnant_man_light_skin_tone ๐Ÿซƒ๐Ÿป|pregnant_man_medium_light_skin_tone ๐Ÿซƒ๐Ÿผ|pregnant_man_medium_skin_tone ๐Ÿซƒ๐Ÿฝ|pregnant_man_medium_dark_skin_tone ๐Ÿซƒ๐Ÿพ|pregnant_man_dark_skin_tone ๐Ÿซƒ๐Ÿฟ|pregnant_person ๐Ÿซ„|pregnant_person_light_skin_tone ๐Ÿซ„๐Ÿป|pregnant_person_medium_light_skin_tone ๐Ÿซ„๐Ÿผ|pregnant_person_medium_skin_tone ๐Ÿซ„๐Ÿฝ|pregnant_person_medium_dark_skin_tone ๐Ÿซ„๐Ÿพ|pregnant_person_dark_skin_tone ๐Ÿซ„๐Ÿฟ|breast_feeding ๐Ÿคฑ|breast_feeding_light_skin_tone ๐Ÿคฑ๐Ÿป|breast_feeding_medium_light_skin_tone ๐Ÿคฑ๐Ÿผ|breast_feeding_medium_skin_tone ๐Ÿคฑ๐Ÿฝ|breast_feeding_medium_dark_skin_tone ๐Ÿคฑ๐Ÿพ|breast_feeding_dark_skin_tone ๐Ÿคฑ๐Ÿฟ|woman_feeding_baby ๐Ÿ‘ฉโ€๐Ÿผ|woman_feeding_baby_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿผ|woman_feeding_baby_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿผ|woman_feeding_baby_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿผ|woman_feeding_baby_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿผ|woman_feeding_baby_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿผ|man_feeding_baby ๐Ÿ‘จโ€๐Ÿผ|man_feeding_baby_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿผ|man_feeding_baby_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿผ|man_feeding_baby_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿผ|man_feeding_baby_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿผ|man_feeding_baby_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿผ|person_feeding_baby ๐Ÿง‘โ€๐Ÿผ|person_feeding_baby_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿผ|person_feeding_baby_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿผ|person_feeding_baby_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿผ|person_feeding_baby_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿผ|person_feeding_baby_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿผ|baby_angel ๐Ÿ‘ผ|baby_angel_light_skin_tone ๐Ÿ‘ผ๐Ÿป|baby_angel_medium_light_skin_tone ๐Ÿ‘ผ๐Ÿผ|baby_angel_medium_skin_tone ๐Ÿ‘ผ๐Ÿฝ|baby_angel_medium_dark_skin_tone ๐Ÿ‘ผ๐Ÿพ|baby_angel_dark_skin_tone ๐Ÿ‘ผ๐Ÿฟ|santa_claus ๐ŸŽ…|santa_claus_light_skin_tone ๐ŸŽ…๐Ÿป|santa_claus_medium_light_skin_tone ๐ŸŽ…๐Ÿผ|santa_claus_medium_skin_tone ๐ŸŽ…๐Ÿฝ|santa_claus_medium_dark_skin_tone ๐ŸŽ…๐Ÿพ|santa_claus_dark_skin_tone ๐ŸŽ…๐Ÿฟ|mrs_claus ๐Ÿคถ|mrs_claus_light_skin_tone ๐Ÿคถ๐Ÿป|mrs_claus_medium_light_skin_tone ๐Ÿคถ๐Ÿผ|mrs_claus_medium_skin_tone ๐Ÿคถ๐Ÿฝ|mrs_claus_medium_dark_skin_tone ๐Ÿคถ๐Ÿพ|mrs_claus_dark_skin_tone ๐Ÿคถ๐Ÿฟ|mx_claus ๐Ÿง‘โ€๐ŸŽ„|mx_claus_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽ„|mx_claus_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽ„|mx_claus_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽ„|mx_claus_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽ„|mx_claus_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽ„|superhero ๐Ÿฆธ|superhero_light_skin_tone ๐Ÿฆธ๐Ÿป|superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผ|superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝ|superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพ|superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟ|man_superhero ๐Ÿฆธโ€โ™‚๏ธ|man_superhero ๐Ÿฆธโ€โ™‚|man_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™‚๏ธ|man_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™‚|man_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™‚๏ธ|man_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™‚|man_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™‚๏ธ|man_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™‚|man_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™‚๏ธ|man_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™‚|man_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™‚๏ธ|man_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™‚|woman_superhero ๐Ÿฆธโ€โ™€๏ธ|woman_superhero ๐Ÿฆธโ€โ™€|woman_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™€๏ธ|woman_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™€|woman_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™€๏ธ|woman_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™€|woman_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™€๏ธ|woman_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™€|woman_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™€๏ธ|woman_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™€|woman_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™€๏ธ|woman_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™€|supervillain ๐Ÿฆน|supervillain_light_skin_tone ๐Ÿฆน๐Ÿป|supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผ|supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝ|supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพ|supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟ|man_supervillain ๐Ÿฆนโ€โ™‚๏ธ|man_supervillain ๐Ÿฆนโ€โ™‚|man_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™‚๏ธ|man_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™‚|man_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™‚๏ธ|man_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™‚|man_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™‚๏ธ|man_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™‚|man_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™‚๏ธ|man_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™‚|man_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™‚๏ธ|man_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™‚|woman_supervillain ๐Ÿฆนโ€โ™€๏ธ|woman_supervillain ๐Ÿฆนโ€โ™€|woman_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™€๏ธ|woman_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™€|woman_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™€๏ธ|woman_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™€|woman_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™€๏ธ|woman_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™€|woman_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™€๏ธ|woman_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™€|woman_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™€๏ธ|woman_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™€|mage ๐Ÿง™|mage_light_skin_tone ๐Ÿง™๐Ÿป|mage_medium_light_skin_tone ๐Ÿง™๐Ÿผ|mage_medium_skin_tone ๐Ÿง™๐Ÿฝ|mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพ|mage_dark_skin_tone ๐Ÿง™๐Ÿฟ|man_mage ๐Ÿง™โ€โ™‚๏ธ|man_mage ๐Ÿง™โ€โ™‚|man_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™‚๏ธ|man_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™‚|man_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™‚๏ธ|man_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™‚|man_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™‚๏ธ|man_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™‚|man_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™‚๏ธ|man_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™‚|man_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™‚๏ธ|man_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™‚|woman_mage ๐Ÿง™โ€โ™€๏ธ|woman_mage ๐Ÿง™โ€โ™€|woman_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™€๏ธ|woman_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™€|woman_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™€๏ธ|woman_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™€|woman_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™€๏ธ|woman_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™€|woman_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™€๏ธ|woman_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™€|woman_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™€๏ธ|woman_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™€|fairy ๐Ÿงš|fairy_light_skin_tone ๐Ÿงš๐Ÿป|fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผ|fairy_medium_skin_tone ๐Ÿงš๐Ÿฝ|fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพ|fairy_dark_skin_tone ๐Ÿงš๐Ÿฟ|man_fairy ๐Ÿงšโ€โ™‚๏ธ|man_fairy ๐Ÿงšโ€โ™‚|man_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™‚๏ธ|man_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™‚|man_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™‚๏ธ|man_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™‚|man_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™‚๏ธ|man_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™‚|man_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™‚๏ธ|man_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™‚|man_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™‚๏ธ|man_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™‚|woman_fairy ๐Ÿงšโ€โ™€๏ธ|woman_fairy ๐Ÿงšโ€โ™€|woman_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™€๏ธ|woman_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™€|woman_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™€๏ธ|woman_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™€|woman_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™€๏ธ|woman_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™€|woman_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™€๏ธ|woman_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™€|woman_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™€๏ธ|woman_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™€|vampire ๐Ÿง›|vampire_light_skin_tone ๐Ÿง›๐Ÿป|vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผ|vampire_medium_skin_tone ๐Ÿง›๐Ÿฝ|vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพ|vampire_dark_skin_tone ๐Ÿง›๐Ÿฟ|man_vampire ๐Ÿง›โ€โ™‚๏ธ|man_vampire ๐Ÿง›โ€โ™‚|man_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™‚๏ธ|man_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™‚|man_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™‚๏ธ|man_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™‚|man_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™‚๏ธ|man_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™‚|man_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™‚๏ธ|man_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™‚|man_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™‚๏ธ|man_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™‚|woman_vampire ๐Ÿง›โ€โ™€๏ธ|woman_vampire ๐Ÿง›โ€โ™€|woman_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™€๏ธ|woman_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™€|woman_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™€๏ธ|woman_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™€|woman_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™€๏ธ|woman_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™€|woman_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™€๏ธ|woman_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™€|woman_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™€๏ธ|woman_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™€|merperson ๐Ÿงœ|merperson_light_skin_tone ๐Ÿงœ๐Ÿป|merperson_medium_light_skin_tone ๐Ÿงœ๐Ÿผ|merperson_medium_skin_tone ๐Ÿงœ๐Ÿฝ|merperson_medium_dark_skin_tone ๐Ÿงœ๐Ÿพ|merperson_dark_skin_tone ๐Ÿงœ๐Ÿฟ|merman ๐Ÿงœโ€โ™‚๏ธ|merman ๐Ÿงœโ€โ™‚|merman_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™‚๏ธ|merman_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™‚|merman_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™‚๏ธ|merman_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™‚|merman_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™‚๏ธ|merman_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™‚|merman_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™‚๏ธ|merman_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™‚|merman_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™‚๏ธ|merman_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™‚|mermaid ๐Ÿงœโ€โ™€๏ธ|mermaid ๐Ÿงœโ€โ™€|mermaid_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™€๏ธ|mermaid_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™€|mermaid_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™€๏ธ|mermaid_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™€|mermaid_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™€๏ธ|mermaid_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™€|mermaid_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™€๏ธ|mermaid_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™€|mermaid_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™€๏ธ|mermaid_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™€|elf ๐Ÿง|elf_light_skin_tone ๐Ÿง๐Ÿป|elf_medium_light_skin_tone ๐Ÿง๐Ÿผ|elf_medium_skin_tone ๐Ÿง๐Ÿฝ|elf_medium_dark_skin_tone ๐Ÿง๐Ÿพ|elf_dark_skin_tone ๐Ÿง๐Ÿฟ|man_elf ๐Ÿงโ€โ™‚๏ธ|man_elf ๐Ÿงโ€โ™‚|man_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚๏ธ|man_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚|man_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚๏ธ|man_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚|man_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚๏ธ|man_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚|man_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚๏ธ|man_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚|man_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚๏ธ|man_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚|woman_elf ๐Ÿงโ€โ™€๏ธ|woman_elf ๐Ÿงโ€โ™€|woman_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€๏ธ|woman_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€|woman_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€๏ธ|woman_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€|woman_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€๏ธ|woman_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€|woman_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€๏ธ|woman_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€|woman_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€๏ธ|woman_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€|genie ๐Ÿงž|man_genie ๐Ÿงžโ€โ™‚๏ธ|man_genie ๐Ÿงžโ€โ™‚|woman_genie ๐Ÿงžโ€โ™€๏ธ|woman_genie ๐Ÿงžโ€โ™€|zombie ๐ŸงŸ|man_zombie ๐ŸงŸโ€โ™‚๏ธ|man_zombie ๐ŸงŸโ€โ™‚|woman_zombie ๐ŸงŸโ€โ™€๏ธ|woman_zombie ๐ŸงŸโ€โ™€|troll ๐ŸงŒ|person_getting_massage ๐Ÿ’†|person_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿป|person_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผ|person_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝ|person_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพ|person_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟ|man_getting_massage ๐Ÿ’†โ€โ™‚๏ธ|man_getting_massage ๐Ÿ’†โ€โ™‚|man_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™‚๏ธ|man_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™‚|man_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™‚๏ธ|man_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™‚|man_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™‚๏ธ|man_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™‚|man_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™‚๏ธ|man_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™‚|man_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ|man_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™‚|woman_getting_massage ๐Ÿ’†โ€โ™€๏ธ|woman_getting_massage ๐Ÿ’†โ€โ™€|woman_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™€๏ธ|woman_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™€|woman_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™€๏ธ|woman_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™€|woman_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™€๏ธ|woman_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™€|woman_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™€๏ธ|woman_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™€|woman_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™€๏ธ|woman_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™€|person_getting_haircut ๐Ÿ’‡|person_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿป|person_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผ|person_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝ|person_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพ|person_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟ|man_getting_haircut ๐Ÿ’‡โ€โ™‚๏ธ|man_getting_haircut ๐Ÿ’‡โ€โ™‚|man_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™‚๏ธ|man_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™‚|man_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™‚๏ธ|man_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™‚|man_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™‚๏ธ|man_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™‚|man_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™‚๏ธ|man_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™‚|man_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™‚๏ธ|man_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™‚|woman_getting_haircut ๐Ÿ’‡โ€โ™€๏ธ|woman_getting_haircut ๐Ÿ’‡โ€โ™€|woman_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™€๏ธ|woman_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™€|woman_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™€๏ธ|woman_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™€|woman_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™€๏ธ|woman_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™€|woman_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™€๏ธ|woman_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™€|woman_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™€๏ธ|woman_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™€|person_walking ๐Ÿšถ|person_walking_light_skin_tone ๐Ÿšถ๐Ÿป|person_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผ|person_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝ|person_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพ|person_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟ|man_walking ๐Ÿšถโ€โ™‚๏ธ|man_walking ๐Ÿšถโ€โ™‚|man_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚๏ธ|man_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚|man_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚๏ธ|man_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚|man_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚๏ธ|man_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚|man_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚๏ธ|man_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚|man_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚๏ธ|man_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚|woman_walking ๐Ÿšถโ€โ™€๏ธ|woman_walking ๐Ÿšถโ€โ™€|woman_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€๏ธ|woman_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€|woman_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€๏ธ|woman_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€|woman_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€๏ธ|woman_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€|woman_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€๏ธ|woman_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€|woman_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€๏ธ|woman_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€|person_walking_facing_right ๐Ÿšถโ€โžก๏ธ|person_walking_facing_right ๐Ÿšถโ€โžก|person_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โžก๏ธ|person_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โžก|person_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โžก๏ธ|person_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โžก|person_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โžก๏ธ|person_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โžก|person_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โžก๏ธ|person_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โžก|person_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โžก๏ธ|person_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โžก|woman_walking_facing_right ๐Ÿšถโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right ๐Ÿšถโ€โ™€โ€โžก๏ธ|woman_walking_facing_right ๐Ÿšถโ€โ™€๏ธโ€โžก|woman_walking_facing_right ๐Ÿšถโ€โ™€โ€โžก|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€โ€โžก|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€โ€โžก|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€โ€โžก|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€โ€โžก|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€โ€โžก|man_walking_facing_right ๐Ÿšถโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right ๐Ÿšถโ€โ™‚โ€โžก๏ธ|man_walking_facing_right ๐Ÿšถโ€โ™‚๏ธโ€โžก|man_walking_facing_right ๐Ÿšถโ€โ™‚โ€โžก|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚โ€โžก|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚โ€โžก|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚โ€โžก|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚โ€โžก|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚โ€โžก|person_standing ๐Ÿง|person_standing_light_skin_tone ๐Ÿง๐Ÿป|person_standing_medium_light_skin_tone ๐Ÿง๐Ÿผ|person_standing_medium_skin_tone ๐Ÿง๐Ÿฝ|person_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพ|person_standing_dark_skin_tone ๐Ÿง๐Ÿฟ|man_standing ๐Ÿงโ€โ™‚๏ธ|man_standing ๐Ÿงโ€โ™‚|man_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚๏ธ|man_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚|man_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚๏ธ|man_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚|man_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚๏ธ|man_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚|man_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚๏ธ|man_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚|man_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚๏ธ|man_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚|woman_standing ๐Ÿงโ€โ™€๏ธ|woman_standing ๐Ÿงโ€โ™€|woman_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€๏ธ|woman_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€|woman_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€๏ธ|woman_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€|woman_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€๏ธ|woman_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€|woman_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€๏ธ|woman_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€|woman_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€๏ธ|woman_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€|person_kneeling ๐ŸงŽ|person_kneeling_light_skin_tone ๐ŸงŽ๐Ÿป|person_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผ|person_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝ|person_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพ|person_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟ|man_kneeling ๐ŸงŽโ€โ™‚๏ธ|man_kneeling ๐ŸงŽโ€โ™‚|man_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚๏ธ|man_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚|man_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚๏ธ|man_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚|man_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚๏ธ|man_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚|man_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚๏ธ|man_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚|man_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚๏ธ|man_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚|woman_kneeling ๐ŸงŽโ€โ™€๏ธ|woman_kneeling ๐ŸงŽโ€โ™€|woman_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€๏ธ|woman_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€|woman_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€๏ธ|woman_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€|woman_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€๏ธ|woman_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€|woman_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€๏ธ|woman_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€|woman_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€๏ธ|woman_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€|person_kneeling_facing_right ๐ŸงŽโ€โžก๏ธ|person_kneeling_facing_right ๐ŸงŽโ€โžก|person_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โžก๏ธ|person_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โžก|person_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โžก๏ธ|person_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โžก|person_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โžก๏ธ|person_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โžก|person_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โžก๏ธ|person_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โžก|person_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โžก๏ธ|person_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โžก|woman_kneeling_facing_right ๐ŸงŽโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right ๐ŸงŽโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right ๐ŸงŽโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right ๐ŸงŽโ€โ™€โ€โžก|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€โ€โžก|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€โ€โžก|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€โ€โžก|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€โ€โžก|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€โ€โžก|man_kneeling_facing_right ๐ŸงŽโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right ๐ŸงŽโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right ๐ŸงŽโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right ๐ŸงŽโ€โ™‚โ€โžก|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚โ€โžก|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚โ€โžก|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚โ€โžก|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚โ€โžก|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚โ€โžก|person_with_white_cane ๐Ÿง‘โ€๐Ÿฆฏ|person_with_white_cane_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฏ|person_with_white_cane_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฏ|person_with_white_cane_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏ|person_with_white_cane_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฏ|person_with_white_cane_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏ|person_with_white_cane_facing_right ๐Ÿง‘โ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right ๐Ÿง‘โ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก|man_with_white_cane ๐Ÿ‘จโ€๐Ÿฆฏ|man_with_white_cane_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏ|man_with_white_cane_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏ|man_with_white_cane_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏ|man_with_white_cane_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏ|man_with_white_cane_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏ|man_with_white_cane_facing_right ๐Ÿ‘จโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right ๐Ÿ‘จโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก|woman_with_white_cane ๐Ÿ‘ฉโ€๐Ÿฆฏ|woman_with_white_cane_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏ|woman_with_white_cane_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏ|woman_with_white_cane_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏ|woman_with_white_cane_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏ|woman_with_white_cane_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏ|woman_with_white_cane_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก|person_in_motorized_wheelchair ๐Ÿง‘โ€๐Ÿฆผ|person_in_motorized_wheelchair_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆผ|person_in_motorized_wheelchair_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆผ|person_in_motorized_wheelchair_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆผ|person_in_motorized_wheelchair_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆผ|person_in_motorized_wheelchair_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆผ|person_in_motorized_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair ๐Ÿ‘จโ€๐Ÿฆผ|man_in_motorized_wheelchair_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผ|man_in_motorized_wheelchair_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผ|man_in_motorized_wheelchair_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผ|man_in_motorized_wheelchair_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผ|man_in_motorized_wheelchair_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผ|man_in_motorized_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair ๐Ÿ‘ฉโ€๐Ÿฆผ|woman_in_motorized_wheelchair_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผ|woman_in_motorized_wheelchair_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผ|woman_in_motorized_wheelchair_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผ|woman_in_motorized_wheelchair_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผ|woman_in_motorized_wheelchair_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผ|woman_in_motorized_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก|person_in_manual_wheelchair ๐Ÿง‘โ€๐Ÿฆฝ|person_in_manual_wheelchair_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฝ|person_in_manual_wheelchair_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฝ|person_in_manual_wheelchair_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝ|person_in_manual_wheelchair_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฝ|person_in_manual_wheelchair_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝ|person_in_manual_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair ๐Ÿ‘จโ€๐Ÿฆฝ|man_in_manual_wheelchair_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝ|man_in_manual_wheelchair_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝ|man_in_manual_wheelchair_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝ|man_in_manual_wheelchair_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝ|man_in_manual_wheelchair_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝ|man_in_manual_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair ๐Ÿ‘ฉโ€๐Ÿฆฝ|woman_in_manual_wheelchair_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝ|woman_in_manual_wheelchair_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝ|woman_in_manual_wheelchair_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝ|woman_in_manual_wheelchair_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝ|woman_in_manual_wheelchair_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝ|woman_in_manual_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก|person_running ๐Ÿƒ|person_running_light_skin_tone ๐Ÿƒ๐Ÿป|person_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผ|person_running_medium_skin_tone ๐Ÿƒ๐Ÿฝ|person_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพ|person_running_dark_skin_tone ๐Ÿƒ๐Ÿฟ|man_running ๐Ÿƒโ€โ™‚๏ธ|man_running ๐Ÿƒโ€โ™‚|man_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚๏ธ|man_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚|man_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚๏ธ|man_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚|man_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚๏ธ|man_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚|man_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚๏ธ|man_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚|man_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚๏ธ|man_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚|woman_running ๐Ÿƒโ€โ™€๏ธ|woman_running ๐Ÿƒโ€โ™€|woman_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€๏ธ|woman_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€|woman_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€๏ธ|woman_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€|woman_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€๏ธ|woman_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€|woman_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€๏ธ|woman_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€|woman_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€๏ธ|woman_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€|person_running_facing_right ๐Ÿƒโ€โžก๏ธ|person_running_facing_right ๐Ÿƒโ€โžก|person_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โžก๏ธ|person_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โžก|person_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โžก๏ธ|person_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โžก|person_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โžก๏ธ|person_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โžก|person_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โžก๏ธ|person_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โžก|person_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โžก๏ธ|person_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โžก|woman_running_facing_right ๐Ÿƒโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right ๐Ÿƒโ€โ™€โ€โžก๏ธ|woman_running_facing_right ๐Ÿƒโ€โ™€๏ธโ€โžก|woman_running_facing_right ๐Ÿƒโ€โ™€โ€โžก|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€โ€โžก๏ธ|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€โ€โžก|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€โ€โžก๏ธ|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€โ€โžก|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€โ€โžก๏ธ|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€โ€โžก|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€โ€โžก๏ธ|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€โ€โžก|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€โ€โžก๏ธ|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€โ€โžก|man_running_facing_right ๐Ÿƒโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right ๐Ÿƒโ€โ™‚โ€โžก๏ธ|man_running_facing_right ๐Ÿƒโ€โ™‚๏ธโ€โžก|man_running_facing_right ๐Ÿƒโ€โ™‚โ€โžก|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚โ€โžก๏ธ|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚โ€โžก|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚โ€โžก๏ธ|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚โ€โžก|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚โ€โžก๏ธ|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚โ€โžก|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚โ€โžก๏ธ|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚โ€โžก|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚โ€โžก๏ธ|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚โ€โžก|woman_dancing ๐Ÿ’ƒ|woman_dancing_light_skin_tone ๐Ÿ’ƒ๐Ÿป|woman_dancing_medium_light_skin_tone ๐Ÿ’ƒ๐Ÿผ|woman_dancing_medium_skin_tone ๐Ÿ’ƒ๐Ÿฝ|woman_dancing_medium_dark_skin_tone ๐Ÿ’ƒ๐Ÿพ|woman_dancing_dark_skin_tone ๐Ÿ’ƒ๐Ÿฟ|man_dancing ๐Ÿ•บ|man_dancing_light_skin_tone ๐Ÿ•บ๐Ÿป|man_dancing_medium_light_skin_tone ๐Ÿ•บ๐Ÿผ|man_dancing_medium_skin_tone ๐Ÿ•บ๐Ÿฝ|man_dancing_medium_dark_skin_tone ๐Ÿ•บ๐Ÿพ|man_dancing_dark_skin_tone ๐Ÿ•บ๐Ÿฟ|person_in_suit_levitating ๐Ÿ•ด๏ธ|person_in_suit_levitating ๐Ÿ•ด|person_in_suit_levitating_light_skin_tone ๐Ÿ•ด๐Ÿป|person_in_suit_levitating_medium_light_skin_tone ๐Ÿ•ด๐Ÿผ|person_in_suit_levitating_medium_skin_tone ๐Ÿ•ด๐Ÿฝ|person_in_suit_levitating_medium_dark_skin_tone ๐Ÿ•ด๐Ÿพ|person_in_suit_levitating_dark_skin_tone ๐Ÿ•ด๐Ÿฟ|people_with_bunny_ears ๐Ÿ‘ฏ|men_with_bunny_ears ๐Ÿ‘ฏโ€โ™‚๏ธ|men_with_bunny_ears ๐Ÿ‘ฏโ€โ™‚|women_with_bunny_ears ๐Ÿ‘ฏโ€โ™€๏ธ|women_with_bunny_ears ๐Ÿ‘ฏโ€โ™€|person_in_steamy_room ๐Ÿง–|person_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿป|person_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผ|person_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝ|person_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพ|person_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟ|man_in_steamy_room ๐Ÿง–โ€โ™‚๏ธ|man_in_steamy_room ๐Ÿง–โ€โ™‚|man_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™‚๏ธ|man_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™‚|man_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™‚๏ธ|man_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™‚|man_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™‚๏ธ|man_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™‚|man_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™‚๏ธ|man_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™‚|man_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™‚๏ธ|man_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™‚|woman_in_steamy_room ๐Ÿง–โ€โ™€๏ธ|woman_in_steamy_room ๐Ÿง–โ€โ™€|woman_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™€๏ธ|woman_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™€|woman_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™€๏ธ|woman_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™€|woman_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™€๏ธ|woman_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™€|woman_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™€๏ธ|woman_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™€|woman_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™€๏ธ|woman_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™€|person_climbing ๐Ÿง—|person_climbing_light_skin_tone ๐Ÿง—๐Ÿป|person_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผ|person_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝ|person_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพ|person_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟ|man_climbing ๐Ÿง—โ€โ™‚๏ธ|man_climbing ๐Ÿง—โ€โ™‚|man_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™‚๏ธ|man_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™‚|man_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™‚๏ธ|man_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™‚|man_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™‚๏ธ|man_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™‚|man_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™‚๏ธ|man_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™‚|man_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™‚๏ธ|man_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™‚|woman_climbing ๐Ÿง—โ€โ™€๏ธ|woman_climbing ๐Ÿง—โ€โ™€|woman_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™€๏ธ|woman_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™€|woman_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™€๏ธ|woman_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™€|woman_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™€๏ธ|woman_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™€|woman_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™€๏ธ|woman_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™€|woman_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™€๏ธ|woman_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™€|person_fencing ๐Ÿคบ|horse_racing ๐Ÿ‡|horse_racing_light_skin_tone ๐Ÿ‡๐Ÿป|horse_racing_medium_light_skin_tone ๐Ÿ‡๐Ÿผ|horse_racing_medium_skin_tone ๐Ÿ‡๐Ÿฝ|horse_racing_medium_dark_skin_tone ๐Ÿ‡๐Ÿพ|horse_racing_dark_skin_tone ๐Ÿ‡๐Ÿฟ|skier โ›ท๏ธ|skier โ›ท|snowboarder ๐Ÿ‚|snowboarder_light_skin_tone ๐Ÿ‚๐Ÿป|snowboarder_medium_light_skin_tone ๐Ÿ‚๐Ÿผ|snowboarder_medium_skin_tone ๐Ÿ‚๐Ÿฝ|snowboarder_medium_dark_skin_tone ๐Ÿ‚๐Ÿพ|snowboarder_dark_skin_tone ๐Ÿ‚๐Ÿฟ|person_golfing ๐ŸŒ๏ธ|person_golfing ๐ŸŒ|person_golfing_light_skin_tone ๐ŸŒ๐Ÿป|person_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผ|person_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝ|person_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพ|person_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟ|man_golfing ๐ŸŒ๏ธโ€โ™‚๏ธ|man_golfing ๐ŸŒโ€โ™‚๏ธ|man_golfing ๐ŸŒ๏ธโ€โ™‚|man_golfing ๐ŸŒโ€โ™‚|man_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™‚๏ธ|man_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™‚|man_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™‚๏ธ|man_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™‚|man_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™‚๏ธ|man_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™‚|man_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™‚๏ธ|man_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™‚|man_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™‚๏ธ|man_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™‚|woman_golfing ๐ŸŒ๏ธโ€โ™€๏ธ|woman_golfing ๐ŸŒโ€โ™€๏ธ|woman_golfing ๐ŸŒ๏ธโ€โ™€|woman_golfing ๐ŸŒโ€โ™€|woman_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™€๏ธ|woman_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™€|woman_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™€๏ธ|woman_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™€|woman_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™€๏ธ|woman_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™€|woman_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™€๏ธ|woman_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™€|woman_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™€๏ธ|woman_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™€|person_surfing ๐Ÿ„|person_surfing_light_skin_tone ๐Ÿ„๐Ÿป|person_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผ|person_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝ|person_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพ|person_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟ|man_surfing ๐Ÿ„โ€โ™‚๏ธ|man_surfing ๐Ÿ„โ€โ™‚|man_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™‚๏ธ|man_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™‚|man_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™‚๏ธ|man_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™‚|man_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™‚๏ธ|man_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™‚|man_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™‚๏ธ|man_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™‚|man_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™‚๏ธ|man_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™‚|woman_surfing ๐Ÿ„โ€โ™€๏ธ|woman_surfing ๐Ÿ„โ€โ™€|woman_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™€๏ธ|woman_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™€|woman_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™€๏ธ|woman_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™€|woman_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™€๏ธ|woman_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™€|woman_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™€๏ธ|woman_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™€|woman_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™€๏ธ|woman_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™€|person_rowing_boat ๐Ÿšฃ|person_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿป|person_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผ|person_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝ|person_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพ|person_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟ|man_rowing_boat ๐Ÿšฃโ€โ™‚๏ธ|man_rowing_boat ๐Ÿšฃโ€โ™‚|man_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™‚๏ธ|man_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™‚|man_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™‚๏ธ|man_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™‚|man_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™‚๏ธ|man_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™‚|man_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™‚๏ธ|man_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™‚|man_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™‚๏ธ|man_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™‚|woman_rowing_boat ๐Ÿšฃโ€โ™€๏ธ|woman_rowing_boat ๐Ÿšฃโ€โ™€|woman_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™€๏ธ|woman_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™€|woman_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™€๏ธ|woman_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™€|woman_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™€๏ธ|woman_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™€|woman_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™€๏ธ|woman_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™€|woman_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™€๏ธ|woman_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™€|person_swimming ๐ŸŠ|person_swimming_light_skin_tone ๐ŸŠ๐Ÿป|person_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผ|person_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝ|person_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพ|person_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟ|man_swimming ๐ŸŠโ€โ™‚๏ธ|man_swimming ๐ŸŠโ€โ™‚|man_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™‚๏ธ|man_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™‚|man_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™‚๏ธ|man_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™‚|man_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™‚๏ธ|man_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™‚|man_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™‚๏ธ|man_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™‚|man_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™‚๏ธ|man_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™‚|woman_swimming ๐ŸŠโ€โ™€๏ธ|woman_swimming ๐ŸŠโ€โ™€|woman_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™€๏ธ|woman_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™€|woman_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™€๏ธ|woman_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™€|woman_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™€๏ธ|woman_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™€|woman_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™€๏ธ|woman_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™€|woman_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™€๏ธ|woman_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™€|person_bouncing_ball โ›น๏ธ|person_bouncing_ball โ›น|person_bouncing_ball_light_skin_tone โ›น๐Ÿป|person_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผ|person_bouncing_ball_medium_skin_tone โ›น๐Ÿฝ|person_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพ|person_bouncing_ball_dark_skin_tone โ›น๐Ÿฟ|man_bouncing_ball โ›น๏ธโ€โ™‚๏ธ|man_bouncing_ball โ›นโ€โ™‚๏ธ|man_bouncing_ball โ›น๏ธโ€โ™‚|man_bouncing_ball โ›นโ€โ™‚|man_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™‚๏ธ|man_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™‚|man_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™‚๏ธ|man_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™‚|man_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™‚๏ธ|man_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™‚|man_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™‚๏ธ|man_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™‚|man_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™‚๏ธ|man_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™‚|woman_bouncing_ball โ›น๏ธโ€โ™€๏ธ|woman_bouncing_ball โ›นโ€โ™€๏ธ|woman_bouncing_ball โ›น๏ธโ€โ™€|woman_bouncing_ball โ›นโ€โ™€|woman_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™€๏ธ|woman_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™€|woman_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™€๏ธ|woman_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™€|woman_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™€๏ธ|woman_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™€|woman_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™€๏ธ|woman_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™€|woman_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™€๏ธ|woman_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™€|person_lifting_weights ๐Ÿ‹๏ธ|person_lifting_weights ๐Ÿ‹|person_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿป|person_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผ|person_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝ|person_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพ|person_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟ|man_lifting_weights ๐Ÿ‹๏ธโ€โ™‚๏ธ|man_lifting_weights ๐Ÿ‹โ€โ™‚๏ธ|man_lifting_weights ๐Ÿ‹๏ธโ€โ™‚|man_lifting_weights ๐Ÿ‹โ€โ™‚|man_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™‚๏ธ|man_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™‚|man_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™‚๏ธ|man_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™‚|man_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™‚๏ธ|man_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™‚|man_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™‚๏ธ|man_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™‚|man_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™‚๏ธ|man_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™‚|woman_lifting_weights ๐Ÿ‹๏ธโ€โ™€๏ธ|woman_lifting_weights ๐Ÿ‹โ€โ™€๏ธ|woman_lifting_weights ๐Ÿ‹๏ธโ€โ™€|woman_lifting_weights ๐Ÿ‹โ€โ™€|woman_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™€๏ธ|woman_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™€|woman_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™€๏ธ|woman_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™€|woman_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™€๏ธ|woman_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™€|woman_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™€๏ธ|woman_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™€|woman_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™€๏ธ|woman_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™€|person_biking ๐Ÿšด|person_biking_light_skin_tone ๐Ÿšด๐Ÿป|person_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผ|person_biking_medium_skin_tone ๐Ÿšด๐Ÿฝ|person_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพ|person_biking_dark_skin_tone ๐Ÿšด๐Ÿฟ|man_biking ๐Ÿšดโ€โ™‚๏ธ|man_biking ๐Ÿšดโ€โ™‚|man_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™‚๏ธ|man_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™‚|man_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™‚๏ธ|man_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™‚|man_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™‚๏ธ|man_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™‚|man_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™‚๏ธ|man_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™‚|man_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™‚๏ธ|man_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™‚|woman_biking ๐Ÿšดโ€โ™€๏ธ|woman_biking ๐Ÿšดโ€โ™€|woman_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™€๏ธ|woman_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™€|woman_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™€๏ธ|woman_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™€|woman_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™€๏ธ|woman_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™€|woman_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™€๏ธ|woman_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™€|woman_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™€๏ธ|woman_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™€|person_mountain_biking ๐Ÿšต|person_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿป|person_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผ|person_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝ|person_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพ|person_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟ|man_mountain_biking ๐Ÿšตโ€โ™‚๏ธ|man_mountain_biking ๐Ÿšตโ€โ™‚|man_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™‚๏ธ|man_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™‚|man_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™‚๏ธ|man_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™‚|man_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™‚๏ธ|man_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™‚|man_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™‚๏ธ|man_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™‚|man_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™‚๏ธ|man_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™‚|woman_mountain_biking ๐Ÿšตโ€โ™€๏ธ|woman_mountain_biking ๐Ÿšตโ€โ™€|woman_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™€๏ธ|woman_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™€|woman_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™€๏ธ|woman_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™€|woman_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™€๏ธ|woman_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™€|woman_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™€๏ธ|woman_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™€|woman_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™€๏ธ|woman_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™€|person_cartwheeling ๐Ÿคธ|person_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿป|person_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผ|person_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝ|person_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพ|person_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟ|man_cartwheeling ๐Ÿคธโ€โ™‚๏ธ|man_cartwheeling ๐Ÿคธโ€โ™‚|man_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™‚๏ธ|man_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™‚|man_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™‚๏ธ|man_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™‚|man_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™‚๏ธ|man_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™‚|man_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™‚๏ธ|man_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™‚|man_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™‚๏ธ|man_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™‚|woman_cartwheeling ๐Ÿคธโ€โ™€๏ธ|woman_cartwheeling ๐Ÿคธโ€โ™€|woman_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™€๏ธ|woman_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™€|woman_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™€๏ธ|woman_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™€|woman_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™€๏ธ|woman_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™€|woman_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™€๏ธ|woman_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™€|woman_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™€๏ธ|woman_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™€|people_wrestling ๐Ÿคผ|men_wrestling ๐Ÿคผโ€โ™‚๏ธ|men_wrestling ๐Ÿคผโ€โ™‚|women_wrestling ๐Ÿคผโ€โ™€๏ธ|women_wrestling ๐Ÿคผโ€โ™€|person_playing_water_polo ๐Ÿคฝ|person_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿป|person_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผ|person_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝ|person_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพ|person_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟ|man_playing_water_polo ๐Ÿคฝโ€โ™‚๏ธ|man_playing_water_polo ๐Ÿคฝโ€โ™‚|man_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™‚๏ธ|man_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™‚|man_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™‚๏ธ|man_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™‚|man_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™‚๏ธ|man_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™‚|man_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™‚๏ธ|man_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™‚|man_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™‚๏ธ|man_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™‚|woman_playing_water_polo ๐Ÿคฝโ€โ™€๏ธ|woman_playing_water_polo ๐Ÿคฝโ€โ™€|woman_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™€๏ธ|woman_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™€|woman_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™€๏ธ|woman_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™€|woman_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™€๏ธ|woman_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™€|woman_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™€๏ธ|woman_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™€|woman_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™€๏ธ|woman_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™€|person_playing_handball ๐Ÿคพ|person_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿป|person_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผ|person_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝ|person_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพ|person_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟ|man_playing_handball ๐Ÿคพโ€โ™‚๏ธ|man_playing_handball ๐Ÿคพโ€โ™‚|man_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™‚๏ธ|man_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™‚|man_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™‚๏ธ|man_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™‚|man_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™‚๏ธ|man_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™‚|man_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™‚๏ธ|man_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™‚|man_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™‚๏ธ|man_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™‚|woman_playing_handball ๐Ÿคพโ€โ™€๏ธ|woman_playing_handball ๐Ÿคพโ€โ™€|woman_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™€๏ธ|woman_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™€|woman_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™€๏ธ|woman_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™€|woman_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™€๏ธ|woman_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™€|woman_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™€๏ธ|woman_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™€|woman_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™€๏ธ|woman_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™€|person_juggling ๐Ÿคน|person_juggling_light_skin_tone ๐Ÿคน๐Ÿป|person_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผ|person_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝ|person_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพ|person_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟ|man_juggling ๐Ÿคนโ€โ™‚๏ธ|man_juggling ๐Ÿคนโ€โ™‚|man_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™‚๏ธ|man_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™‚|man_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™‚๏ธ|man_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™‚|man_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™‚๏ธ|man_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™‚|man_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™‚๏ธ|man_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™‚|man_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™‚๏ธ|man_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™‚|woman_juggling ๐Ÿคนโ€โ™€๏ธ|woman_juggling ๐Ÿคนโ€โ™€|woman_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™€๏ธ|woman_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™€|woman_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™€๏ธ|woman_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™€|woman_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™€๏ธ|woman_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™€|woman_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™€๏ธ|woman_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™€|woman_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™€๏ธ|woman_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™€|person_in_lotus_position ๐Ÿง˜|person_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿป|person_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผ|person_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝ|person_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพ|person_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟ|man_in_lotus_position ๐Ÿง˜โ€โ™‚๏ธ|man_in_lotus_position ๐Ÿง˜โ€โ™‚|man_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™‚๏ธ|man_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™‚|man_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™‚๏ธ|man_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™‚|man_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™‚๏ธ|man_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™‚|man_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™‚๏ธ|man_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™‚|man_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™‚๏ธ|man_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™‚|woman_in_lotus_position ๐Ÿง˜โ€โ™€๏ธ|woman_in_lotus_position ๐Ÿง˜โ€โ™€|woman_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™€๏ธ|woman_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™€|woman_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™€๏ธ|woman_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™€|woman_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™€๏ธ|woman_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™€|woman_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™€๏ธ|woman_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™€|woman_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™€๏ธ|woman_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™€|person_taking_bath ๐Ÿ›€|person_taking_bath_light_skin_tone ๐Ÿ›€๐Ÿป|person_taking_bath_medium_light_skin_tone ๐Ÿ›€๐Ÿผ|person_taking_bath_medium_skin_tone ๐Ÿ›€๐Ÿฝ|person_taking_bath_medium_dark_skin_tone ๐Ÿ›€๐Ÿพ|person_taking_bath_dark_skin_tone ๐Ÿ›€๐Ÿฟ|person_in_bed ๐Ÿ›Œ|person_in_bed_light_skin_tone ๐Ÿ›Œ๐Ÿป|person_in_bed_medium_light_skin_tone ๐Ÿ›Œ๐Ÿผ|person_in_bed_medium_skin_tone ๐Ÿ›Œ๐Ÿฝ|person_in_bed_medium_dark_skin_tone ๐Ÿ›Œ๐Ÿพ|person_in_bed_dark_skin_tone ๐Ÿ›Œ๐Ÿฟ|people_holding_hands ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘|people_holding_hands_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|women_holding_hands ๐Ÿ‘ญ|women_holding_hands_light_skin_tone ๐Ÿ‘ญ๐Ÿป|women_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_medium_light_skin_tone ๐Ÿ‘ญ๐Ÿผ|women_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_medium_skin_tone ๐Ÿ‘ญ๐Ÿฝ|women_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_medium_dark_skin_tone ๐Ÿ‘ญ๐Ÿพ|women_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_dark_skin_tone ๐Ÿ‘ญ๐Ÿฟ|woman_and_man_holding_hands ๐Ÿ‘ซ|woman_and_man_holding_hands_light_skin_tone ๐Ÿ‘ซ๐Ÿป|woman_and_man_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_medium_light_skin_tone ๐Ÿ‘ซ๐Ÿผ|woman_and_man_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_medium_skin_tone ๐Ÿ‘ซ๐Ÿฝ|woman_and_man_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_medium_dark_skin_tone ๐Ÿ‘ซ๐Ÿพ|woman_and_man_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_dark_skin_tone ๐Ÿ‘ซ๐Ÿฟ|men_holding_hands ๐Ÿ‘ฌ|men_holding_hands_light_skin_tone ๐Ÿ‘ฌ๐Ÿป|men_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_medium_light_skin_tone ๐Ÿ‘ฌ๐Ÿผ|men_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_medium_skin_tone ๐Ÿ‘ฌ๐Ÿฝ|men_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_medium_dark_skin_tone ๐Ÿ‘ฌ๐Ÿพ|men_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_dark_skin_tone ๐Ÿ‘ฌ๐Ÿฟ|kiss ๐Ÿ’|kiss_light_skin_tone ๐Ÿ’๐Ÿป|kiss_medium_light_skin_tone ๐Ÿ’๐Ÿผ|kiss_medium_skin_tone ๐Ÿ’๐Ÿฝ|kiss_medium_dark_skin_tone ๐Ÿ’๐Ÿพ|kiss_dark_skin_tone ๐Ÿ’๐Ÿฟ|kiss_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_woman_man ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_woman_man ๐Ÿ‘ฉโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_man_man ๐Ÿ‘จโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_woman ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ|kiss_woman_woman ๐Ÿ‘ฉโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ|kiss_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart ๐Ÿ’‘|couple_with_heart_light_skin_tone ๐Ÿ’‘๐Ÿป|couple_with_heart_medium_light_skin_tone ๐Ÿ’‘๐Ÿผ|couple_with_heart_medium_skin_tone ๐Ÿ’‘๐Ÿฝ|couple_with_heart_medium_dark_skin_tone ๐Ÿ’‘๐Ÿพ|couple_with_heart_dark_skin_tone ๐Ÿ’‘๐Ÿฟ|couple_with_heart_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_woman_man ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ|couple_with_heart_woman_man ๐Ÿ‘ฉโ€โคโ€๐Ÿ‘จ|couple_with_heart_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ|couple_with_heart_man_man ๐Ÿ‘จโ€โคโ€๐Ÿ‘จ|couple_with_heart_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_woman ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ|couple_with_heart_woman_woman ๐Ÿ‘ฉโ€โคโ€๐Ÿ‘ฉ|couple_with_heart_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|family_man_woman_boy ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ|family_man_woman_girl ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง|family_man_woman_girl_boy ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_man_woman_boy_boy ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_man_woman_girl_girl ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_man_man_boy ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ|family_man_man_girl ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง|family_man_man_girl_boy ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_man_man_boy_boy ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_man_man_girl_girl ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_woman_woman_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ|family_woman_woman_girl ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง|family_woman_woman_girl_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_woman_woman_boy_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_woman_woman_girl_girl ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_man_boy ๐Ÿ‘จโ€๐Ÿ‘ฆ|family_man_boy_boy ๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_man_girl ๐Ÿ‘จโ€๐Ÿ‘ง|family_man_girl_boy ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_man_girl_girl ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_woman_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฆ|family_woman_boy_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_woman_girl ๐Ÿ‘ฉโ€๐Ÿ‘ง|family_woman_girl_boy ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_woman_girl_girl ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง|speaking_head ๐Ÿ—ฃ๏ธ|speaking_head ๐Ÿ—ฃ|bust_in_silhouette ๐Ÿ‘ค|busts_in_silhouette ๐Ÿ‘ฅ|people_hugging ๐Ÿซ‚|family ๐Ÿ‘ช|family_adult_adult_child ๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’|family_adult_adult_child_child ๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’|family_adult_child ๐Ÿง‘โ€๐Ÿง’|family_adult_child_child ๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’|footprints ๐Ÿ‘ฃ|light_skin_tone ๐Ÿป|medium_light_skin_tone ๐Ÿผ|medium_skin_tone ๐Ÿฝ|medium_dark_skin_tone ๐Ÿพ|dark_skin_tone ๐Ÿฟ|red_hair ๐Ÿฆฐ|curly_hair ๐Ÿฆฑ|white_hair ๐Ÿฆณ|bald ๐Ÿฆฒ|monkey_face ๐Ÿต|monkey ๐Ÿ’|gorilla ๐Ÿฆ|orangutan ๐Ÿฆง|dog_face ๐Ÿถ|dog ๐Ÿ•|guide_dog ๐Ÿฆฎ|service_dog ๐Ÿ•โ€๐Ÿฆบ|poodle ๐Ÿฉ|wolf ๐Ÿบ|fox ๐ŸฆŠ|raccoon ๐Ÿฆ|cat_face ๐Ÿฑ|cat ๐Ÿˆ|black_cat ๐Ÿˆโ€โฌ›|lion ๐Ÿฆ|tiger_face ๐Ÿฏ|tiger ๐Ÿ…|leopard ๐Ÿ†|horse_face ๐Ÿด|moose ๐ŸซŽ|donkey ๐Ÿซ|horse ๐ŸŽ|unicorn ๐Ÿฆ„|zebra ๐Ÿฆ“|deer ๐ŸฆŒ|bison ๐Ÿฆฌ|cow_face ๐Ÿฎ|ox ๐Ÿ‚|water_buffalo ๐Ÿƒ|cow ๐Ÿ„|pig_face ๐Ÿท|pig ๐Ÿ–|boar ๐Ÿ—|pig_nose ๐Ÿฝ|ram ๐Ÿ|ewe ๐Ÿ‘|goat ๐Ÿ|camel ๐Ÿช|two_hump_camel ๐Ÿซ|llama ๐Ÿฆ™|giraffe ๐Ÿฆ’|elephant ๐Ÿ˜|mammoth ๐Ÿฆฃ|rhinoceros ๐Ÿฆ|hippopotamus ๐Ÿฆ›|mouse_face ๐Ÿญ|mouse ๐Ÿ|rat ๐Ÿ€|hamster ๐Ÿน|rabbit_face ๐Ÿฐ|rabbit ๐Ÿ‡|chipmunk ๐Ÿฟ๏ธ|chipmunk ๐Ÿฟ|beaver ๐Ÿฆซ|hedgehog ๐Ÿฆ”|bat ๐Ÿฆ‡|bear ๐Ÿป|polar_bear ๐Ÿปโ€โ„๏ธ|polar_bear ๐Ÿปโ€โ„|koala ๐Ÿจ|panda ๐Ÿผ|sloth ๐Ÿฆฅ|otter ๐Ÿฆฆ|skunk ๐Ÿฆจ|kangaroo ๐Ÿฆ˜|badger ๐Ÿฆก|paw_prints ๐Ÿพ|turkey ๐Ÿฆƒ|chicken ๐Ÿ”|rooster ๐Ÿ“|hatching_chick ๐Ÿฃ|baby_chick ๐Ÿค|front_facing_baby_chick ๐Ÿฅ|bird ๐Ÿฆ|penguin ๐Ÿง|dove ๐Ÿ•Š๏ธ|dove ๐Ÿ•Š|eagle ๐Ÿฆ…|duck ๐Ÿฆ†|swan ๐Ÿฆข|owl ๐Ÿฆ‰|dodo ๐Ÿฆค|feather ๐Ÿชถ|flamingo ๐Ÿฆฉ|peacock ๐Ÿฆš|parrot ๐Ÿฆœ|wing ๐Ÿชฝ|black_bird ๐Ÿฆโ€โฌ›|goose ๐Ÿชฟ|phoenix ๐Ÿฆโ€๐Ÿ”ฅ|frog ๐Ÿธ|crocodile ๐ŸŠ|turtle ๐Ÿข|lizard ๐ŸฆŽ|snake ๐Ÿ|dragon_face ๐Ÿฒ|dragon ๐Ÿ‰|sauropod ๐Ÿฆ•|t_rex ๐Ÿฆ–|spouting_whale ๐Ÿณ|whale ๐Ÿ‹|dolphin ๐Ÿฌ|seal ๐Ÿฆญ|fish ๐ŸŸ|tropical_fish ๐Ÿ |blowfish ๐Ÿก|shark ๐Ÿฆˆ|octopus ๐Ÿ™|spiral_shell ๐Ÿš|coral ๐Ÿชธ|jellyfish ๐Ÿชผ|snail ๐ŸŒ|butterfly ๐Ÿฆ‹|bug ๐Ÿ›|ant ๐Ÿœ|honeybee ๐Ÿ|beetle ๐Ÿชฒ|lady_beetle ๐Ÿž|cricket ๐Ÿฆ—|cockroach ๐Ÿชณ|spider ๐Ÿ•ท๏ธ|spider ๐Ÿ•ท|spider_web ๐Ÿ•ธ๏ธ|spider_web ๐Ÿ•ธ|scorpion ๐Ÿฆ‚|mosquito ๐ŸฆŸ|fly ๐Ÿชฐ|worm ๐Ÿชฑ|microbe ๐Ÿฆ |bouquet ๐Ÿ’|cherry_blossom ๐ŸŒธ|white_flower ๐Ÿ’ฎ|lotus ๐Ÿชท|rosette ๐Ÿต๏ธ|rosette ๐Ÿต|rose ๐ŸŒน|wilted_flower ๐Ÿฅ€|hibiscus ๐ŸŒบ|sunflower ๐ŸŒป|blossom ๐ŸŒผ|tulip ๐ŸŒท|hyacinth ๐Ÿชป|seedling ๐ŸŒฑ|potted_plant ๐Ÿชด|evergreen_tree ๐ŸŒฒ|deciduous_tree ๐ŸŒณ|palm_tree ๐ŸŒด|cactus ๐ŸŒต|sheaf_of_rice ๐ŸŒพ|herb ๐ŸŒฟ|shamrock โ˜˜๏ธ|shamrock โ˜˜|four_leaf_clover ๐Ÿ€|maple_leaf ๐Ÿ|fallen_leaf ๐Ÿ‚|leaf_fluttering_in_wind ๐Ÿƒ|empty_nest ๐Ÿชน|nest_with_eggs ๐Ÿชบ|mushroom ๐Ÿ„|grapes ๐Ÿ‡|melon ๐Ÿˆ|watermelon ๐Ÿ‰|tangerine ๐ŸŠ|lemon ๐Ÿ‹|lime ๐Ÿ‹โ€๐ŸŸฉ|banana ๐ŸŒ|pineapple ๐Ÿ|mango ๐Ÿฅญ|red_apple ๐ŸŽ|green_apple ๐Ÿ|pear ๐Ÿ|peach ๐Ÿ‘|cherries ๐Ÿ’|strawberry ๐Ÿ“|blueberries ๐Ÿซ|kiwi_fruit ๐Ÿฅ|tomato ๐Ÿ…|olive ๐Ÿซ’|coconut ๐Ÿฅฅ|avocado ๐Ÿฅ‘|eggplant ๐Ÿ†|potato ๐Ÿฅ”|carrot ๐Ÿฅ•|ear_of_corn ๐ŸŒฝ|hot_pepper ๐ŸŒถ๏ธ|hot_pepper ๐ŸŒถ|bell_pepper ๐Ÿซ‘|cucumber ๐Ÿฅ’|leafy_green ๐Ÿฅฌ|broccoli ๐Ÿฅฆ|garlic ๐Ÿง„|onion ๐Ÿง…|peanuts ๐Ÿฅœ|beans ๐Ÿซ˜|chestnut ๐ŸŒฐ|ginger_root ๐Ÿซš|pea_pod ๐Ÿซ›|brown_mushroom ๐Ÿ„โ€๐ŸŸซ|bread ๐Ÿž|croissant ๐Ÿฅ|baguette_bread ๐Ÿฅ–|flatbread ๐Ÿซ“|pretzel ๐Ÿฅจ|bagel ๐Ÿฅฏ|pancakes ๐Ÿฅž|waffle ๐Ÿง‡|cheese_wedge ๐Ÿง€|meat_on_bone ๐Ÿ–|poultry_leg ๐Ÿ—|cut_of_meat ๐Ÿฅฉ|bacon ๐Ÿฅ“|hamburger ๐Ÿ”|french_fries ๐ŸŸ|pizza ๐Ÿ•|hot_dog ๐ŸŒญ|sandwich ๐Ÿฅช|taco ๐ŸŒฎ|burrito ๐ŸŒฏ|tamale ๐Ÿซ”|stuffed_flatbread ๐Ÿฅ™|falafel ๐Ÿง†|egg ๐Ÿฅš|cooking ๐Ÿณ|shallow_pan_of_food ๐Ÿฅ˜|pot_of_food ๐Ÿฒ|fondue ๐Ÿซ•|bowl_with_spoon ๐Ÿฅฃ|green_salad ๐Ÿฅ—|popcorn ๐Ÿฟ|butter ๐Ÿงˆ|salt ๐Ÿง‚|canned_food ๐Ÿฅซ|bento_box ๐Ÿฑ|rice_cracker ๐Ÿ˜|rice_ball ๐Ÿ™|cooked_rice ๐Ÿš|curry_rice ๐Ÿ›|steaming_bowl ๐Ÿœ|spaghetti ๐Ÿ|roasted_sweet_potato ๐Ÿ |oden ๐Ÿข|sushi ๐Ÿฃ|fried_shrimp ๐Ÿค|fish_cake_with_swirl ๐Ÿฅ|moon_cake ๐Ÿฅฎ|dango ๐Ÿก|dumpling ๐ŸฅŸ|fortune_cookie ๐Ÿฅ |takeout_box ๐Ÿฅก|crab ๐Ÿฆ€|lobster ๐Ÿฆž|shrimp ๐Ÿฆ|squid ๐Ÿฆ‘|oyster ๐Ÿฆช|soft_ice_cream ๐Ÿฆ|shaved_ice ๐Ÿง|ice_cream ๐Ÿจ|doughnut ๐Ÿฉ|cookie ๐Ÿช|birthday_cake ๐ŸŽ‚|shortcake ๐Ÿฐ|cupcake ๐Ÿง|pie ๐Ÿฅง|chocolate_bar ๐Ÿซ|candy ๐Ÿฌ|lollipop ๐Ÿญ|custard ๐Ÿฎ|honey_pot ๐Ÿฏ|baby_bottle ๐Ÿผ|glass_of_milk ๐Ÿฅ›|hot_beverage โ˜•|teapot ๐Ÿซ–|teacup_without_handle ๐Ÿต|sake ๐Ÿถ|bottle_with_popping_cork ๐Ÿพ|wine_glass ๐Ÿท|cocktail_glass ๐Ÿธ|tropical_drink ๐Ÿน|beer_mug ๐Ÿบ|clinking_beer_mugs ๐Ÿป|clinking_glasses ๐Ÿฅ‚|tumbler_glass ๐Ÿฅƒ|pouring_liquid ๐Ÿซ—|cup_with_straw ๐Ÿฅค|bubble_tea ๐Ÿง‹|beverage_box ๐Ÿงƒ|mate ๐Ÿง‰|ice ๐ŸงŠ|chopsticks ๐Ÿฅข|fork_and_knife_with_plate ๐Ÿฝ๏ธ|fork_and_knife_with_plate ๐Ÿฝ|fork_and_knife ๐Ÿด|spoon ๐Ÿฅ„|kitchen_knife ๐Ÿ”ช|jar ๐Ÿซ™|amphora ๐Ÿบ|globe_showing_europe_africa ๐ŸŒ|globe_showing_americas ๐ŸŒŽ|globe_showing_asia_australia ๐ŸŒ|globe_with_meridians ๐ŸŒ|world_map ๐Ÿ—บ๏ธ|world_map ๐Ÿ—บ|map_of_japan ๐Ÿ—พ|compass ๐Ÿงญ|snow_capped_mountain ๐Ÿ”๏ธ|snow_capped_mountain ๐Ÿ”|mountain โ›ฐ๏ธ|mountain โ›ฐ|volcano ๐ŸŒ‹|mount_fuji ๐Ÿ—ป|camping ๐Ÿ•๏ธ|camping ๐Ÿ•|beach_with_umbrella ๐Ÿ–๏ธ|beach_with_umbrella ๐Ÿ–|desert ๐Ÿœ๏ธ|desert ๐Ÿœ|desert_island ๐Ÿ๏ธ|desert_island ๐Ÿ|national_park ๐Ÿž๏ธ|national_park ๐Ÿž|stadium ๐ŸŸ๏ธ|stadium ๐ŸŸ|classical_building ๐Ÿ›๏ธ|classical_building ๐Ÿ›|building_construction ๐Ÿ—๏ธ|building_construction ๐Ÿ—|brick ๐Ÿงฑ|rock ๐Ÿชจ|wood ๐Ÿชต|hut ๐Ÿ›–|houses ๐Ÿ˜๏ธ|houses ๐Ÿ˜|derelict_house ๐Ÿš๏ธ|derelict_house ๐Ÿš|house ๐Ÿ |house_with_garden ๐Ÿก|office_building ๐Ÿข|japanese_post_office ๐Ÿฃ|post_office ๐Ÿค|hospital ๐Ÿฅ|bank ๐Ÿฆ|hotel ๐Ÿจ|love_hotel ๐Ÿฉ|convenience_store ๐Ÿช|school ๐Ÿซ|department_store ๐Ÿฌ|factory ๐Ÿญ|japanese_castle ๐Ÿฏ|castle ๐Ÿฐ|wedding ๐Ÿ’’|tokyo_tower ๐Ÿ—ผ|statue_of_liberty ๐Ÿ—ฝ|church โ›ช|mosque ๐Ÿ•Œ|hindu_temple ๐Ÿ›•|synagogue ๐Ÿ•|shinto_shrine โ›ฉ๏ธ|shinto_shrine โ›ฉ|kaaba ๐Ÿ•‹|fountain โ›ฒ|tent โ›บ|foggy ๐ŸŒ|night_with_stars ๐ŸŒƒ|cityscape ๐Ÿ™๏ธ|cityscape ๐Ÿ™|sunrise_over_mountains ๐ŸŒ„|sunrise ๐ŸŒ…|cityscape_at_dusk ๐ŸŒ†|sunset ๐ŸŒ‡|bridge_at_night ๐ŸŒ‰|hot_springs โ™จ๏ธ|hot_springs โ™จ|carousel_horse ๐ŸŽ |playground_slide ๐Ÿ›|ferris_wheel ๐ŸŽก|roller_coaster ๐ŸŽข|barber_pole ๐Ÿ’ˆ|circus_tent ๐ŸŽช|locomotive ๐Ÿš‚|railway_car ๐Ÿšƒ|high_speed_train ๐Ÿš„|bullet_train ๐Ÿš…|train ๐Ÿš†|metro ๐Ÿš‡|light_rail ๐Ÿšˆ|station ๐Ÿš‰|tram ๐ŸšŠ|monorail ๐Ÿš|mountain_railway ๐Ÿšž|tram_car ๐Ÿš‹|bus ๐ŸšŒ|oncoming_bus ๐Ÿš|trolleybus ๐ŸšŽ|minibus ๐Ÿš|ambulance ๐Ÿš‘|fire_engine ๐Ÿš’|police_car ๐Ÿš“|oncoming_police_car ๐Ÿš”|taxi ๐Ÿš•|oncoming_taxi ๐Ÿš–|automobile ๐Ÿš—|oncoming_automobile ๐Ÿš˜|sport_utility_vehicle ๐Ÿš™|pickup_truck ๐Ÿ›ป|delivery_truck ๐Ÿšš|articulated_lorry ๐Ÿš›|tractor ๐Ÿšœ|racing_car ๐ŸŽ๏ธ|racing_car ๐ŸŽ|motorcycle ๐Ÿ๏ธ|motorcycle ๐Ÿ|motor_scooter ๐Ÿ›ต|manual_wheelchair ๐Ÿฆฝ|motorized_wheelchair ๐Ÿฆผ|auto_rickshaw ๐Ÿ›บ|bicycle ๐Ÿšฒ|kick_scooter ๐Ÿ›ด|skateboard ๐Ÿ›น|roller_skate ๐Ÿ›ผ|bus_stop ๐Ÿš|motorway ๐Ÿ›ฃ๏ธ|motorway ๐Ÿ›ฃ|railway_track ๐Ÿ›ค๏ธ|railway_track ๐Ÿ›ค|oil_drum ๐Ÿ›ข๏ธ|oil_drum ๐Ÿ›ข|fuel_pump โ›ฝ|wheel ๐Ÿ›ž|police_car_light ๐Ÿšจ|horizontal_traffic_light ๐Ÿšฅ|vertical_traffic_light ๐Ÿšฆ|stop_sign ๐Ÿ›‘|construction ๐Ÿšง|anchor โš“|ring_buoy ๐Ÿ›Ÿ|sailboat โ›ต|canoe ๐Ÿ›ถ|speedboat ๐Ÿšค|passenger_ship ๐Ÿ›ณ๏ธ|passenger_ship ๐Ÿ›ณ|ferry โ›ด๏ธ|ferry โ›ด|motor_boat ๐Ÿ›ฅ๏ธ|motor_boat ๐Ÿ›ฅ|ship ๐Ÿšข|airplane โœˆ๏ธ|airplane โœˆ|small_airplane ๐Ÿ›ฉ๏ธ|small_airplane ๐Ÿ›ฉ|airplane_departure ๐Ÿ›ซ|airplane_arrival ๐Ÿ›ฌ|parachute ๐Ÿช‚|seat ๐Ÿ’บ|helicopter ๐Ÿš|suspension_railway ๐ŸšŸ|mountain_cableway ๐Ÿš |aerial_tramway ๐Ÿšก|satellite ๐Ÿ›ฐ๏ธ|satellite ๐Ÿ›ฐ|rocket ๐Ÿš€|flying_saucer ๐Ÿ›ธ|bellhop_bell ๐Ÿ›Ž๏ธ|bellhop_bell ๐Ÿ›Ž|luggage ๐Ÿงณ|hourglass_done โŒ›|hourglass_not_done โณ|watch โŒš|alarm_clock โฐ|stopwatch โฑ๏ธ|stopwatch โฑ|timer_clock โฒ๏ธ|timer_clock โฒ|mantelpiece_clock ๐Ÿ•ฐ๏ธ|mantelpiece_clock ๐Ÿ•ฐ|twelve_o_clock ๐Ÿ•›|twelve_thirty ๐Ÿ•ง|one_o_clock ๐Ÿ•|one_thirty ๐Ÿ•œ|two_o_clock ๐Ÿ•‘|two_thirty ๐Ÿ•|three_o_clock ๐Ÿ•’|three_thirty ๐Ÿ•ž|four_o_clock ๐Ÿ•“|four_thirty ๐Ÿ•Ÿ|five_o_clock ๐Ÿ•”|five_thirty ๐Ÿ• |six_o_clock ๐Ÿ••|six_thirty ๐Ÿ•ก|seven_o_clock ๐Ÿ•–|seven_thirty ๐Ÿ•ข|eight_o_clock ๐Ÿ•—|eight_thirty ๐Ÿ•ฃ|nine_o_clock ๐Ÿ•˜|nine_thirty ๐Ÿ•ค|ten_o_clock ๐Ÿ•™|ten_thirty ๐Ÿ•ฅ|eleven_o_clock ๐Ÿ•š|eleven_thirty ๐Ÿ•ฆ|new_moon ๐ŸŒ‘|waxing_crescent_moon ๐ŸŒ’|first_quarter_moon ๐ŸŒ“|waxing_gibbous_moon ๐ŸŒ”|full_moon ๐ŸŒ•|waning_gibbous_moon ๐ŸŒ–|last_quarter_moon ๐ŸŒ—|waning_crescent_moon ๐ŸŒ˜|crescent_moon ๐ŸŒ™|new_moon_face ๐ŸŒš|first_quarter_moon_face ๐ŸŒ›|last_quarter_moon_face ๐ŸŒœ|thermometer ๐ŸŒก๏ธ|thermometer ๐ŸŒก|sun โ˜€๏ธ|sun โ˜€|full_moon_face ๐ŸŒ|sun_with_face ๐ŸŒž|ringed_planet ๐Ÿช|star โญ|glowing_star ๐ŸŒŸ|shooting_star ๐ŸŒ |milky_way ๐ŸŒŒ|cloud โ˜๏ธ|cloud โ˜|sun_behind_cloud โ›…|cloud_with_lightning_and_rain โ›ˆ๏ธ|cloud_with_lightning_and_rain โ›ˆ|sun_behind_small_cloud ๐ŸŒค๏ธ|sun_behind_small_cloud ๐ŸŒค|sun_behind_large_cloud ๐ŸŒฅ๏ธ|sun_behind_large_cloud ๐ŸŒฅ|sun_behind_rain_cloud ๐ŸŒฆ๏ธ|sun_behind_rain_cloud ๐ŸŒฆ|cloud_with_rain ๐ŸŒง๏ธ|cloud_with_rain ๐ŸŒง|cloud_with_snow ๐ŸŒจ๏ธ|cloud_with_snow ๐ŸŒจ|cloud_with_lightning ๐ŸŒฉ๏ธ|cloud_with_lightning ๐ŸŒฉ|tornado ๐ŸŒช๏ธ|tornado ๐ŸŒช|fog ๐ŸŒซ๏ธ|fog ๐ŸŒซ|wind_face ๐ŸŒฌ๏ธ|wind_face ๐ŸŒฌ|cyclone ๐ŸŒ€|rainbow ๐ŸŒˆ|closed_umbrella ๐ŸŒ‚|umbrella โ˜‚๏ธ|umbrella โ˜‚|umbrella_with_rain_drops โ˜”|umbrella_on_ground โ›ฑ๏ธ|umbrella_on_ground โ›ฑ|high_voltage โšก|snowflake โ„๏ธ|snowflake โ„|snowman โ˜ƒ๏ธ|snowman โ˜ƒ|snowman_without_snow โ›„|comet โ˜„๏ธ|comet โ˜„|fire ๐Ÿ”ฅ|droplet ๐Ÿ’ง|water_wave ๐ŸŒŠ|jack_o_lantern ๐ŸŽƒ|christmas_tree ๐ŸŽ„|fireworks ๐ŸŽ†|sparkler ๐ŸŽ‡|firecracker ๐Ÿงจ|sparkles โœจ|balloon ๐ŸŽˆ|party_popper ๐ŸŽ‰|confetti_ball ๐ŸŽŠ|tanabata_tree ๐ŸŽ‹|pine_decoration ๐ŸŽ|japanese_dolls ๐ŸŽŽ|carp_streamer ๐ŸŽ|wind_chime ๐ŸŽ|moon_viewing_ceremony ๐ŸŽ‘|red_envelope ๐Ÿงง|ribbon ๐ŸŽ€|wrapped_gift ๐ŸŽ|reminder_ribbon ๐ŸŽ—๏ธ|reminder_ribbon ๐ŸŽ—|admission_tickets ๐ŸŽŸ๏ธ|admission_tickets ๐ŸŽŸ|ticket ๐ŸŽซ|military_medal ๐ŸŽ–๏ธ|military_medal ๐ŸŽ–|trophy ๐Ÿ†|sports_medal ๐Ÿ…|1st_place_medal ๐Ÿฅ‡|2nd_place_medal ๐Ÿฅˆ|3rd_place_medal ๐Ÿฅ‰|soccer_ball โšฝ|baseball โšพ|softball ๐ŸฅŽ|basketball ๐Ÿ€|volleyball ๐Ÿ|american_football ๐Ÿˆ|rugby_football ๐Ÿ‰|tennis ๐ŸŽพ|flying_disc ๐Ÿฅ|bowling ๐ŸŽณ|cricket_game ๐Ÿ|field_hockey ๐Ÿ‘|ice_hockey ๐Ÿ’|lacrosse ๐Ÿฅ|ping_pong ๐Ÿ“|badminton ๐Ÿธ|boxing_glove ๐ŸฅŠ|martial_arts_uniform ๐Ÿฅ‹|goal_net ๐Ÿฅ…|flag_in_hole โ›ณ|ice_skate โ›ธ๏ธ|ice_skate โ›ธ|fishing_pole ๐ŸŽฃ|diving_mask ๐Ÿคฟ|running_shirt ๐ŸŽฝ|skis ๐ŸŽฟ|sled ๐Ÿ›ท|curling_stone ๐ŸฅŒ|bullseye ๐ŸŽฏ|yo_yo ๐Ÿช€|kite ๐Ÿช|water_pistol ๐Ÿ”ซ|pool_8_ball ๐ŸŽฑ|crystal_ball ๐Ÿ”ฎ|magic_wand ๐Ÿช„|video_game ๐ŸŽฎ|joystick ๐Ÿ•น๏ธ|joystick ๐Ÿ•น|slot_machine ๐ŸŽฐ|game_die ๐ŸŽฒ|puzzle_piece ๐Ÿงฉ|teddy_bear ๐Ÿงธ|pi_ata ๐Ÿช…|mirror_ball ๐Ÿชฉ|nesting_dolls ๐Ÿช†|spade_suit โ™ ๏ธ|spade_suit โ™ |heart_suit โ™ฅ๏ธ|heart_suit โ™ฅ|diamond_suit โ™ฆ๏ธ|diamond_suit โ™ฆ|club_suit โ™ฃ๏ธ|club_suit โ™ฃ|chess_pawn โ™Ÿ๏ธ|chess_pawn โ™Ÿ|joker ๐Ÿƒ|mahjong_red_dragon ๐Ÿ€„|flower_playing_cards ๐ŸŽด|performing_arts ๐ŸŽญ|framed_picture ๐Ÿ–ผ๏ธ|framed_picture ๐Ÿ–ผ|artist_palette ๐ŸŽจ|thread ๐Ÿงต|sewing_needle ๐Ÿชก|yarn ๐Ÿงถ|knot ๐Ÿชข|glasses ๐Ÿ‘“|sunglasses ๐Ÿ•ถ๏ธ|sunglasses ๐Ÿ•ถ|goggles ๐Ÿฅฝ|lab_coat ๐Ÿฅผ|safety_vest ๐Ÿฆบ|necktie ๐Ÿ‘”|t_shirt ๐Ÿ‘•|jeans ๐Ÿ‘–|scarf ๐Ÿงฃ|gloves ๐Ÿงค|coat ๐Ÿงฅ|socks ๐Ÿงฆ|dress ๐Ÿ‘—|kimono ๐Ÿ‘˜|sari ๐Ÿฅป|one_piece_swimsuit ๐Ÿฉฑ|briefs ๐Ÿฉฒ|shorts ๐Ÿฉณ|bikini ๐Ÿ‘™|woman_s_clothes ๐Ÿ‘š|folding_hand_fan ๐Ÿชญ|purse ๐Ÿ‘›|handbag ๐Ÿ‘œ|clutch_bag ๐Ÿ‘|shopping_bags ๐Ÿ›๏ธ|shopping_bags ๐Ÿ›|backpack ๐ŸŽ’|thong_sandal ๐Ÿฉด|man_s_shoe ๐Ÿ‘ž|running_shoe ๐Ÿ‘Ÿ|hiking_boot ๐Ÿฅพ|flat_shoe ๐Ÿฅฟ|high_heeled_shoe ๐Ÿ‘ |woman_s_sandal ๐Ÿ‘ก|ballet_shoes ๐Ÿฉฐ|woman_s_boot ๐Ÿ‘ข|hair_pick ๐Ÿชฎ|crown ๐Ÿ‘‘|woman_s_hat ๐Ÿ‘’|top_hat ๐ŸŽฉ|graduation_cap ๐ŸŽ“|billed_cap ๐Ÿงข|military_helmet ๐Ÿช–|rescue_worker_s_helmet โ›‘๏ธ|rescue_worker_s_helmet โ›‘|prayer_beads ๐Ÿ“ฟ|lipstick ๐Ÿ’„|ring ๐Ÿ’|gem_stone ๐Ÿ’Ž|muted_speaker ๐Ÿ”‡|speaker_low_volume ๐Ÿ”ˆ|speaker_medium_volume ๐Ÿ”‰|speaker_high_volume ๐Ÿ”Š|loudspeaker ๐Ÿ“ข|megaphone ๐Ÿ“ฃ|postal_horn ๐Ÿ“ฏ|bell ๐Ÿ””|bell_with_slash ๐Ÿ”•|musical_score ๐ŸŽผ|musical_note ๐ŸŽต|musical_notes ๐ŸŽถ|studio_microphone ๐ŸŽ™๏ธ|studio_microphone ๐ŸŽ™|level_slider ๐ŸŽš๏ธ|level_slider ๐ŸŽš|control_knobs ๐ŸŽ›๏ธ|control_knobs ๐ŸŽ›|microphone ๐ŸŽค|headphone ๐ŸŽง|radio ๐Ÿ“ป|saxophone ๐ŸŽท|accordion ๐Ÿช—|guitar ๐ŸŽธ|musical_keyboard ๐ŸŽน|trumpet ๐ŸŽบ|violin ๐ŸŽป|banjo ๐Ÿช•|drum ๐Ÿฅ|long_drum ๐Ÿช˜|maracas ๐Ÿช‡|flute ๐Ÿชˆ|mobile_phone ๐Ÿ“ฑ|mobile_phone_with_arrow ๐Ÿ“ฒ|telephone โ˜Ž๏ธ|telephone โ˜Ž|telephone_receiver ๐Ÿ“ž|pager ๐Ÿ“Ÿ|fax_machine ๐Ÿ“ |battery ๐Ÿ”‹|low_battery ๐Ÿชซ|electric_plug ๐Ÿ”Œ|laptop ๐Ÿ’ป|desktop_computer ๐Ÿ–ฅ๏ธ|desktop_computer ๐Ÿ–ฅ|printer ๐Ÿ–จ๏ธ|printer ๐Ÿ–จ|keyboard โŒจ๏ธ|keyboard โŒจ|computer_mouse ๐Ÿ–ฑ๏ธ|computer_mouse ๐Ÿ–ฑ|trackball ๐Ÿ–ฒ๏ธ|trackball ๐Ÿ–ฒ|computer_disk ๐Ÿ’ฝ|floppy_disk ๐Ÿ’พ|optical_disk ๐Ÿ’ฟ|dvd ๐Ÿ“€|abacus ๐Ÿงฎ|movie_camera ๐ŸŽฅ|film_frames ๐ŸŽž๏ธ|film_frames ๐ŸŽž|film_projector ๐Ÿ“ฝ๏ธ|film_projector ๐Ÿ“ฝ|clapper_board ๐ŸŽฌ|television ๐Ÿ“บ|camera ๐Ÿ“ท|camera_with_flash ๐Ÿ“ธ|video_camera ๐Ÿ“น|videocassette ๐Ÿ“ผ|magnifying_glass_tilted_left ๐Ÿ”|magnifying_glass_tilted_right ๐Ÿ”Ž|candle ๐Ÿ•ฏ๏ธ|candle ๐Ÿ•ฏ|light_bulb ๐Ÿ’ก|flashlight ๐Ÿ”ฆ|red_paper_lantern ๐Ÿฎ|diya_lamp ๐Ÿช”|notebook_with_decorative_cover ๐Ÿ“”|closed_book ๐Ÿ“•|open_book ๐Ÿ“–|green_book ๐Ÿ“—|blue_book ๐Ÿ“˜|orange_book ๐Ÿ“™|books ๐Ÿ“š|notebook ๐Ÿ““|ledger ๐Ÿ“’|page_with_curl ๐Ÿ“ƒ|scroll ๐Ÿ“œ|page_facing_up ๐Ÿ“„|newspaper ๐Ÿ“ฐ|rolled_up_newspaper ๐Ÿ—ž๏ธ|rolled_up_newspaper ๐Ÿ—ž|bookmark_tabs ๐Ÿ“‘|bookmark ๐Ÿ”–|label ๐Ÿท๏ธ|label ๐Ÿท|money_bag ๐Ÿ’ฐ|coin ๐Ÿช™|yen_banknote ๐Ÿ’ด|dollar_banknote ๐Ÿ’ต|euro_banknote ๐Ÿ’ถ|pound_banknote ๐Ÿ’ท|money_with_wings ๐Ÿ’ธ|credit_card ๐Ÿ’ณ|receipt ๐Ÿงพ|chart_increasing_with_yen ๐Ÿ’น|envelope โœ‰๏ธ|envelope โœ‰|e_mail ๐Ÿ“ง|incoming_envelope ๐Ÿ“จ|envelope_with_arrow ๐Ÿ“ฉ|outbox_tray ๐Ÿ“ค|inbox_tray ๐Ÿ“ฅ|package ๐Ÿ“ฆ|closed_mailbox_with_raised_flag ๐Ÿ“ซ|closed_mailbox_with_lowered_flag ๐Ÿ“ช|open_mailbox_with_raised_flag ๐Ÿ“ฌ|open_mailbox_with_lowered_flag ๐Ÿ“ญ|postbox ๐Ÿ“ฎ|ballot_box_with_ballot ๐Ÿ—ณ๏ธ|ballot_box_with_ballot ๐Ÿ—ณ|pencil โœ๏ธ|pencil โœ|black_nib โœ’๏ธ|black_nib โœ’|fountain_pen ๐Ÿ–‹๏ธ|fountain_pen ๐Ÿ–‹|pen ๐Ÿ–Š๏ธ|pen ๐Ÿ–Š|paintbrush ๐Ÿ–Œ๏ธ|paintbrush ๐Ÿ–Œ|crayon ๐Ÿ–๏ธ|crayon ๐Ÿ–|memo ๐Ÿ“|briefcase ๐Ÿ’ผ|file_folder ๐Ÿ“|open_file_folder ๐Ÿ“‚|card_index_dividers ๐Ÿ—‚๏ธ|card_index_dividers ๐Ÿ—‚|calendar ๐Ÿ“…|tear_off_calendar ๐Ÿ“†|spiral_notepad ๐Ÿ—’๏ธ|spiral_notepad ๐Ÿ—’|spiral_calendar ๐Ÿ—“๏ธ|spiral_calendar ๐Ÿ—“|card_index ๐Ÿ“‡|chart_increasing ๐Ÿ“ˆ|chart_decreasing ๐Ÿ“‰|bar_chart ๐Ÿ“Š|clipboard ๐Ÿ“‹|pushpin ๐Ÿ“Œ|round_pushpin ๐Ÿ“|paperclip ๐Ÿ“Ž|linked_paperclips ๐Ÿ–‡๏ธ|linked_paperclips ๐Ÿ–‡|straight_ruler ๐Ÿ“|triangular_ruler ๐Ÿ“|scissors โœ‚๏ธ|scissors โœ‚|card_file_box ๐Ÿ—ƒ๏ธ|card_file_box ๐Ÿ—ƒ|file_cabinet ๐Ÿ—„๏ธ|file_cabinet ๐Ÿ—„|wastebasket ๐Ÿ—‘๏ธ|wastebasket ๐Ÿ—‘|locked ๐Ÿ”’|unlocked ๐Ÿ”“|locked_with_pen ๐Ÿ”|locked_with_key ๐Ÿ”|key ๐Ÿ”‘|old_key ๐Ÿ—๏ธ|old_key ๐Ÿ—|hammer ๐Ÿ”จ|axe ๐Ÿช“|pick โ›๏ธ|pick โ›|hammer_and_pick โš’๏ธ|hammer_and_pick โš’|hammer_and_wrench ๐Ÿ› ๏ธ|hammer_and_wrench ๐Ÿ› |dagger ๐Ÿ—ก๏ธ|dagger ๐Ÿ—ก|crossed_swords โš”๏ธ|crossed_swords โš”|bomb ๐Ÿ’ฃ|boomerang ๐Ÿชƒ|bow_and_arrow ๐Ÿน|shield ๐Ÿ›ก๏ธ|shield ๐Ÿ›ก|carpentry_saw ๐Ÿชš|wrench ๐Ÿ”ง|screwdriver ๐Ÿช›|nut_and_bolt ๐Ÿ”ฉ|gear โš™๏ธ|gear โš™|clamp ๐Ÿ—œ๏ธ|clamp ๐Ÿ—œ|balance_scale โš–๏ธ|balance_scale โš–|white_cane ๐Ÿฆฏ|link ๐Ÿ”—|broken_chain โ›“๏ธโ€๐Ÿ’ฅ|broken_chain โ›“โ€๐Ÿ’ฅ|chains โ›“๏ธ|chains โ›“|hook ๐Ÿช|toolbox ๐Ÿงฐ|magnet ๐Ÿงฒ|ladder ๐Ÿชœ|alembic โš—๏ธ|alembic โš—|test_tube ๐Ÿงช|petri_dish ๐Ÿงซ|dna ๐Ÿงฌ|microscope ๐Ÿ”ฌ|telescope ๐Ÿ”ญ|satellite_antenna ๐Ÿ“ก|syringe ๐Ÿ’‰|drop_of_blood ๐Ÿฉธ|pill ๐Ÿ’Š|adhesive_bandage ๐Ÿฉน|crutch ๐Ÿฉผ|stethoscope ๐Ÿฉบ|x_ray ๐Ÿฉป|door ๐Ÿšช|elevator ๐Ÿ›—|mirror ๐Ÿชž|window ๐ŸชŸ|bed ๐Ÿ›๏ธ|bed ๐Ÿ›|couch_and_lamp ๐Ÿ›‹๏ธ|couch_and_lamp ๐Ÿ›‹|chair ๐Ÿช‘|toilet ๐Ÿšฝ|plunger ๐Ÿช |shower ๐Ÿšฟ|bathtub ๐Ÿ›|mouse_trap ๐Ÿชค|razor ๐Ÿช’|lotion_bottle ๐Ÿงด|safety_pin ๐Ÿงท|broom ๐Ÿงน|basket ๐Ÿงบ|roll_of_paper ๐Ÿงป|bucket ๐Ÿชฃ|soap ๐Ÿงผ|bubbles ๐Ÿซง|toothbrush ๐Ÿชฅ|sponge ๐Ÿงฝ|fire_extinguisher ๐Ÿงฏ|shopping_cart ๐Ÿ›’|cigarette ๐Ÿšฌ|coffin โšฐ๏ธ|coffin โšฐ|headstone ๐Ÿชฆ|funeral_urn โšฑ๏ธ|funeral_urn โšฑ|nazar_amulet ๐Ÿงฟ|hamsa ๐Ÿชฌ|moai ๐Ÿ—ฟ|placard ๐Ÿชง|identification_card ๐Ÿชช|atm_sign ๐Ÿง|litter_in_bin_sign ๐Ÿšฎ|potable_water ๐Ÿšฐ|wheelchair_symbol โ™ฟ|men_s_room ๐Ÿšน|women_s_room ๐Ÿšบ|restroom ๐Ÿšป|baby_symbol ๐Ÿšผ|water_closet ๐Ÿšพ|passport_control ๐Ÿ›‚|customs ๐Ÿ›ƒ|baggage_claim ๐Ÿ›„|left_luggage ๐Ÿ›…|warning โš ๏ธ|warning โš |children_crossing ๐Ÿšธ|no_entry โ›”|prohibited ๐Ÿšซ|no_bicycles ๐Ÿšณ|no_smoking ๐Ÿšญ|no_littering ๐Ÿšฏ|non_potable_water ๐Ÿšฑ|no_pedestrians ๐Ÿšท|no_mobile_phones ๐Ÿ“ต|no_one_under_eighteen ๐Ÿ”ž|radioactive โ˜ข๏ธ|radioactive โ˜ข|biohazard โ˜ฃ๏ธ|biohazard โ˜ฃ|up_arrow โฌ†๏ธ|up_arrow โฌ†|up_right_arrow โ†—๏ธ|up_right_arrow โ†—|right_arrow โžก๏ธ|right_arrow โžก|down_right_arrow โ†˜๏ธ|down_right_arrow โ†˜|down_arrow โฌ‡๏ธ|down_arrow โฌ‡|down_left_arrow โ†™๏ธ|down_left_arrow โ†™|left_arrow โฌ…๏ธ|left_arrow โฌ…|up_left_arrow โ†–๏ธ|up_left_arrow โ†–|up_down_arrow โ†•๏ธ|up_down_arrow โ†•|left_right_arrow โ†”๏ธ|left_right_arrow โ†”|right_arrow_curving_left โ†ฉ๏ธ|right_arrow_curving_left โ†ฉ|left_arrow_curving_right โ†ช๏ธ|left_arrow_curving_right โ†ช|right_arrow_curving_up โคด๏ธ|right_arrow_curving_up โคด|right_arrow_curving_down โคต๏ธ|right_arrow_curving_down โคต|clockwise_vertical_arrows ๐Ÿ”ƒ|counterclockwise_arrows_button ๐Ÿ”„|back_arrow ๐Ÿ”™|end_arrow ๐Ÿ”š|on_arrow ๐Ÿ”›|soon_arrow ๐Ÿ”œ|top_arrow ๐Ÿ”|place_of_worship ๐Ÿ›|atom_symbol โš›๏ธ|atom_symbol โš›|om ๐Ÿ•‰๏ธ|om ๐Ÿ•‰|star_of_david โœก๏ธ|star_of_david โœก|wheel_of_dharma โ˜ธ๏ธ|wheel_of_dharma โ˜ธ|yin_yang โ˜ฏ๏ธ|yin_yang โ˜ฏ|latin_cross โœ๏ธ|latin_cross โœ|orthodox_cross โ˜ฆ๏ธ|orthodox_cross โ˜ฆ|star_and_crescent โ˜ช๏ธ|star_and_crescent โ˜ช|peace_symbol โ˜ฎ๏ธ|peace_symbol โ˜ฎ|menorah ๐Ÿ•Ž|dotted_six_pointed_star ๐Ÿ”ฏ|khanda ๐Ÿชฏ|aries โ™ˆ|taurus โ™‰|gemini โ™Š|cancer โ™‹|leo โ™Œ|virgo โ™|libra โ™Ž|scorpio โ™|sagittarius โ™|capricorn โ™‘|aquarius โ™’|pisces โ™“|ophiuchus โ›Ž|shuffle_tracks_button ๐Ÿ”€|repeat_button ๐Ÿ”|repeat_single_button ๐Ÿ”‚|play_button โ–ถ๏ธ|play_button โ–ถ|fast_forward_button โฉ|next_track_button โญ๏ธ|next_track_button โญ|play_or_pause_button โฏ๏ธ|play_or_pause_button โฏ|reverse_button โ—€๏ธ|reverse_button โ—€|fast_reverse_button โช|last_track_button โฎ๏ธ|last_track_button โฎ|upwards_button ๐Ÿ”ผ|fast_up_button โซ|downwards_button ๐Ÿ”ฝ|fast_down_button โฌ|pause_button โธ๏ธ|pause_button โธ|stop_button โน๏ธ|stop_button โน|record_button โบ๏ธ|record_button โบ|eject_button โ๏ธ|eject_button โ|cinema ๐ŸŽฆ|dim_button ๐Ÿ”…|bright_button ๐Ÿ”†|antenna_bars ๐Ÿ“ถ|wireless ๐Ÿ›œ|vibration_mode ๐Ÿ“ณ|mobile_phone_off ๐Ÿ“ด|female_sign โ™€๏ธ|female_sign โ™€|male_sign โ™‚๏ธ|male_sign โ™‚|transgender_symbol โšง๏ธ|transgender_symbol โšง|multiply โœ–๏ธ|multiply โœ–|plus โž•|minus โž–|divide โž—|heavy_equals_sign ๐ŸŸฐ|infinity โ™พ๏ธ|infinity โ™พ|double_exclamation_mark โ€ผ๏ธ|double_exclamation_mark โ€ผ|exclamation_question_mark โ‰๏ธ|exclamation_question_mark โ‰|red_question_mark โ“|white_question_mark โ”|white_exclamation_mark โ•|red_exclamation_mark โ—|wavy_dash ใ€ฐ๏ธ|wavy_dash ใ€ฐ|currency_exchange ๐Ÿ’ฑ|heavy_dollar_sign ๐Ÿ’ฒ|medical_symbol โš•๏ธ|medical_symbol โš•|recycling_symbol โ™ป๏ธ|recycling_symbol โ™ป|fleur_de_lis โšœ๏ธ|fleur_de_lis โšœ|trident_emblem ๐Ÿ”ฑ|name_badge ๐Ÿ“›|japanese_symbol_for_beginner ๐Ÿ”ฐ|hollow_red_circle โญ•|check_mark_button โœ…|check_box_with_check โ˜‘๏ธ|check_box_with_check โ˜‘|check_mark โœ”๏ธ|check_mark โœ”|cross_mark โŒ|cross_mark_button โŽ|curly_loop โžฐ|double_curly_loop โžฟ|part_alternation_mark ใ€ฝ๏ธ|part_alternation_mark ใ€ฝ|eight_spoked_asterisk โœณ๏ธ|eight_spoked_asterisk โœณ|eight_pointed_star โœด๏ธ|eight_pointed_star โœด|sparkle โ‡๏ธ|sparkle โ‡|copyright ยฉ๏ธ|copyright ยฉ|registered ยฎ๏ธ|registered ยฎ|trade_mark โ„ข๏ธ|trade_mark โ„ข|keycap_ #๏ธโƒฃ|keycap_ #โƒฃ|keycap_ *๏ธโƒฃ|keycap_ *โƒฃ|keycap_0 0๏ธโƒฃ|keycap_0 0โƒฃ|keycap_1 1๏ธโƒฃ|keycap_1 1โƒฃ|keycap_2 2๏ธโƒฃ|keycap_2 2โƒฃ|keycap_3 3๏ธโƒฃ|keycap_3 3โƒฃ|keycap_4 4๏ธโƒฃ|keycap_4 4โƒฃ|keycap_5 5๏ธโƒฃ|keycap_5 5โƒฃ|keycap_6 6๏ธโƒฃ|keycap_6 6โƒฃ|keycap_7 7๏ธโƒฃ|keycap_7 7โƒฃ|keycap_8 8๏ธโƒฃ|keycap_8 8โƒฃ|keycap_9 9๏ธโƒฃ|keycap_9 9โƒฃ|keycap_10 ๐Ÿ”Ÿ|input_latin_uppercase ๐Ÿ” |input_latin_lowercase ๐Ÿ”ก|input_numbers ๐Ÿ”ข|input_symbols ๐Ÿ”ฃ|input_latin_letters ๐Ÿ”ค|a_button_blood_type_ ๐Ÿ…ฐ๏ธ|a_button_blood_type_ ๐Ÿ…ฐ|ab_button_blood_type_ ๐Ÿ†Ž|b_button_blood_type_ ๐Ÿ…ฑ๏ธ|b_button_blood_type_ ๐Ÿ…ฑ|cl_button ๐Ÿ†‘|cool_button ๐Ÿ†’|free_button ๐Ÿ†“|information โ„น๏ธ|information โ„น|id_button ๐Ÿ†”|circled_m โ“‚๏ธ|circled_m โ“‚|new_button ๐Ÿ†•|ng_button ๐Ÿ†–|o_button_blood_type_ ๐Ÿ…พ๏ธ|o_button_blood_type_ ๐Ÿ…พ|ok_button ๐Ÿ†—|p_button ๐Ÿ…ฟ๏ธ|p_button ๐Ÿ…ฟ|sos_button ๐Ÿ†˜|up_button ๐Ÿ†™|vs_button ๐Ÿ†š|japanese_here_button ๐Ÿˆ|japanese_service_charge_button ๐Ÿˆ‚๏ธ|japanese_service_charge_button ๐Ÿˆ‚|japanese_monthly_amount_button ๐Ÿˆท๏ธ|japanese_monthly_amount_button ๐Ÿˆท|japanese_not_free_of_charge_button ๐Ÿˆถ|japanese_reserved_button ๐Ÿˆฏ|japanese_bargain_button ๐Ÿ‰|japanese_discount_button ๐Ÿˆน|japanese_free_of_charge_button ๐Ÿˆš|japanese_prohibited_button ๐Ÿˆฒ|japanese_acceptable_button ๐Ÿ‰‘|japanese_application_button ๐Ÿˆธ|japanese_passing_grade_button ๐Ÿˆด|japanese_vacancy_button ๐Ÿˆณ|japanese_congratulations_button ใŠ—๏ธ|japanese_congratulations_button ใŠ—|japanese_secret_button ใŠ™๏ธ|japanese_secret_button ใŠ™|japanese_open_for_business_button ๐Ÿˆบ|japanese_no_vacancy_button ๐Ÿˆต|red_circle ๐Ÿ”ด|orange_circle ๐ŸŸ |yellow_circle ๐ŸŸก|green_circle ๐ŸŸข|blue_circle ๐Ÿ”ต|purple_circle ๐ŸŸฃ|brown_circle ๐ŸŸค|black_circle โšซ|white_circle โšช|red_square ๐ŸŸฅ|orange_square ๐ŸŸง|yellow_square ๐ŸŸจ|green_square ๐ŸŸฉ|blue_square ๐ŸŸฆ|purple_square ๐ŸŸช|brown_square ๐ŸŸซ|black_large_square โฌ›|white_large_square โฌœ|black_medium_square โ—ผ๏ธ|black_medium_square โ—ผ|white_medium_square โ—ป๏ธ|white_medium_square โ—ป|black_medium_small_square โ—พ|white_medium_small_square โ—ฝ|black_small_square โ–ช๏ธ|black_small_square โ–ช|white_small_square โ–ซ๏ธ|white_small_square โ–ซ|large_orange_diamond ๐Ÿ”ถ|large_blue_diamond ๐Ÿ”ท|small_orange_diamond ๐Ÿ”ธ|small_blue_diamond ๐Ÿ”น|red_triangle_pointed_up ๐Ÿ”บ|red_triangle_pointed_down ๐Ÿ”ป|diamond_with_a_dot ๐Ÿ’ |radio_button ๐Ÿ”˜|white_square_button ๐Ÿ”ณ|black_square_button ๐Ÿ”ฒ|chequered_flag ๐Ÿ|triangular_flag ๐Ÿšฉ|crossed_flags ๐ŸŽŒ|black_flag ๐Ÿด|white_flag ๐Ÿณ๏ธ|white_flag ๐Ÿณ|rainbow_flag ๐Ÿณ๏ธโ€๐ŸŒˆ|rainbow_flag ๐Ÿณโ€๐ŸŒˆ|transgender_flag ๐Ÿณ๏ธโ€โšง๏ธ|transgender_flag ๐Ÿณโ€โšง๏ธ|transgender_flag ๐Ÿณ๏ธโ€โšง|transgender_flag ๐Ÿณโ€โšง|pirate_flag ๐Ÿดโ€โ˜ ๏ธ|pirate_flag ๐Ÿดโ€โ˜ |flag_ascension_island ๐Ÿ‡ฆ๐Ÿ‡จ|flag_andorra ๐Ÿ‡ฆ๐Ÿ‡ฉ|flag_united_arab_emirates ๐Ÿ‡ฆ๐Ÿ‡ช|flag_afghanistan ๐Ÿ‡ฆ๐Ÿ‡ซ|flag_antigua_barbuda ๐Ÿ‡ฆ๐Ÿ‡ฌ|flag_anguilla ๐Ÿ‡ฆ๐Ÿ‡ฎ|flag_albania ๐Ÿ‡ฆ๐Ÿ‡ฑ|flag_armenia ๐Ÿ‡ฆ๐Ÿ‡ฒ|flag_angola ๐Ÿ‡ฆ๐Ÿ‡ด|flag_antarctica ๐Ÿ‡ฆ๐Ÿ‡ถ|flag_argentina ๐Ÿ‡ฆ๐Ÿ‡ท|flag_american_samoa ๐Ÿ‡ฆ๐Ÿ‡ธ|flag_austria ๐Ÿ‡ฆ๐Ÿ‡น|flag_australia ๐Ÿ‡ฆ๐Ÿ‡บ|flag_aruba ๐Ÿ‡ฆ๐Ÿ‡ผ|flag_land_islands ๐Ÿ‡ฆ๐Ÿ‡ฝ|flag_azerbaijan ๐Ÿ‡ฆ๐Ÿ‡ฟ|flag_bosnia_herzegovina ๐Ÿ‡ง๐Ÿ‡ฆ|flag_barbados ๐Ÿ‡ง๐Ÿ‡ง|flag_bangladesh ๐Ÿ‡ง๐Ÿ‡ฉ|flag_belgium ๐Ÿ‡ง๐Ÿ‡ช|flag_burkina_faso ๐Ÿ‡ง๐Ÿ‡ซ|flag_bulgaria ๐Ÿ‡ง๐Ÿ‡ฌ|flag_bahrain ๐Ÿ‡ง๐Ÿ‡ญ|flag_burundi ๐Ÿ‡ง๐Ÿ‡ฎ|flag_benin ๐Ÿ‡ง๐Ÿ‡ฏ|flag_st_barth_lemy ๐Ÿ‡ง๐Ÿ‡ฑ|flag_bermuda ๐Ÿ‡ง๐Ÿ‡ฒ|flag_brunei ๐Ÿ‡ง๐Ÿ‡ณ|flag_bolivia ๐Ÿ‡ง๐Ÿ‡ด|flag_caribbean_netherlands ๐Ÿ‡ง๐Ÿ‡ถ|flag_brazil ๐Ÿ‡ง๐Ÿ‡ท|flag_bahamas ๐Ÿ‡ง๐Ÿ‡ธ|flag_bhutan ๐Ÿ‡ง๐Ÿ‡น|flag_bouvet_island ๐Ÿ‡ง๐Ÿ‡ป|flag_botswana ๐Ÿ‡ง๐Ÿ‡ผ|flag_belarus ๐Ÿ‡ง๐Ÿ‡พ|flag_belize ๐Ÿ‡ง๐Ÿ‡ฟ|flag_canada ๐Ÿ‡จ๐Ÿ‡ฆ|flag_cocos_keeling_islands ๐Ÿ‡จ๐Ÿ‡จ|flag_congo_kinshasa ๐Ÿ‡จ๐Ÿ‡ฉ|flag_central_african_republic ๐Ÿ‡จ๐Ÿ‡ซ|flag_congo_brazzaville ๐Ÿ‡จ๐Ÿ‡ฌ|flag_switzerland ๐Ÿ‡จ๐Ÿ‡ญ|flag_c_te_d_ivoire ๐Ÿ‡จ๐Ÿ‡ฎ|flag_cook_islands ๐Ÿ‡จ๐Ÿ‡ฐ|flag_chile ๐Ÿ‡จ๐Ÿ‡ฑ|flag_cameroon ๐Ÿ‡จ๐Ÿ‡ฒ|flag_china ๐Ÿ‡จ๐Ÿ‡ณ|flag_colombia ๐Ÿ‡จ๐Ÿ‡ด|flag_clipperton_island ๐Ÿ‡จ๐Ÿ‡ต|flag_costa_rica ๐Ÿ‡จ๐Ÿ‡ท|flag_cuba ๐Ÿ‡จ๐Ÿ‡บ|flag_cape_verde ๐Ÿ‡จ๐Ÿ‡ป|flag_cura_ao ๐Ÿ‡จ๐Ÿ‡ผ|flag_christmas_island ๐Ÿ‡จ๐Ÿ‡ฝ|flag_cyprus ๐Ÿ‡จ๐Ÿ‡พ|flag_czechia ๐Ÿ‡จ๐Ÿ‡ฟ|flag_germany ๐Ÿ‡ฉ๐Ÿ‡ช|flag_diego_garcia ๐Ÿ‡ฉ๐Ÿ‡ฌ|flag_djibouti ๐Ÿ‡ฉ๐Ÿ‡ฏ|flag_denmark ๐Ÿ‡ฉ๐Ÿ‡ฐ|flag_dominica ๐Ÿ‡ฉ๐Ÿ‡ฒ|flag_dominican_republic ๐Ÿ‡ฉ๐Ÿ‡ด|flag_algeria ๐Ÿ‡ฉ๐Ÿ‡ฟ|flag_ceuta_melilla ๐Ÿ‡ช๐Ÿ‡ฆ|flag_ecuador ๐Ÿ‡ช๐Ÿ‡จ|flag_estonia ๐Ÿ‡ช๐Ÿ‡ช|flag_egypt ๐Ÿ‡ช๐Ÿ‡ฌ|flag_western_sahara ๐Ÿ‡ช๐Ÿ‡ญ|flag_eritrea ๐Ÿ‡ช๐Ÿ‡ท|flag_spain ๐Ÿ‡ช๐Ÿ‡ธ|flag_ethiopia ๐Ÿ‡ช๐Ÿ‡น|flag_european_union ๐Ÿ‡ช๐Ÿ‡บ|flag_finland ๐Ÿ‡ซ๐Ÿ‡ฎ|flag_fiji ๐Ÿ‡ซ๐Ÿ‡ฏ|flag_falkland_islands ๐Ÿ‡ซ๐Ÿ‡ฐ|flag_micronesia ๐Ÿ‡ซ๐Ÿ‡ฒ|flag_faroe_islands ๐Ÿ‡ซ๐Ÿ‡ด|flag_france ๐Ÿ‡ซ๐Ÿ‡ท|flag_gabon ๐Ÿ‡ฌ๐Ÿ‡ฆ|flag_united_kingdom ๐Ÿ‡ฌ๐Ÿ‡ง|flag_grenada ๐Ÿ‡ฌ๐Ÿ‡ฉ|flag_georgia ๐Ÿ‡ฌ๐Ÿ‡ช|flag_french_guiana ๐Ÿ‡ฌ๐Ÿ‡ซ|flag_guernsey ๐Ÿ‡ฌ๐Ÿ‡ฌ|flag_ghana ๐Ÿ‡ฌ๐Ÿ‡ญ|flag_gibraltar ๐Ÿ‡ฌ๐Ÿ‡ฎ|flag_greenland ๐Ÿ‡ฌ๐Ÿ‡ฑ|flag_gambia ๐Ÿ‡ฌ๐Ÿ‡ฒ|flag_guinea ๐Ÿ‡ฌ๐Ÿ‡ณ|flag_guadeloupe ๐Ÿ‡ฌ๐Ÿ‡ต|flag_equatorial_guinea ๐Ÿ‡ฌ๐Ÿ‡ถ|flag_greece ๐Ÿ‡ฌ๐Ÿ‡ท|flag_south_georgia_south_sandwich_islands ๐Ÿ‡ฌ๐Ÿ‡ธ|flag_guatemala ๐Ÿ‡ฌ๐Ÿ‡น|flag_guam ๐Ÿ‡ฌ๐Ÿ‡บ|flag_guinea_bissau ๐Ÿ‡ฌ๐Ÿ‡ผ|flag_guyana ๐Ÿ‡ฌ๐Ÿ‡พ|flag_hong_kong_sar_china ๐Ÿ‡ญ๐Ÿ‡ฐ|flag_heard_mcdonald_islands ๐Ÿ‡ญ๐Ÿ‡ฒ|flag_honduras ๐Ÿ‡ญ๐Ÿ‡ณ|flag_croatia ๐Ÿ‡ญ๐Ÿ‡ท|flag_haiti ๐Ÿ‡ญ๐Ÿ‡น|flag_hungary ๐Ÿ‡ญ๐Ÿ‡บ|flag_canary_islands ๐Ÿ‡ฎ๐Ÿ‡จ|flag_indonesia ๐Ÿ‡ฎ๐Ÿ‡ฉ|flag_ireland ๐Ÿ‡ฎ๐Ÿ‡ช|flag_israel ๐Ÿ‡ฎ๐Ÿ‡ฑ|flag_isle_of_man ๐Ÿ‡ฎ๐Ÿ‡ฒ|flag_india ๐Ÿ‡ฎ๐Ÿ‡ณ|flag_british_indian_ocean_territory ๐Ÿ‡ฎ๐Ÿ‡ด|flag_iraq ๐Ÿ‡ฎ๐Ÿ‡ถ|flag_iran ๐Ÿ‡ฎ๐Ÿ‡ท|flag_iceland ๐Ÿ‡ฎ๐Ÿ‡ธ|flag_italy ๐Ÿ‡ฎ๐Ÿ‡น|flag_jersey ๐Ÿ‡ฏ๐Ÿ‡ช|flag_jamaica ๐Ÿ‡ฏ๐Ÿ‡ฒ|flag_jordan ๐Ÿ‡ฏ๐Ÿ‡ด|flag_japan ๐Ÿ‡ฏ๐Ÿ‡ต|flag_kenya ๐Ÿ‡ฐ๐Ÿ‡ช|flag_kyrgyzstan ๐Ÿ‡ฐ๐Ÿ‡ฌ|flag_cambodia ๐Ÿ‡ฐ๐Ÿ‡ญ|flag_kiribati ๐Ÿ‡ฐ๐Ÿ‡ฎ|flag_comoros ๐Ÿ‡ฐ๐Ÿ‡ฒ|flag_st_kitts_nevis ๐Ÿ‡ฐ๐Ÿ‡ณ|flag_north_korea ๐Ÿ‡ฐ๐Ÿ‡ต|flag_south_korea ๐Ÿ‡ฐ๐Ÿ‡ท|flag_kuwait ๐Ÿ‡ฐ๐Ÿ‡ผ|flag_cayman_islands ๐Ÿ‡ฐ๐Ÿ‡พ|flag_kazakhstan ๐Ÿ‡ฐ๐Ÿ‡ฟ|flag_laos ๐Ÿ‡ฑ๐Ÿ‡ฆ|flag_lebanon ๐Ÿ‡ฑ๐Ÿ‡ง|flag_st_lucia ๐Ÿ‡ฑ๐Ÿ‡จ|flag_liechtenstein ๐Ÿ‡ฑ๐Ÿ‡ฎ|flag_sri_lanka ๐Ÿ‡ฑ๐Ÿ‡ฐ|flag_liberia ๐Ÿ‡ฑ๐Ÿ‡ท|flag_lesotho ๐Ÿ‡ฑ๐Ÿ‡ธ|flag_lithuania ๐Ÿ‡ฑ๐Ÿ‡น|flag_luxembourg ๐Ÿ‡ฑ๐Ÿ‡บ|flag_latvia ๐Ÿ‡ฑ๐Ÿ‡ป|flag_libya ๐Ÿ‡ฑ๐Ÿ‡พ|flag_morocco ๐Ÿ‡ฒ๐Ÿ‡ฆ|flag_monaco ๐Ÿ‡ฒ๐Ÿ‡จ|flag_moldova ๐Ÿ‡ฒ๐Ÿ‡ฉ|flag_montenegro ๐Ÿ‡ฒ๐Ÿ‡ช|flag_st_martin ๐Ÿ‡ฒ๐Ÿ‡ซ|flag_madagascar ๐Ÿ‡ฒ๐Ÿ‡ฌ|flag_marshall_islands ๐Ÿ‡ฒ๐Ÿ‡ญ|flag_north_macedonia ๐Ÿ‡ฒ๐Ÿ‡ฐ|flag_mali ๐Ÿ‡ฒ๐Ÿ‡ฑ|flag_myanmar_burma_ ๐Ÿ‡ฒ๐Ÿ‡ฒ|flag_mongolia ๐Ÿ‡ฒ๐Ÿ‡ณ|flag_macao_sar_china ๐Ÿ‡ฒ๐Ÿ‡ด|flag_northern_mariana_islands ๐Ÿ‡ฒ๐Ÿ‡ต|flag_martinique ๐Ÿ‡ฒ๐Ÿ‡ถ|flag_mauritania ๐Ÿ‡ฒ๐Ÿ‡ท|flag_montserrat ๐Ÿ‡ฒ๐Ÿ‡ธ|flag_malta ๐Ÿ‡ฒ๐Ÿ‡น|flag_mauritius ๐Ÿ‡ฒ๐Ÿ‡บ|flag_maldives ๐Ÿ‡ฒ๐Ÿ‡ป|flag_malawi ๐Ÿ‡ฒ๐Ÿ‡ผ|flag_mexico ๐Ÿ‡ฒ๐Ÿ‡ฝ|flag_malaysia ๐Ÿ‡ฒ๐Ÿ‡พ|flag_mozambique ๐Ÿ‡ฒ๐Ÿ‡ฟ|flag_namibia ๐Ÿ‡ณ๐Ÿ‡ฆ|flag_new_caledonia ๐Ÿ‡ณ๐Ÿ‡จ|flag_niger ๐Ÿ‡ณ๐Ÿ‡ช|flag_norfolk_island ๐Ÿ‡ณ๐Ÿ‡ซ|flag_nigeria ๐Ÿ‡ณ๐Ÿ‡ฌ|flag_nicaragua ๐Ÿ‡ณ๐Ÿ‡ฎ|flag_netherlands ๐Ÿ‡ณ๐Ÿ‡ฑ|flag_norway ๐Ÿ‡ณ๐Ÿ‡ด|flag_nepal ๐Ÿ‡ณ๐Ÿ‡ต|flag_nauru ๐Ÿ‡ณ๐Ÿ‡ท|flag_niue ๐Ÿ‡ณ๐Ÿ‡บ|flag_new_zealand ๐Ÿ‡ณ๐Ÿ‡ฟ|flag_oman ๐Ÿ‡ด๐Ÿ‡ฒ|flag_panama ๐Ÿ‡ต๐Ÿ‡ฆ|flag_peru ๐Ÿ‡ต๐Ÿ‡ช|flag_french_polynesia ๐Ÿ‡ต๐Ÿ‡ซ|flag_papua_new_guinea ๐Ÿ‡ต๐Ÿ‡ฌ|flag_philippines ๐Ÿ‡ต๐Ÿ‡ญ|flag_pakistan ๐Ÿ‡ต๐Ÿ‡ฐ|flag_poland ๐Ÿ‡ต๐Ÿ‡ฑ|flag_st_pierre_miquelon ๐Ÿ‡ต๐Ÿ‡ฒ|flag_pitcairn_islands ๐Ÿ‡ต๐Ÿ‡ณ|flag_puerto_rico ๐Ÿ‡ต๐Ÿ‡ท|flag_palestinian_territories ๐Ÿ‡ต๐Ÿ‡ธ|flag_portugal ๐Ÿ‡ต๐Ÿ‡น|flag_palau ๐Ÿ‡ต๐Ÿ‡ผ|flag_paraguay ๐Ÿ‡ต๐Ÿ‡พ|flag_qatar ๐Ÿ‡ถ๐Ÿ‡ฆ|flag_r_union ๐Ÿ‡ท๐Ÿ‡ช|flag_romania ๐Ÿ‡ท๐Ÿ‡ด|flag_serbia ๐Ÿ‡ท๐Ÿ‡ธ|flag_russia ๐Ÿ‡ท๐Ÿ‡บ|flag_rwanda ๐Ÿ‡ท๐Ÿ‡ผ|flag_saudi_arabia ๐Ÿ‡ธ๐Ÿ‡ฆ|flag_solomon_islands ๐Ÿ‡ธ๐Ÿ‡ง|flag_seychelles ๐Ÿ‡ธ๐Ÿ‡จ|flag_sudan ๐Ÿ‡ธ๐Ÿ‡ฉ|flag_sweden ๐Ÿ‡ธ๐Ÿ‡ช|flag_singapore ๐Ÿ‡ธ๐Ÿ‡ฌ|flag_st_helena ๐Ÿ‡ธ๐Ÿ‡ญ|flag_slovenia ๐Ÿ‡ธ๐Ÿ‡ฎ|flag_svalbard_jan_mayen ๐Ÿ‡ธ๐Ÿ‡ฏ|flag_slovakia ๐Ÿ‡ธ๐Ÿ‡ฐ|flag_sierra_leone ๐Ÿ‡ธ๐Ÿ‡ฑ|flag_san_marino ๐Ÿ‡ธ๐Ÿ‡ฒ|flag_senegal ๐Ÿ‡ธ๐Ÿ‡ณ|flag_somalia ๐Ÿ‡ธ๐Ÿ‡ด|flag_suriname ๐Ÿ‡ธ๐Ÿ‡ท|flag_south_sudan ๐Ÿ‡ธ๐Ÿ‡ธ|flag_s_o_tom_pr_ncipe ๐Ÿ‡ธ๐Ÿ‡น|flag_el_salvador ๐Ÿ‡ธ๐Ÿ‡ป|flag_sint_maarten ๐Ÿ‡ธ๐Ÿ‡ฝ|flag_syria ๐Ÿ‡ธ๐Ÿ‡พ|flag_eswatini ๐Ÿ‡ธ๐Ÿ‡ฟ|flag_tristan_da_cunha ๐Ÿ‡น๐Ÿ‡ฆ|flag_turks_caicos_islands ๐Ÿ‡น๐Ÿ‡จ|flag_chad ๐Ÿ‡น๐Ÿ‡ฉ|flag_french_southern_territories ๐Ÿ‡น๐Ÿ‡ซ|flag_togo ๐Ÿ‡น๐Ÿ‡ฌ|flag_thailand ๐Ÿ‡น๐Ÿ‡ญ|flag_tajikistan ๐Ÿ‡น๐Ÿ‡ฏ|flag_tokelau ๐Ÿ‡น๐Ÿ‡ฐ|flag_timor_leste ๐Ÿ‡น๐Ÿ‡ฑ|flag_turkmenistan ๐Ÿ‡น๐Ÿ‡ฒ|flag_tunisia ๐Ÿ‡น๐Ÿ‡ณ|flag_tonga ๐Ÿ‡น๐Ÿ‡ด|flag_t_rkiye ๐Ÿ‡น๐Ÿ‡ท|flag_trinidad_tobago ๐Ÿ‡น๐Ÿ‡น|flag_tuvalu ๐Ÿ‡น๐Ÿ‡ป|flag_taiwan ๐Ÿ‡น๐Ÿ‡ผ|flag_tanzania ๐Ÿ‡น๐Ÿ‡ฟ|flag_ukraine ๐Ÿ‡บ๐Ÿ‡ฆ|flag_uganda ๐Ÿ‡บ๐Ÿ‡ฌ|flag_u_s_outlying_islands ๐Ÿ‡บ๐Ÿ‡ฒ|flag_united_nations ๐Ÿ‡บ๐Ÿ‡ณ|flag_united_states ๐Ÿ‡บ๐Ÿ‡ธ|flag_uruguay ๐Ÿ‡บ๐Ÿ‡พ|flag_uzbekistan ๐Ÿ‡บ๐Ÿ‡ฟ|flag_vatican_city ๐Ÿ‡ป๐Ÿ‡ฆ|flag_st_vincent_grenadines ๐Ÿ‡ป๐Ÿ‡จ|flag_venezuela ๐Ÿ‡ป๐Ÿ‡ช|flag_british_virgin_islands ๐Ÿ‡ป๐Ÿ‡ฌ|flag_u_s_virgin_islands ๐Ÿ‡ป๐Ÿ‡ฎ|flag_vietnam ๐Ÿ‡ป๐Ÿ‡ณ|flag_vanuatu ๐Ÿ‡ป๐Ÿ‡บ|flag_wallis_futuna ๐Ÿ‡ผ๐Ÿ‡ซ|flag_samoa ๐Ÿ‡ผ๐Ÿ‡ธ|flag_kosovo ๐Ÿ‡ฝ๐Ÿ‡ฐ|flag_yemen ๐Ÿ‡พ๐Ÿ‡ช|flag_mayotte ๐Ÿ‡พ๐Ÿ‡น|flag_south_africa ๐Ÿ‡ฟ๐Ÿ‡ฆ|flag_zambia ๐Ÿ‡ฟ๐Ÿ‡ฒ|flag_zimbabwe ๐Ÿ‡ฟ๐Ÿ‡ผ|flag_england ๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ|flag_scotland ๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ|flag_wales ๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ" \ No newline at end of file +"grinning_face ๐Ÿ˜€|grinning_face_with_big_eyes ๐Ÿ˜ƒ|grinning_face_with_smiling_eyes ๐Ÿ˜„|beaming_face_with_smiling_eyes ๐Ÿ˜|grinning_squinting_face ๐Ÿ˜†|grinning_face_with_sweat ๐Ÿ˜…|rolling_on_the_floor_laughing ๐Ÿคฃ|face_with_tears_of_joy ๐Ÿ˜‚|slightly_smiling_face ๐Ÿ™‚|upside_down_face ๐Ÿ™ƒ|melting_face ๐Ÿซ |winking_face ๐Ÿ˜‰|smiling_face_with_smiling_eyes ๐Ÿ˜Š|smiling_face_with_halo ๐Ÿ˜‡|smiling_face_with_hearts ๐Ÿฅฐ|smiling_face_with_heart_eyes ๐Ÿ˜|star_struck ๐Ÿคฉ|face_blowing_a_kiss ๐Ÿ˜˜|kissing_face ๐Ÿ˜—|smiling_face โ˜บ๏ธ|smiling_face โ˜บ|kissing_face_with_closed_eyes ๐Ÿ˜š|kissing_face_with_smiling_eyes ๐Ÿ˜™|smiling_face_with_tear ๐Ÿฅฒ|face_savoring_food ๐Ÿ˜‹|face_with_tongue ๐Ÿ˜›|winking_face_with_tongue ๐Ÿ˜œ|zany_face ๐Ÿคช|squinting_face_with_tongue ๐Ÿ˜|money_mouth_face ๐Ÿค‘|smiling_face_with_open_hands ๐Ÿค—|face_with_hand_over_mouth ๐Ÿคญ|face_with_open_eyes_and_hand_over_mouth ๐Ÿซข|face_with_peeking_eye ๐Ÿซฃ|shushing_face ๐Ÿคซ|thinking_face ๐Ÿค”|saluting_face ๐Ÿซก|zipper_mouth_face ๐Ÿค|face_with_raised_eyebrow ๐Ÿคจ|neutral_face ๐Ÿ˜|expressionless_face ๐Ÿ˜‘|face_without_mouth ๐Ÿ˜ถ|dotted_line_face ๐Ÿซฅ|face_in_clouds ๐Ÿ˜ถโ€๐ŸŒซ๏ธ|face_in_clouds ๐Ÿ˜ถโ€๐ŸŒซ|smirking_face ๐Ÿ˜|unamused_face ๐Ÿ˜’|face_with_rolling_eyes ๐Ÿ™„|grimacing_face ๐Ÿ˜ฌ|face_exhaling ๐Ÿ˜ฎโ€๐Ÿ’จ|lying_face ๐Ÿคฅ|shaking_face ๐Ÿซจ|head_shaking_horizontally ๐Ÿ™‚โ€โ†”๏ธ|head_shaking_horizontally ๐Ÿ™‚โ€โ†”|head_shaking_vertically ๐Ÿ™‚โ€โ†•๏ธ|head_shaking_vertically ๐Ÿ™‚โ€โ†•|relieved_face ๐Ÿ˜Œ|pensive_face ๐Ÿ˜”|sleepy_face ๐Ÿ˜ช|drooling_face ๐Ÿคค|sleeping_face ๐Ÿ˜ด|face_with_medical_mask ๐Ÿ˜ท|face_with_thermometer ๐Ÿค’|face_with_head_bandage ๐Ÿค•|nauseated_face ๐Ÿคข|face_vomiting ๐Ÿคฎ|sneezing_face ๐Ÿคง|hot_face ๐Ÿฅต|cold_face ๐Ÿฅถ|woozy_face ๐Ÿฅด|face_with_crossed_out_eyes ๐Ÿ˜ต|face_with_spiral_eyes ๐Ÿ˜ตโ€๐Ÿ’ซ|exploding_head ๐Ÿคฏ|cowboy_hat_face ๐Ÿค |partying_face ๐Ÿฅณ|disguised_face ๐Ÿฅธ|smiling_face_with_sunglasses ๐Ÿ˜Ž|nerd_face ๐Ÿค“|face_with_monocle ๐Ÿง|confused_face ๐Ÿ˜•|face_with_diagonal_mouth ๐Ÿซค|worried_face ๐Ÿ˜Ÿ|slightly_frowning_face ๐Ÿ™|frowning_face โ˜น๏ธ|frowning_face โ˜น|face_with_open_mouth ๐Ÿ˜ฎ|hushed_face ๐Ÿ˜ฏ|astonished_face ๐Ÿ˜ฒ|flushed_face ๐Ÿ˜ณ|pleading_face ๐Ÿฅบ|face_holding_back_tears ๐Ÿฅน|frowning_face_with_open_mouth ๐Ÿ˜ฆ|anguished_face ๐Ÿ˜ง|fearful_face ๐Ÿ˜จ|anxious_face_with_sweat ๐Ÿ˜ฐ|sad_but_relieved_face ๐Ÿ˜ฅ|crying_face ๐Ÿ˜ข|loudly_crying_face ๐Ÿ˜ญ|face_screaming_in_fear ๐Ÿ˜ฑ|confounded_face ๐Ÿ˜–|persevering_face ๐Ÿ˜ฃ|disappointed_face ๐Ÿ˜ž|downcast_face_with_sweat ๐Ÿ˜“|weary_face ๐Ÿ˜ฉ|tired_face ๐Ÿ˜ซ|yawning_face ๐Ÿฅฑ|face_with_steam_from_nose ๐Ÿ˜ค|enraged_face ๐Ÿ˜ก|angry_face ๐Ÿ˜ |face_with_symbols_on_mouth ๐Ÿคฌ|smiling_face_with_horns ๐Ÿ˜ˆ|angry_face_with_horns ๐Ÿ‘ฟ|skull ๐Ÿ’€|skull_and_crossbones โ˜ ๏ธ|skull_and_crossbones โ˜ |pile_of_poo ๐Ÿ’ฉ|clown_face ๐Ÿคก|ogre ๐Ÿ‘น|goblin ๐Ÿ‘บ|ghost ๐Ÿ‘ป|alien ๐Ÿ‘ฝ|alien_monster ๐Ÿ‘พ|robot ๐Ÿค–|grinning_cat ๐Ÿ˜บ|grinning_cat_with_smiling_eyes ๐Ÿ˜ธ|cat_with_tears_of_joy ๐Ÿ˜น|smiling_cat_with_heart_eyes ๐Ÿ˜ป|cat_with_wry_smile ๐Ÿ˜ผ|kissing_cat ๐Ÿ˜ฝ|weary_cat ๐Ÿ™€|crying_cat ๐Ÿ˜ฟ|pouting_cat ๐Ÿ˜พ|see_no_evil_monkey ๐Ÿ™ˆ|hear_no_evil_monkey ๐Ÿ™‰|speak_no_evil_monkey ๐Ÿ™Š|love_letter ๐Ÿ’Œ|heart_with_arrow ๐Ÿ’˜|heart_with_ribbon ๐Ÿ’|sparkling_heart ๐Ÿ’–|growing_heart ๐Ÿ’—|beating_heart ๐Ÿ’“|revolving_hearts ๐Ÿ’ž|two_hearts ๐Ÿ’•|heart_decoration ๐Ÿ’Ÿ|heart_exclamation โฃ๏ธ|heart_exclamation โฃ|broken_heart ๐Ÿ’”|heart_on_fire โค๏ธโ€๐Ÿ”ฅ|heart_on_fire โคโ€๐Ÿ”ฅ|mending_heart โค๏ธโ€๐Ÿฉน|mending_heart โคโ€๐Ÿฉน|red_heart โค๏ธ|red_heart โค|pink_heart ๐Ÿฉท|orange_heart ๐Ÿงก|yellow_heart ๐Ÿ’›|green_heart ๐Ÿ’š|blue_heart ๐Ÿ’™|light_blue_heart ๐Ÿฉต|purple_heart ๐Ÿ’œ|brown_heart ๐ŸคŽ|black_heart ๐Ÿ–ค|grey_heart ๐Ÿฉถ|white_heart ๐Ÿค|kiss_mark ๐Ÿ’‹|hundred_points ๐Ÿ’ฏ|anger_symbol ๐Ÿ’ข|collision ๐Ÿ’ฅ|dizzy ๐Ÿ’ซ|sweat_droplets ๐Ÿ’ฆ|dashing_away ๐Ÿ’จ|hole ๐Ÿ•ณ๏ธ|hole ๐Ÿ•ณ|speech_balloon ๐Ÿ’ฌ|eye_in_speech_bubble ๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ|eye_in_speech_bubble ๐Ÿ‘โ€๐Ÿ—จ๏ธ|eye_in_speech_bubble ๐Ÿ‘๏ธโ€๐Ÿ—จ|eye_in_speech_bubble ๐Ÿ‘โ€๐Ÿ—จ|left_speech_bubble ๐Ÿ—จ๏ธ|left_speech_bubble ๐Ÿ—จ|right_anger_bubble ๐Ÿ—ฏ๏ธ|right_anger_bubble ๐Ÿ—ฏ|thought_balloon ๐Ÿ’ญ|zzz ๐Ÿ’ค|waving_hand ๐Ÿ‘‹|waving_hand_light_skin_tone ๐Ÿ‘‹๐Ÿป|waving_hand_medium_light_skin_tone ๐Ÿ‘‹๐Ÿผ|waving_hand_medium_skin_tone ๐Ÿ‘‹๐Ÿฝ|waving_hand_medium_dark_skin_tone ๐Ÿ‘‹๐Ÿพ|waving_hand_dark_skin_tone ๐Ÿ‘‹๐Ÿฟ|raised_back_of_hand ๐Ÿคš|raised_back_of_hand_light_skin_tone ๐Ÿคš๐Ÿป|raised_back_of_hand_medium_light_skin_tone ๐Ÿคš๐Ÿผ|raised_back_of_hand_medium_skin_tone ๐Ÿคš๐Ÿฝ|raised_back_of_hand_medium_dark_skin_tone ๐Ÿคš๐Ÿพ|raised_back_of_hand_dark_skin_tone ๐Ÿคš๐Ÿฟ|hand_with_fingers_splayed ๐Ÿ–๏ธ|hand_with_fingers_splayed ๐Ÿ–|hand_with_fingers_splayed_light_skin_tone ๐Ÿ–๐Ÿป|hand_with_fingers_splayed_medium_light_skin_tone ๐Ÿ–๐Ÿผ|hand_with_fingers_splayed_medium_skin_tone ๐Ÿ–๐Ÿฝ|hand_with_fingers_splayed_medium_dark_skin_tone ๐Ÿ–๐Ÿพ|hand_with_fingers_splayed_dark_skin_tone ๐Ÿ–๐Ÿฟ|raised_hand โœ‹|raised_hand_light_skin_tone โœ‹๐Ÿป|raised_hand_medium_light_skin_tone โœ‹๐Ÿผ|raised_hand_medium_skin_tone โœ‹๐Ÿฝ|raised_hand_medium_dark_skin_tone โœ‹๐Ÿพ|raised_hand_dark_skin_tone โœ‹๐Ÿฟ|vulcan_salute ๐Ÿ––|vulcan_salute_light_skin_tone ๐Ÿ––๐Ÿป|vulcan_salute_medium_light_skin_tone ๐Ÿ––๐Ÿผ|vulcan_salute_medium_skin_tone ๐Ÿ––๐Ÿฝ|vulcan_salute_medium_dark_skin_tone ๐Ÿ––๐Ÿพ|vulcan_salute_dark_skin_tone ๐Ÿ––๐Ÿฟ|rightwards_hand ๐Ÿซฑ|rightwards_hand_light_skin_tone ๐Ÿซฑ๐Ÿป|rightwards_hand_medium_light_skin_tone ๐Ÿซฑ๐Ÿผ|rightwards_hand_medium_skin_tone ๐Ÿซฑ๐Ÿฝ|rightwards_hand_medium_dark_skin_tone ๐Ÿซฑ๐Ÿพ|rightwards_hand_dark_skin_tone ๐Ÿซฑ๐Ÿฟ|leftwards_hand ๐Ÿซฒ|leftwards_hand_light_skin_tone ๐Ÿซฒ๐Ÿป|leftwards_hand_medium_light_skin_tone ๐Ÿซฒ๐Ÿผ|leftwards_hand_medium_skin_tone ๐Ÿซฒ๐Ÿฝ|leftwards_hand_medium_dark_skin_tone ๐Ÿซฒ๐Ÿพ|leftwards_hand_dark_skin_tone ๐Ÿซฒ๐Ÿฟ|palm_down_hand ๐Ÿซณ|palm_down_hand_light_skin_tone ๐Ÿซณ๐Ÿป|palm_down_hand_medium_light_skin_tone ๐Ÿซณ๐Ÿผ|palm_down_hand_medium_skin_tone ๐Ÿซณ๐Ÿฝ|palm_down_hand_medium_dark_skin_tone ๐Ÿซณ๐Ÿพ|palm_down_hand_dark_skin_tone ๐Ÿซณ๐Ÿฟ|palm_up_hand ๐Ÿซด|palm_up_hand_light_skin_tone ๐Ÿซด๐Ÿป|palm_up_hand_medium_light_skin_tone ๐Ÿซด๐Ÿผ|palm_up_hand_medium_skin_tone ๐Ÿซด๐Ÿฝ|palm_up_hand_medium_dark_skin_tone ๐Ÿซด๐Ÿพ|palm_up_hand_dark_skin_tone ๐Ÿซด๐Ÿฟ|leftwards_pushing_hand ๐Ÿซท|leftwards_pushing_hand_light_skin_tone ๐Ÿซท๐Ÿป|leftwards_pushing_hand_medium_light_skin_tone ๐Ÿซท๐Ÿผ|leftwards_pushing_hand_medium_skin_tone ๐Ÿซท๐Ÿฝ|leftwards_pushing_hand_medium_dark_skin_tone ๐Ÿซท๐Ÿพ|leftwards_pushing_hand_dark_skin_tone ๐Ÿซท๐Ÿฟ|rightwards_pushing_hand ๐Ÿซธ|rightwards_pushing_hand_light_skin_tone ๐Ÿซธ๐Ÿป|rightwards_pushing_hand_medium_light_skin_tone ๐Ÿซธ๐Ÿผ|rightwards_pushing_hand_medium_skin_tone ๐Ÿซธ๐Ÿฝ|rightwards_pushing_hand_medium_dark_skin_tone ๐Ÿซธ๐Ÿพ|rightwards_pushing_hand_dark_skin_tone ๐Ÿซธ๐Ÿฟ|ok_hand ๐Ÿ‘Œ|ok_hand_light_skin_tone ๐Ÿ‘Œ๐Ÿป|ok_hand_medium_light_skin_tone ๐Ÿ‘Œ๐Ÿผ|ok_hand_medium_skin_tone ๐Ÿ‘Œ๐Ÿฝ|ok_hand_medium_dark_skin_tone ๐Ÿ‘Œ๐Ÿพ|ok_hand_dark_skin_tone ๐Ÿ‘Œ๐Ÿฟ|pinched_fingers ๐ŸคŒ|pinched_fingers_light_skin_tone ๐ŸคŒ๐Ÿป|pinched_fingers_medium_light_skin_tone ๐ŸคŒ๐Ÿผ|pinched_fingers_medium_skin_tone ๐ŸคŒ๐Ÿฝ|pinched_fingers_medium_dark_skin_tone ๐ŸคŒ๐Ÿพ|pinched_fingers_dark_skin_tone ๐ŸคŒ๐Ÿฟ|pinching_hand ๐Ÿค|pinching_hand_light_skin_tone ๐Ÿค๐Ÿป|pinching_hand_medium_light_skin_tone ๐Ÿค๐Ÿผ|pinching_hand_medium_skin_tone ๐Ÿค๐Ÿฝ|pinching_hand_medium_dark_skin_tone ๐Ÿค๐Ÿพ|pinching_hand_dark_skin_tone ๐Ÿค๐Ÿฟ|victory_hand โœŒ๏ธ|victory_hand โœŒ|victory_hand_light_skin_tone โœŒ๐Ÿป|victory_hand_medium_light_skin_tone โœŒ๐Ÿผ|victory_hand_medium_skin_tone โœŒ๐Ÿฝ|victory_hand_medium_dark_skin_tone โœŒ๐Ÿพ|victory_hand_dark_skin_tone โœŒ๐Ÿฟ|crossed_fingers ๐Ÿคž|crossed_fingers_light_skin_tone ๐Ÿคž๐Ÿป|crossed_fingers_medium_light_skin_tone ๐Ÿคž๐Ÿผ|crossed_fingers_medium_skin_tone ๐Ÿคž๐Ÿฝ|crossed_fingers_medium_dark_skin_tone ๐Ÿคž๐Ÿพ|crossed_fingers_dark_skin_tone ๐Ÿคž๐Ÿฟ|hand_with_index_finger_and_thumb_crossed ๐Ÿซฐ|hand_with_index_finger_and_thumb_crossed_light_skin_tone ๐Ÿซฐ๐Ÿป|hand_with_index_finger_and_thumb_crossed_medium_light_skin_tone ๐Ÿซฐ๐Ÿผ|hand_with_index_finger_and_thumb_crossed_medium_skin_tone ๐Ÿซฐ๐Ÿฝ|hand_with_index_finger_and_thumb_crossed_medium_dark_skin_tone ๐Ÿซฐ๐Ÿพ|hand_with_index_finger_and_thumb_crossed_dark_skin_tone ๐Ÿซฐ๐Ÿฟ|love_you_gesture ๐ŸคŸ|love_you_gesture_light_skin_tone ๐ŸคŸ๐Ÿป|love_you_gesture_medium_light_skin_tone ๐ŸคŸ๐Ÿผ|love_you_gesture_medium_skin_tone ๐ŸคŸ๐Ÿฝ|love_you_gesture_medium_dark_skin_tone ๐ŸคŸ๐Ÿพ|love_you_gesture_dark_skin_tone ๐ŸคŸ๐Ÿฟ|sign_of_the_horns ๐Ÿค˜|sign_of_the_horns_light_skin_tone ๐Ÿค˜๐Ÿป|sign_of_the_horns_medium_light_skin_tone ๐Ÿค˜๐Ÿผ|sign_of_the_horns_medium_skin_tone ๐Ÿค˜๐Ÿฝ|sign_of_the_horns_medium_dark_skin_tone ๐Ÿค˜๐Ÿพ|sign_of_the_horns_dark_skin_tone ๐Ÿค˜๐Ÿฟ|call_me_hand ๐Ÿค™|call_me_hand_light_skin_tone ๐Ÿค™๐Ÿป|call_me_hand_medium_light_skin_tone ๐Ÿค™๐Ÿผ|call_me_hand_medium_skin_tone ๐Ÿค™๐Ÿฝ|call_me_hand_medium_dark_skin_tone ๐Ÿค™๐Ÿพ|call_me_hand_dark_skin_tone ๐Ÿค™๐Ÿฟ|backhand_index_pointing_left ๐Ÿ‘ˆ|backhand_index_pointing_left_light_skin_tone ๐Ÿ‘ˆ๐Ÿป|backhand_index_pointing_left_medium_light_skin_tone ๐Ÿ‘ˆ๐Ÿผ|backhand_index_pointing_left_medium_skin_tone ๐Ÿ‘ˆ๐Ÿฝ|backhand_index_pointing_left_medium_dark_skin_tone ๐Ÿ‘ˆ๐Ÿพ|backhand_index_pointing_left_dark_skin_tone ๐Ÿ‘ˆ๐Ÿฟ|backhand_index_pointing_right ๐Ÿ‘‰|backhand_index_pointing_right_light_skin_tone ๐Ÿ‘‰๐Ÿป|backhand_index_pointing_right_medium_light_skin_tone ๐Ÿ‘‰๐Ÿผ|backhand_index_pointing_right_medium_skin_tone ๐Ÿ‘‰๐Ÿฝ|backhand_index_pointing_right_medium_dark_skin_tone ๐Ÿ‘‰๐Ÿพ|backhand_index_pointing_right_dark_skin_tone ๐Ÿ‘‰๐Ÿฟ|backhand_index_pointing_up ๐Ÿ‘†|backhand_index_pointing_up_light_skin_tone ๐Ÿ‘†๐Ÿป|backhand_index_pointing_up_medium_light_skin_tone ๐Ÿ‘†๐Ÿผ|backhand_index_pointing_up_medium_skin_tone ๐Ÿ‘†๐Ÿฝ|backhand_index_pointing_up_medium_dark_skin_tone ๐Ÿ‘†๐Ÿพ|backhand_index_pointing_up_dark_skin_tone ๐Ÿ‘†๐Ÿฟ|middle_finger ๐Ÿ–•|middle_finger_light_skin_tone ๐Ÿ–•๐Ÿป|middle_finger_medium_light_skin_tone ๐Ÿ–•๐Ÿผ|middle_finger_medium_skin_tone ๐Ÿ–•๐Ÿฝ|middle_finger_medium_dark_skin_tone ๐Ÿ–•๐Ÿพ|middle_finger_dark_skin_tone ๐Ÿ–•๐Ÿฟ|backhand_index_pointing_down ๐Ÿ‘‡|backhand_index_pointing_down_light_skin_tone ๐Ÿ‘‡๐Ÿป|backhand_index_pointing_down_medium_light_skin_tone ๐Ÿ‘‡๐Ÿผ|backhand_index_pointing_down_medium_skin_tone ๐Ÿ‘‡๐Ÿฝ|backhand_index_pointing_down_medium_dark_skin_tone ๐Ÿ‘‡๐Ÿพ|backhand_index_pointing_down_dark_skin_tone ๐Ÿ‘‡๐Ÿฟ|index_pointing_up โ˜๏ธ|index_pointing_up โ˜|index_pointing_up_light_skin_tone โ˜๐Ÿป|index_pointing_up_medium_light_skin_tone โ˜๐Ÿผ|index_pointing_up_medium_skin_tone โ˜๐Ÿฝ|index_pointing_up_medium_dark_skin_tone โ˜๐Ÿพ|index_pointing_up_dark_skin_tone โ˜๐Ÿฟ|index_pointing_at_the_viewer ๐Ÿซต|index_pointing_at_the_viewer_light_skin_tone ๐Ÿซต๐Ÿป|index_pointing_at_the_viewer_medium_light_skin_tone ๐Ÿซต๐Ÿผ|index_pointing_at_the_viewer_medium_skin_tone ๐Ÿซต๐Ÿฝ|index_pointing_at_the_viewer_medium_dark_skin_tone ๐Ÿซต๐Ÿพ|index_pointing_at_the_viewer_dark_skin_tone ๐Ÿซต๐Ÿฟ|thumbs_up ๐Ÿ‘|thumbs_up_light_skin_tone ๐Ÿ‘๐Ÿป|thumbs_up_medium_light_skin_tone ๐Ÿ‘๐Ÿผ|thumbs_up_medium_skin_tone ๐Ÿ‘๐Ÿฝ|thumbs_up_medium_dark_skin_tone ๐Ÿ‘๐Ÿพ|thumbs_up_dark_skin_tone ๐Ÿ‘๐Ÿฟ|thumbs_down ๐Ÿ‘Ž|thumbs_down_light_skin_tone ๐Ÿ‘Ž๐Ÿป|thumbs_down_medium_light_skin_tone ๐Ÿ‘Ž๐Ÿผ|thumbs_down_medium_skin_tone ๐Ÿ‘Ž๐Ÿฝ|thumbs_down_medium_dark_skin_tone ๐Ÿ‘Ž๐Ÿพ|thumbs_down_dark_skin_tone ๐Ÿ‘Ž๐Ÿฟ|raised_fist โœŠ|raised_fist_light_skin_tone โœŠ๐Ÿป|raised_fist_medium_light_skin_tone โœŠ๐Ÿผ|raised_fist_medium_skin_tone โœŠ๐Ÿฝ|raised_fist_medium_dark_skin_tone โœŠ๐Ÿพ|raised_fist_dark_skin_tone โœŠ๐Ÿฟ|oncoming_fist ๐Ÿ‘Š|oncoming_fist_light_skin_tone ๐Ÿ‘Š๐Ÿป|oncoming_fist_medium_light_skin_tone ๐Ÿ‘Š๐Ÿผ|oncoming_fist_medium_skin_tone ๐Ÿ‘Š๐Ÿฝ|oncoming_fist_medium_dark_skin_tone ๐Ÿ‘Š๐Ÿพ|oncoming_fist_dark_skin_tone ๐Ÿ‘Š๐Ÿฟ|left_facing_fist ๐Ÿค›|left_facing_fist_light_skin_tone ๐Ÿค›๐Ÿป|left_facing_fist_medium_light_skin_tone ๐Ÿค›๐Ÿผ|left_facing_fist_medium_skin_tone ๐Ÿค›๐Ÿฝ|left_facing_fist_medium_dark_skin_tone ๐Ÿค›๐Ÿพ|left_facing_fist_dark_skin_tone ๐Ÿค›๐Ÿฟ|right_facing_fist ๐Ÿคœ|right_facing_fist_light_skin_tone ๐Ÿคœ๐Ÿป|right_facing_fist_medium_light_skin_tone ๐Ÿคœ๐Ÿผ|right_facing_fist_medium_skin_tone ๐Ÿคœ๐Ÿฝ|right_facing_fist_medium_dark_skin_tone ๐Ÿคœ๐Ÿพ|right_facing_fist_dark_skin_tone ๐Ÿคœ๐Ÿฟ|clapping_hands ๐Ÿ‘|clapping_hands_light_skin_tone ๐Ÿ‘๐Ÿป|clapping_hands_medium_light_skin_tone ๐Ÿ‘๐Ÿผ|clapping_hands_medium_skin_tone ๐Ÿ‘๐Ÿฝ|clapping_hands_medium_dark_skin_tone ๐Ÿ‘๐Ÿพ|clapping_hands_dark_skin_tone ๐Ÿ‘๐Ÿฟ|raising_hands ๐Ÿ™Œ|raising_hands_light_skin_tone ๐Ÿ™Œ๐Ÿป|raising_hands_medium_light_skin_tone ๐Ÿ™Œ๐Ÿผ|raising_hands_medium_skin_tone ๐Ÿ™Œ๐Ÿฝ|raising_hands_medium_dark_skin_tone ๐Ÿ™Œ๐Ÿพ|raising_hands_dark_skin_tone ๐Ÿ™Œ๐Ÿฟ|heart_hands ๐Ÿซถ|heart_hands_light_skin_tone ๐Ÿซถ๐Ÿป|heart_hands_medium_light_skin_tone ๐Ÿซถ๐Ÿผ|heart_hands_medium_skin_tone ๐Ÿซถ๐Ÿฝ|heart_hands_medium_dark_skin_tone ๐Ÿซถ๐Ÿพ|heart_hands_dark_skin_tone ๐Ÿซถ๐Ÿฟ|open_hands ๐Ÿ‘|open_hands_light_skin_tone ๐Ÿ‘๐Ÿป|open_hands_medium_light_skin_tone ๐Ÿ‘๐Ÿผ|open_hands_medium_skin_tone ๐Ÿ‘๐Ÿฝ|open_hands_medium_dark_skin_tone ๐Ÿ‘๐Ÿพ|open_hands_dark_skin_tone ๐Ÿ‘๐Ÿฟ|palms_up_together ๐Ÿคฒ|palms_up_together_light_skin_tone ๐Ÿคฒ๐Ÿป|palms_up_together_medium_light_skin_tone ๐Ÿคฒ๐Ÿผ|palms_up_together_medium_skin_tone ๐Ÿคฒ๐Ÿฝ|palms_up_together_medium_dark_skin_tone ๐Ÿคฒ๐Ÿพ|palms_up_together_dark_skin_tone ๐Ÿคฒ๐Ÿฟ|handshake ๐Ÿค|handshake_light_skin_tone ๐Ÿค๐Ÿป|handshake_medium_light_skin_tone ๐Ÿค๐Ÿผ|handshake_medium_skin_tone ๐Ÿค๐Ÿฝ|handshake_medium_dark_skin_tone ๐Ÿค๐Ÿพ|handshake_dark_skin_tone ๐Ÿค๐Ÿฟ|handshake_light_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿผ|handshake_light_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฝ|handshake_light_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿพ|handshake_light_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฟ|handshake_medium_light_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿป|handshake_medium_light_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฝ|handshake_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿพ|handshake_medium_light_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿผโ€๐Ÿซฒ๐Ÿฟ|handshake_medium_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿป|handshake_medium_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿผ|handshake_medium_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿพ|handshake_medium_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿฝโ€๐Ÿซฒ๐Ÿฟ|handshake_medium_dark_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿป|handshake_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿผ|handshake_medium_dark_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฝ|handshake_medium_dark_skin_tone_dark_skin_tone ๐Ÿซฑ๐Ÿพโ€๐Ÿซฒ๐Ÿฟ|handshake_dark_skin_tone_light_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿป|handshake_dark_skin_tone_medium_light_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿผ|handshake_dark_skin_tone_medium_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿฝ|handshake_dark_skin_tone_medium_dark_skin_tone ๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿพ|folded_hands ๐Ÿ™|folded_hands_light_skin_tone ๐Ÿ™๐Ÿป|folded_hands_medium_light_skin_tone ๐Ÿ™๐Ÿผ|folded_hands_medium_skin_tone ๐Ÿ™๐Ÿฝ|folded_hands_medium_dark_skin_tone ๐Ÿ™๐Ÿพ|folded_hands_dark_skin_tone ๐Ÿ™๐Ÿฟ|writing_hand โœ๏ธ|writing_hand โœ|writing_hand_light_skin_tone โœ๐Ÿป|writing_hand_medium_light_skin_tone โœ๐Ÿผ|writing_hand_medium_skin_tone โœ๐Ÿฝ|writing_hand_medium_dark_skin_tone โœ๐Ÿพ|writing_hand_dark_skin_tone โœ๐Ÿฟ|nail_polish ๐Ÿ’…|nail_polish_light_skin_tone ๐Ÿ’…๐Ÿป|nail_polish_medium_light_skin_tone ๐Ÿ’…๐Ÿผ|nail_polish_medium_skin_tone ๐Ÿ’…๐Ÿฝ|nail_polish_medium_dark_skin_tone ๐Ÿ’…๐Ÿพ|nail_polish_dark_skin_tone ๐Ÿ’…๐Ÿฟ|selfie ๐Ÿคณ|selfie_light_skin_tone ๐Ÿคณ๐Ÿป|selfie_medium_light_skin_tone ๐Ÿคณ๐Ÿผ|selfie_medium_skin_tone ๐Ÿคณ๐Ÿฝ|selfie_medium_dark_skin_tone ๐Ÿคณ๐Ÿพ|selfie_dark_skin_tone ๐Ÿคณ๐Ÿฟ|flexed_biceps ๐Ÿ’ช|flexed_biceps_light_skin_tone ๐Ÿ’ช๐Ÿป|flexed_biceps_medium_light_skin_tone ๐Ÿ’ช๐Ÿผ|flexed_biceps_medium_skin_tone ๐Ÿ’ช๐Ÿฝ|flexed_biceps_medium_dark_skin_tone ๐Ÿ’ช๐Ÿพ|flexed_biceps_dark_skin_tone ๐Ÿ’ช๐Ÿฟ|mechanical_arm ๐Ÿฆพ|mechanical_leg ๐Ÿฆฟ|leg ๐Ÿฆต|leg_light_skin_tone ๐Ÿฆต๐Ÿป|leg_medium_light_skin_tone ๐Ÿฆต๐Ÿผ|leg_medium_skin_tone ๐Ÿฆต๐Ÿฝ|leg_medium_dark_skin_tone ๐Ÿฆต๐Ÿพ|leg_dark_skin_tone ๐Ÿฆต๐Ÿฟ|foot ๐Ÿฆถ|foot_light_skin_tone ๐Ÿฆถ๐Ÿป|foot_medium_light_skin_tone ๐Ÿฆถ๐Ÿผ|foot_medium_skin_tone ๐Ÿฆถ๐Ÿฝ|foot_medium_dark_skin_tone ๐Ÿฆถ๐Ÿพ|foot_dark_skin_tone ๐Ÿฆถ๐Ÿฟ|ear ๐Ÿ‘‚|ear_light_skin_tone ๐Ÿ‘‚๐Ÿป|ear_medium_light_skin_tone ๐Ÿ‘‚๐Ÿผ|ear_medium_skin_tone ๐Ÿ‘‚๐Ÿฝ|ear_medium_dark_skin_tone ๐Ÿ‘‚๐Ÿพ|ear_dark_skin_tone ๐Ÿ‘‚๐Ÿฟ|ear_with_hearing_aid ๐Ÿฆป|ear_with_hearing_aid_light_skin_tone ๐Ÿฆป๐Ÿป|ear_with_hearing_aid_medium_light_skin_tone ๐Ÿฆป๐Ÿผ|ear_with_hearing_aid_medium_skin_tone ๐Ÿฆป๐Ÿฝ|ear_with_hearing_aid_medium_dark_skin_tone ๐Ÿฆป๐Ÿพ|ear_with_hearing_aid_dark_skin_tone ๐Ÿฆป๐Ÿฟ|nose ๐Ÿ‘ƒ|nose_light_skin_tone ๐Ÿ‘ƒ๐Ÿป|nose_medium_light_skin_tone ๐Ÿ‘ƒ๐Ÿผ|nose_medium_skin_tone ๐Ÿ‘ƒ๐Ÿฝ|nose_medium_dark_skin_tone ๐Ÿ‘ƒ๐Ÿพ|nose_dark_skin_tone ๐Ÿ‘ƒ๐Ÿฟ|brain ๐Ÿง |anatomical_heart ๐Ÿซ€|lungs ๐Ÿซ|tooth ๐Ÿฆท|bone ๐Ÿฆด|eyes ๐Ÿ‘€|eye ๐Ÿ‘๏ธ|eye ๐Ÿ‘|tongue ๐Ÿ‘…|mouth ๐Ÿ‘„|biting_lip ๐Ÿซฆ|baby ๐Ÿ‘ถ|baby_light_skin_tone ๐Ÿ‘ถ๐Ÿป|baby_medium_light_skin_tone ๐Ÿ‘ถ๐Ÿผ|baby_medium_skin_tone ๐Ÿ‘ถ๐Ÿฝ|baby_medium_dark_skin_tone ๐Ÿ‘ถ๐Ÿพ|baby_dark_skin_tone ๐Ÿ‘ถ๐Ÿฟ|child ๐Ÿง’|child_light_skin_tone ๐Ÿง’๐Ÿป|child_medium_light_skin_tone ๐Ÿง’๐Ÿผ|child_medium_skin_tone ๐Ÿง’๐Ÿฝ|child_medium_dark_skin_tone ๐Ÿง’๐Ÿพ|child_dark_skin_tone ๐Ÿง’๐Ÿฟ|boy ๐Ÿ‘ฆ|boy_light_skin_tone ๐Ÿ‘ฆ๐Ÿป|boy_medium_light_skin_tone ๐Ÿ‘ฆ๐Ÿผ|boy_medium_skin_tone ๐Ÿ‘ฆ๐Ÿฝ|boy_medium_dark_skin_tone ๐Ÿ‘ฆ๐Ÿพ|boy_dark_skin_tone ๐Ÿ‘ฆ๐Ÿฟ|girl ๐Ÿ‘ง|girl_light_skin_tone ๐Ÿ‘ง๐Ÿป|girl_medium_light_skin_tone ๐Ÿ‘ง๐Ÿผ|girl_medium_skin_tone ๐Ÿ‘ง๐Ÿฝ|girl_medium_dark_skin_tone ๐Ÿ‘ง๐Ÿพ|girl_dark_skin_tone ๐Ÿ‘ง๐Ÿฟ|person ๐Ÿง‘|person_light_skin_tone ๐Ÿง‘๐Ÿป|person_medium_light_skin_tone ๐Ÿง‘๐Ÿผ|person_medium_skin_tone ๐Ÿง‘๐Ÿฝ|person_medium_dark_skin_tone ๐Ÿง‘๐Ÿพ|person_dark_skin_tone ๐Ÿง‘๐Ÿฟ|person_blond_hair ๐Ÿ‘ฑ|person_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿป|person_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผ|person_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝ|person_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพ|person_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟ|man ๐Ÿ‘จ|man_light_skin_tone ๐Ÿ‘จ๐Ÿป|man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผ|man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝ|man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพ|man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟ|person_beard ๐Ÿง”|person_light_skin_tone_beard ๐Ÿง”๐Ÿป|person_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผ|person_medium_skin_tone_beard ๐Ÿง”๐Ÿฝ|person_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพ|person_dark_skin_tone_beard ๐Ÿง”๐Ÿฟ|man_beard ๐Ÿง”โ€โ™‚๏ธ|man_beard ๐Ÿง”โ€โ™‚|man_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™‚๏ธ|man_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™‚|man_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™‚๏ธ|man_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™‚|man_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™‚๏ธ|man_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™‚|man_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™‚๏ธ|man_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™‚|man_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™‚๏ธ|man_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™‚|woman_beard ๐Ÿง”โ€โ™€๏ธ|woman_beard ๐Ÿง”โ€โ™€|woman_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™€๏ธ|woman_light_skin_tone_beard ๐Ÿง”๐Ÿปโ€โ™€|woman_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™€๏ธ|woman_medium_light_skin_tone_beard ๐Ÿง”๐Ÿผโ€โ™€|woman_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™€๏ธ|woman_medium_skin_tone_beard ๐Ÿง”๐Ÿฝโ€โ™€|woman_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™€๏ธ|woman_medium_dark_skin_tone_beard ๐Ÿง”๐Ÿพโ€โ™€|woman_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™€๏ธ|woman_dark_skin_tone_beard ๐Ÿง”๐Ÿฟโ€โ™€|man_red_hair ๐Ÿ‘จโ€๐Ÿฆฐ|man_light_skin_tone_red_hair ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฐ|man_medium_light_skin_tone_red_hair ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฐ|man_medium_skin_tone_red_hair ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฐ|man_medium_dark_skin_tone_red_hair ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฐ|man_dark_skin_tone_red_hair ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฐ|man_curly_hair ๐Ÿ‘จโ€๐Ÿฆฑ|man_light_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฑ|man_medium_light_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฑ|man_medium_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฑ|man_medium_dark_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฑ|man_dark_skin_tone_curly_hair ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฑ|man_white_hair ๐Ÿ‘จโ€๐Ÿฆณ|man_light_skin_tone_white_hair ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆณ|man_medium_light_skin_tone_white_hair ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆณ|man_medium_skin_tone_white_hair ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆณ|man_medium_dark_skin_tone_white_hair ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆณ|man_dark_skin_tone_white_hair ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆณ|man_bald ๐Ÿ‘จโ€๐Ÿฆฒ|man_light_skin_tone_bald ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฒ|man_medium_light_skin_tone_bald ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฒ|man_medium_skin_tone_bald ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฒ|man_medium_dark_skin_tone_bald ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฒ|man_dark_skin_tone_bald ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฒ|woman ๐Ÿ‘ฉ|woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿป|woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผ|woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝ|woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพ|woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟ|woman_red_hair ๐Ÿ‘ฉโ€๐Ÿฆฐ|woman_light_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฐ|woman_medium_light_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฐ|woman_medium_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฐ|woman_medium_dark_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฐ|woman_dark_skin_tone_red_hair ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฐ|person_red_hair ๐Ÿง‘โ€๐Ÿฆฐ|person_light_skin_tone_red_hair ๐Ÿง‘๐Ÿปโ€๐Ÿฆฐ|person_medium_light_skin_tone_red_hair ๐Ÿง‘๐Ÿผโ€๐Ÿฆฐ|person_medium_skin_tone_red_hair ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฐ|person_medium_dark_skin_tone_red_hair ๐Ÿง‘๐Ÿพโ€๐Ÿฆฐ|person_dark_skin_tone_red_hair ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฐ|woman_curly_hair ๐Ÿ‘ฉโ€๐Ÿฆฑ|woman_light_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฑ|woman_medium_light_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฑ|woman_medium_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฑ|woman_medium_dark_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฑ|woman_dark_skin_tone_curly_hair ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฑ|person_curly_hair ๐Ÿง‘โ€๐Ÿฆฑ|person_light_skin_tone_curly_hair ๐Ÿง‘๐Ÿปโ€๐Ÿฆฑ|person_medium_light_skin_tone_curly_hair ๐Ÿง‘๐Ÿผโ€๐Ÿฆฑ|person_medium_skin_tone_curly_hair ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฑ|person_medium_dark_skin_tone_curly_hair ๐Ÿง‘๐Ÿพโ€๐Ÿฆฑ|person_dark_skin_tone_curly_hair ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฑ|woman_white_hair ๐Ÿ‘ฉโ€๐Ÿฆณ|woman_light_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆณ|woman_medium_light_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆณ|woman_medium_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆณ|woman_medium_dark_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆณ|woman_dark_skin_tone_white_hair ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆณ|person_white_hair ๐Ÿง‘โ€๐Ÿฆณ|person_light_skin_tone_white_hair ๐Ÿง‘๐Ÿปโ€๐Ÿฆณ|person_medium_light_skin_tone_white_hair ๐Ÿง‘๐Ÿผโ€๐Ÿฆณ|person_medium_skin_tone_white_hair ๐Ÿง‘๐Ÿฝโ€๐Ÿฆณ|person_medium_dark_skin_tone_white_hair ๐Ÿง‘๐Ÿพโ€๐Ÿฆณ|person_dark_skin_tone_white_hair ๐Ÿง‘๐Ÿฟโ€๐Ÿฆณ|woman_bald ๐Ÿ‘ฉโ€๐Ÿฆฒ|woman_light_skin_tone_bald ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฒ|woman_medium_light_skin_tone_bald ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฒ|woman_medium_skin_tone_bald ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฒ|woman_medium_dark_skin_tone_bald ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฒ|woman_dark_skin_tone_bald ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฒ|person_bald ๐Ÿง‘โ€๐Ÿฆฒ|person_light_skin_tone_bald ๐Ÿง‘๐Ÿปโ€๐Ÿฆฒ|person_medium_light_skin_tone_bald ๐Ÿง‘๐Ÿผโ€๐Ÿฆฒ|person_medium_skin_tone_bald ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฒ|person_medium_dark_skin_tone_bald ๐Ÿง‘๐Ÿพโ€๐Ÿฆฒ|person_dark_skin_tone_bald ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฒ|woman_blond_hair ๐Ÿ‘ฑโ€โ™€๏ธ|woman_blond_hair ๐Ÿ‘ฑโ€โ™€|woman_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ|woman_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™€|woman_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™€๏ธ|woman_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™€|woman_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™€๏ธ|woman_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™€|woman_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™€๏ธ|woman_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™€|woman_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ|woman_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™€|man_blond_hair ๐Ÿ‘ฑโ€โ™‚๏ธ|man_blond_hair ๐Ÿ‘ฑโ€โ™‚|man_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™‚๏ธ|man_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿปโ€โ™‚|man_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™‚๏ธ|man_medium_light_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿผโ€โ™‚|man_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™‚๏ธ|man_medium_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฝโ€โ™‚|man_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™‚๏ธ|man_medium_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿพโ€โ™‚|man_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™‚๏ธ|man_dark_skin_tone_blond_hair ๐Ÿ‘ฑ๐Ÿฟโ€โ™‚|older_person ๐Ÿง“|older_person_light_skin_tone ๐Ÿง“๐Ÿป|older_person_medium_light_skin_tone ๐Ÿง“๐Ÿผ|older_person_medium_skin_tone ๐Ÿง“๐Ÿฝ|older_person_medium_dark_skin_tone ๐Ÿง“๐Ÿพ|older_person_dark_skin_tone ๐Ÿง“๐Ÿฟ|old_man ๐Ÿ‘ด|old_man_light_skin_tone ๐Ÿ‘ด๐Ÿป|old_man_medium_light_skin_tone ๐Ÿ‘ด๐Ÿผ|old_man_medium_skin_tone ๐Ÿ‘ด๐Ÿฝ|old_man_medium_dark_skin_tone ๐Ÿ‘ด๐Ÿพ|old_man_dark_skin_tone ๐Ÿ‘ด๐Ÿฟ|old_woman ๐Ÿ‘ต|old_woman_light_skin_tone ๐Ÿ‘ต๐Ÿป|old_woman_medium_light_skin_tone ๐Ÿ‘ต๐Ÿผ|old_woman_medium_skin_tone ๐Ÿ‘ต๐Ÿฝ|old_woman_medium_dark_skin_tone ๐Ÿ‘ต๐Ÿพ|old_woman_dark_skin_tone ๐Ÿ‘ต๐Ÿฟ|person_frowning ๐Ÿ™|person_frowning_light_skin_tone ๐Ÿ™๐Ÿป|person_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผ|person_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝ|person_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพ|person_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟ|man_frowning ๐Ÿ™โ€โ™‚๏ธ|man_frowning ๐Ÿ™โ€โ™‚|man_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™‚๏ธ|man_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™‚|man_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™‚๏ธ|man_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™‚|man_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™‚๏ธ|man_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™‚|man_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™‚๏ธ|man_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™‚|man_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™‚๏ธ|man_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™‚|woman_frowning ๐Ÿ™โ€โ™€๏ธ|woman_frowning ๐Ÿ™โ€โ™€|woman_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™€๏ธ|woman_frowning_light_skin_tone ๐Ÿ™๐Ÿปโ€โ™€|woman_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™€๏ธ|woman_frowning_medium_light_skin_tone ๐Ÿ™๐Ÿผโ€โ™€|woman_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™€๏ธ|woman_frowning_medium_skin_tone ๐Ÿ™๐Ÿฝโ€โ™€|woman_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™€๏ธ|woman_frowning_medium_dark_skin_tone ๐Ÿ™๐Ÿพโ€โ™€|woman_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™€๏ธ|woman_frowning_dark_skin_tone ๐Ÿ™๐Ÿฟโ€โ™€|person_pouting ๐Ÿ™Ž|person_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿป|person_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผ|person_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝ|person_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพ|person_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟ|man_pouting ๐Ÿ™Žโ€โ™‚๏ธ|man_pouting ๐Ÿ™Žโ€โ™‚|man_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™‚๏ธ|man_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™‚|man_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™‚๏ธ|man_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™‚|man_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™‚๏ธ|man_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™‚|man_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™‚๏ธ|man_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™‚|man_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™‚๏ธ|man_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™‚|woman_pouting ๐Ÿ™Žโ€โ™€๏ธ|woman_pouting ๐Ÿ™Žโ€โ™€|woman_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™€๏ธ|woman_pouting_light_skin_tone ๐Ÿ™Ž๐Ÿปโ€โ™€|woman_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™€๏ธ|woman_pouting_medium_light_skin_tone ๐Ÿ™Ž๐Ÿผโ€โ™€|woman_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™€๏ธ|woman_pouting_medium_skin_tone ๐Ÿ™Ž๐Ÿฝโ€โ™€|woman_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™€๏ธ|woman_pouting_medium_dark_skin_tone ๐Ÿ™Ž๐Ÿพโ€โ™€|woman_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™€๏ธ|woman_pouting_dark_skin_tone ๐Ÿ™Ž๐Ÿฟโ€โ™€|person_gesturing_no ๐Ÿ™…|person_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿป|person_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผ|person_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝ|person_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพ|person_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟ|man_gesturing_no ๐Ÿ™…โ€โ™‚๏ธ|man_gesturing_no ๐Ÿ™…โ€โ™‚|man_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™‚๏ธ|man_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™‚|man_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™‚๏ธ|man_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™‚|man_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™‚๏ธ|man_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™‚|man_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™‚๏ธ|man_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™‚|man_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™‚๏ธ|man_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™‚|woman_gesturing_no ๐Ÿ™…โ€โ™€๏ธ|woman_gesturing_no ๐Ÿ™…โ€โ™€|woman_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™€๏ธ|woman_gesturing_no_light_skin_tone ๐Ÿ™…๐Ÿปโ€โ™€|woman_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™€๏ธ|woman_gesturing_no_medium_light_skin_tone ๐Ÿ™…๐Ÿผโ€โ™€|woman_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™€๏ธ|woman_gesturing_no_medium_skin_tone ๐Ÿ™…๐Ÿฝโ€โ™€|woman_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™€๏ธ|woman_gesturing_no_medium_dark_skin_tone ๐Ÿ™…๐Ÿพโ€โ™€|woman_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™€๏ธ|woman_gesturing_no_dark_skin_tone ๐Ÿ™…๐Ÿฟโ€โ™€|person_gesturing_ok ๐Ÿ™†|person_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿป|person_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผ|person_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝ|person_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพ|person_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟ|man_gesturing_ok ๐Ÿ™†โ€โ™‚๏ธ|man_gesturing_ok ๐Ÿ™†โ€โ™‚|man_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™‚๏ธ|man_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™‚|man_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™‚๏ธ|man_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™‚|man_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™‚๏ธ|man_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™‚|man_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™‚๏ธ|man_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™‚|man_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™‚๏ธ|man_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™‚|woman_gesturing_ok ๐Ÿ™†โ€โ™€๏ธ|woman_gesturing_ok ๐Ÿ™†โ€โ™€|woman_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™€๏ธ|woman_gesturing_ok_light_skin_tone ๐Ÿ™†๐Ÿปโ€โ™€|woman_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™€๏ธ|woman_gesturing_ok_medium_light_skin_tone ๐Ÿ™†๐Ÿผโ€โ™€|woman_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™€๏ธ|woman_gesturing_ok_medium_skin_tone ๐Ÿ™†๐Ÿฝโ€โ™€|woman_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™€๏ธ|woman_gesturing_ok_medium_dark_skin_tone ๐Ÿ™†๐Ÿพโ€โ™€|woman_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™€๏ธ|woman_gesturing_ok_dark_skin_tone ๐Ÿ™†๐Ÿฟโ€โ™€|person_tipping_hand ๐Ÿ’|person_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿป|person_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผ|person_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝ|person_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพ|person_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟ|man_tipping_hand ๐Ÿ’โ€โ™‚๏ธ|man_tipping_hand ๐Ÿ’โ€โ™‚|man_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™‚๏ธ|man_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™‚|man_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™‚๏ธ|man_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™‚|man_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™‚๏ธ|man_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™‚|man_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™‚๏ธ|man_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™‚|man_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™‚๏ธ|man_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™‚|woman_tipping_hand ๐Ÿ’โ€โ™€๏ธ|woman_tipping_hand ๐Ÿ’โ€โ™€|woman_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™€๏ธ|woman_tipping_hand_light_skin_tone ๐Ÿ’๐Ÿปโ€โ™€|woman_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™€๏ธ|woman_tipping_hand_medium_light_skin_tone ๐Ÿ’๐Ÿผโ€โ™€|woman_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™€๏ธ|woman_tipping_hand_medium_skin_tone ๐Ÿ’๐Ÿฝโ€โ™€|woman_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™€๏ธ|woman_tipping_hand_medium_dark_skin_tone ๐Ÿ’๐Ÿพโ€โ™€|woman_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™€๏ธ|woman_tipping_hand_dark_skin_tone ๐Ÿ’๐Ÿฟโ€โ™€|person_raising_hand ๐Ÿ™‹|person_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿป|person_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผ|person_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝ|person_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพ|person_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟ|man_raising_hand ๐Ÿ™‹โ€โ™‚๏ธ|man_raising_hand ๐Ÿ™‹โ€โ™‚|man_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ|man_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™‚|man_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™‚๏ธ|man_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™‚|man_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™‚๏ธ|man_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™‚|man_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™‚๏ธ|man_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™‚|man_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™‚๏ธ|man_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™‚|woman_raising_hand ๐Ÿ™‹โ€โ™€๏ธ|woman_raising_hand ๐Ÿ™‹โ€โ™€|woman_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™€๏ธ|woman_raising_hand_light_skin_tone ๐Ÿ™‹๐Ÿปโ€โ™€|woman_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™€๏ธ|woman_raising_hand_medium_light_skin_tone ๐Ÿ™‹๐Ÿผโ€โ™€|woman_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™€๏ธ|woman_raising_hand_medium_skin_tone ๐Ÿ™‹๐Ÿฝโ€โ™€|woman_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™€๏ธ|woman_raising_hand_medium_dark_skin_tone ๐Ÿ™‹๐Ÿพโ€โ™€|woman_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™€๏ธ|woman_raising_hand_dark_skin_tone ๐Ÿ™‹๐Ÿฟโ€โ™€|deaf_person ๐Ÿง|deaf_person_light_skin_tone ๐Ÿง๐Ÿป|deaf_person_medium_light_skin_tone ๐Ÿง๐Ÿผ|deaf_person_medium_skin_tone ๐Ÿง๐Ÿฝ|deaf_person_medium_dark_skin_tone ๐Ÿง๐Ÿพ|deaf_person_dark_skin_tone ๐Ÿง๐Ÿฟ|deaf_man ๐Ÿงโ€โ™‚๏ธ|deaf_man ๐Ÿงโ€โ™‚|deaf_man_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚๏ธ|deaf_man_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚|deaf_man_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚๏ธ|deaf_man_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚|deaf_man_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚๏ธ|deaf_man_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚|deaf_man_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚๏ธ|deaf_man_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚|deaf_man_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚๏ธ|deaf_man_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚|deaf_woman ๐Ÿงโ€โ™€๏ธ|deaf_woman ๐Ÿงโ€โ™€|deaf_woman_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€๏ธ|deaf_woman_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€|deaf_woman_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€๏ธ|deaf_woman_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€|deaf_woman_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€๏ธ|deaf_woman_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€|deaf_woman_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€๏ธ|deaf_woman_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€|deaf_woman_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€๏ธ|deaf_woman_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€|person_bowing ๐Ÿ™‡|person_bowing_light_skin_tone ๐Ÿ™‡๐Ÿป|person_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผ|person_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝ|person_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพ|person_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟ|man_bowing ๐Ÿ™‡โ€โ™‚๏ธ|man_bowing ๐Ÿ™‡โ€โ™‚|man_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™‚๏ธ|man_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™‚|man_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™‚๏ธ|man_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™‚|man_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™‚๏ธ|man_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™‚|man_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™‚๏ธ|man_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™‚|man_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™‚๏ธ|man_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™‚|woman_bowing ๐Ÿ™‡โ€โ™€๏ธ|woman_bowing ๐Ÿ™‡โ€โ™€|woman_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™€๏ธ|woman_bowing_light_skin_tone ๐Ÿ™‡๐Ÿปโ€โ™€|woman_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™€๏ธ|woman_bowing_medium_light_skin_tone ๐Ÿ™‡๐Ÿผโ€โ™€|woman_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™€๏ธ|woman_bowing_medium_skin_tone ๐Ÿ™‡๐Ÿฝโ€โ™€|woman_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™€๏ธ|woman_bowing_medium_dark_skin_tone ๐Ÿ™‡๐Ÿพโ€โ™€|woman_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™€๏ธ|woman_bowing_dark_skin_tone ๐Ÿ™‡๐Ÿฟโ€โ™€|person_facepalming ๐Ÿคฆ|person_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿป|person_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผ|person_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝ|person_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพ|person_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟ|man_facepalming ๐Ÿคฆโ€โ™‚๏ธ|man_facepalming ๐Ÿคฆโ€โ™‚|man_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ|man_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™‚|man_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™‚๏ธ|man_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™‚|man_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™‚๏ธ|man_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™‚|man_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™‚๏ธ|man_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™‚|man_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™‚๏ธ|man_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™‚|woman_facepalming ๐Ÿคฆโ€โ™€๏ธ|woman_facepalming ๐Ÿคฆโ€โ™€|woman_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™€๏ธ|woman_facepalming_light_skin_tone ๐Ÿคฆ๐Ÿปโ€โ™€|woman_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™€๏ธ|woman_facepalming_medium_light_skin_tone ๐Ÿคฆ๐Ÿผโ€โ™€|woman_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™€๏ธ|woman_facepalming_medium_skin_tone ๐Ÿคฆ๐Ÿฝโ€โ™€|woman_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™€๏ธ|woman_facepalming_medium_dark_skin_tone ๐Ÿคฆ๐Ÿพโ€โ™€|woman_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™€๏ธ|woman_facepalming_dark_skin_tone ๐Ÿคฆ๐Ÿฟโ€โ™€|person_shrugging ๐Ÿคท|person_shrugging_light_skin_tone ๐Ÿคท๐Ÿป|person_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผ|person_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝ|person_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพ|person_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟ|man_shrugging ๐Ÿคทโ€โ™‚๏ธ|man_shrugging ๐Ÿคทโ€โ™‚|man_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™‚๏ธ|man_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™‚|man_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™‚๏ธ|man_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™‚|man_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™‚๏ธ|man_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™‚|man_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™‚๏ธ|man_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™‚|man_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™‚๏ธ|man_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™‚|woman_shrugging ๐Ÿคทโ€โ™€๏ธ|woman_shrugging ๐Ÿคทโ€โ™€|woman_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™€๏ธ|woman_shrugging_light_skin_tone ๐Ÿคท๐Ÿปโ€โ™€|woman_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™€๏ธ|woman_shrugging_medium_light_skin_tone ๐Ÿคท๐Ÿผโ€โ™€|woman_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™€๏ธ|woman_shrugging_medium_skin_tone ๐Ÿคท๐Ÿฝโ€โ™€|woman_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™€๏ธ|woman_shrugging_medium_dark_skin_tone ๐Ÿคท๐Ÿพโ€โ™€|woman_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™€๏ธ|woman_shrugging_dark_skin_tone ๐Ÿคท๐Ÿฟโ€โ™€|health_worker ๐Ÿง‘โ€โš•๏ธ|health_worker ๐Ÿง‘โ€โš•|health_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš•๏ธ|health_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš•|health_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš•๏ธ|health_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš•|health_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš•๏ธ|health_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš•|health_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš•๏ธ|health_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš•|health_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš•๏ธ|health_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš•|man_health_worker ๐Ÿ‘จโ€โš•๏ธ|man_health_worker ๐Ÿ‘จโ€โš•|man_health_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš•๏ธ|man_health_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš•|man_health_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš•๏ธ|man_health_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš•|man_health_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš•๏ธ|man_health_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš•|man_health_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš•๏ธ|man_health_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš•|man_health_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš•๏ธ|man_health_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš•|woman_health_worker ๐Ÿ‘ฉโ€โš•๏ธ|woman_health_worker ๐Ÿ‘ฉโ€โš•|woman_health_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš•๏ธ|woman_health_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš•|woman_health_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš•๏ธ|woman_health_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš•|woman_health_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš•๏ธ|woman_health_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš•|woman_health_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš•๏ธ|woman_health_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš•|woman_health_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ|woman_health_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš•|student ๐Ÿง‘โ€๐ŸŽ“|student_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽ“|student_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽ“|student_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽ“|student_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽ“|student_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽ“|man_student ๐Ÿ‘จโ€๐ŸŽ“|man_student_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŽ“|man_student_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŽ“|man_student_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽ“|man_student_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŽ“|man_student_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽ“|woman_student ๐Ÿ‘ฉโ€๐ŸŽ“|woman_student_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽ“|woman_student_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽ“|woman_student_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽ“|woman_student_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽ“|woman_student_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽ“|teacher ๐Ÿง‘โ€๐Ÿซ|teacher_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿซ|teacher_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿซ|teacher_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿซ|teacher_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿซ|teacher_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿซ|man_teacher ๐Ÿ‘จโ€๐Ÿซ|man_teacher_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿซ|man_teacher_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿซ|man_teacher_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ|man_teacher_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿซ|man_teacher_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿซ|woman_teacher ๐Ÿ‘ฉโ€๐Ÿซ|woman_teacher_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ|woman_teacher_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿซ|woman_teacher_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ|woman_teacher_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿซ|woman_teacher_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿซ|judge ๐Ÿง‘โ€โš–๏ธ|judge ๐Ÿง‘โ€โš–|judge_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš–๏ธ|judge_light_skin_tone ๐Ÿง‘๐Ÿปโ€โš–|judge_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš–๏ธ|judge_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โš–|judge_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš–๏ธ|judge_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โš–|judge_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš–๏ธ|judge_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โš–|judge_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš–๏ธ|judge_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โš–|man_judge ๐Ÿ‘จโ€โš–๏ธ|man_judge ๐Ÿ‘จโ€โš–|man_judge_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš–๏ธ|man_judge_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โš–|man_judge_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš–๏ธ|man_judge_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โš–|man_judge_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš–๏ธ|man_judge_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โš–|man_judge_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš–๏ธ|man_judge_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โš–|man_judge_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš–๏ธ|man_judge_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โš–|woman_judge ๐Ÿ‘ฉโ€โš–๏ธ|woman_judge ๐Ÿ‘ฉโ€โš–|woman_judge_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš–๏ธ|woman_judge_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โš–|woman_judge_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš–๏ธ|woman_judge_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โš–|woman_judge_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš–๏ธ|woman_judge_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โš–|woman_judge_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš–๏ธ|woman_judge_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โš–|woman_judge_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš–๏ธ|woman_judge_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โš–|farmer ๐Ÿง‘โ€๐ŸŒพ|farmer_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŒพ|farmer_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŒพ|farmer_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŒพ|farmer_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŒพ|farmer_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŒพ|man_farmer ๐Ÿ‘จโ€๐ŸŒพ|man_farmer_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŒพ|man_farmer_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŒพ|man_farmer_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŒพ|man_farmer_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŒพ|man_farmer_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŒพ|woman_farmer ๐Ÿ‘ฉโ€๐ŸŒพ|woman_farmer_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŒพ|woman_farmer_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŒพ|woman_farmer_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŒพ|woman_farmer_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŒพ|woman_farmer_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŒพ|cook ๐Ÿง‘โ€๐Ÿณ|cook_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿณ|cook_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿณ|cook_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿณ|cook_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿณ|cook_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿณ|man_cook ๐Ÿ‘จโ€๐Ÿณ|man_cook_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿณ|man_cook_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿณ|man_cook_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿณ|man_cook_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿณ|man_cook_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿณ|woman_cook ๐Ÿ‘ฉโ€๐Ÿณ|woman_cook_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿณ|woman_cook_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿณ|woman_cook_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿณ|woman_cook_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿณ|woman_cook_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿณ|mechanic ๐Ÿง‘โ€๐Ÿ”ง|mechanic_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ”ง|mechanic_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ”ง|mechanic_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ”ง|mechanic_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ”ง|mechanic_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ”ง|man_mechanic ๐Ÿ‘จโ€๐Ÿ”ง|man_mechanic_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ง|man_mechanic_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ง|man_mechanic_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ง|man_mechanic_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ง|man_mechanic_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ง|woman_mechanic ๐Ÿ‘ฉโ€๐Ÿ”ง|woman_mechanic_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ง|woman_mechanic_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ง|woman_mechanic_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ง|woman_mechanic_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ง|woman_mechanic_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ง|factory_worker ๐Ÿง‘โ€๐Ÿญ|factory_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿญ|factory_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿญ|factory_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿญ|factory_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿญ|factory_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿญ|man_factory_worker ๐Ÿ‘จโ€๐Ÿญ|man_factory_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿญ|man_factory_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿญ|man_factory_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿญ|man_factory_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿญ|man_factory_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿญ|woman_factory_worker ๐Ÿ‘ฉโ€๐Ÿญ|woman_factory_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿญ|woman_factory_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿญ|woman_factory_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿญ|woman_factory_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿญ|woman_factory_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿญ|office_worker ๐Ÿง‘โ€๐Ÿ’ผ|office_worker_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ’ผ|office_worker_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ’ผ|office_worker_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ’ผ|office_worker_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ’ผ|office_worker_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ’ผ|man_office_worker ๐Ÿ‘จโ€๐Ÿ’ผ|man_office_worker_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ|man_office_worker_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ผ|man_office_worker_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ผ|man_office_worker_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ผ|man_office_worker_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ผ|woman_office_worker ๐Ÿ‘ฉโ€๐Ÿ’ผ|woman_office_worker_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ผ|woman_office_worker_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ผ|woman_office_worker_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ผ|woman_office_worker_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ผ|woman_office_worker_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ผ|scientist ๐Ÿง‘โ€๐Ÿ”ฌ|scientist_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ”ฌ|scientist_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ”ฌ|scientist_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ”ฌ|scientist_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ”ฌ|scientist_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ”ฌ|man_scientist ๐Ÿ‘จโ€๐Ÿ”ฌ|man_scientist_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ฌ|man_scientist_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ฌ|man_scientist_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ฌ|man_scientist_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ฌ|man_scientist_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ฌ|woman_scientist ๐Ÿ‘ฉโ€๐Ÿ”ฌ|woman_scientist_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ฌ|woman_scientist_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ|woman_scientist_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ฌ|woman_scientist_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ฌ|woman_scientist_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ฌ|technologist ๐Ÿง‘โ€๐Ÿ’ป|technologist_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป|technologist_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿ’ป|technologist_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿ’ป|technologist_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿ’ป|technologist_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿ’ป|man_technologist ๐Ÿ‘จโ€๐Ÿ’ป|man_technologist_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป|man_technologist_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป|man_technologist_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป|man_technologist_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป|man_technologist_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ป|woman_technologist ๐Ÿ‘ฉโ€๐Ÿ’ป|woman_technologist_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป|woman_technologist_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ป|woman_technologist_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป|woman_technologist_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ป|woman_technologist_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ป|singer ๐Ÿง‘โ€๐ŸŽค|singer_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽค|singer_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽค|singer_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽค|singer_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽค|singer_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽค|man_singer ๐Ÿ‘จโ€๐ŸŽค|man_singer_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŽค|man_singer_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŽค|man_singer_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽค|man_singer_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŽค|man_singer_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽค|woman_singer ๐Ÿ‘ฉโ€๐ŸŽค|woman_singer_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽค|woman_singer_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค|woman_singer_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽค|woman_singer_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽค|woman_singer_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค|artist ๐Ÿง‘โ€๐ŸŽจ|artist_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽจ|artist_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽจ|artist_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽจ|artist_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽจ|artist_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽจ|man_artist ๐Ÿ‘จโ€๐ŸŽจ|man_artist_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐ŸŽจ|man_artist_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐ŸŽจ|man_artist_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽจ|man_artist_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐ŸŽจ|man_artist_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽจ|woman_artist ๐Ÿ‘ฉโ€๐ŸŽจ|woman_artist_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽจ|woman_artist_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽจ|woman_artist_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽจ|woman_artist_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽจ|woman_artist_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽจ|pilot ๐Ÿง‘โ€โœˆ๏ธ|pilot ๐Ÿง‘โ€โœˆ|pilot_light_skin_tone ๐Ÿง‘๐Ÿปโ€โœˆ๏ธ|pilot_light_skin_tone ๐Ÿง‘๐Ÿปโ€โœˆ|pilot_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โœˆ๏ธ|pilot_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€โœˆ|pilot_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โœˆ๏ธ|pilot_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€โœˆ|pilot_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โœˆ๏ธ|pilot_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โœˆ|pilot_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โœˆ๏ธ|pilot_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โœˆ|man_pilot ๐Ÿ‘จโ€โœˆ๏ธ|man_pilot ๐Ÿ‘จโ€โœˆ|man_pilot_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โœˆ๏ธ|man_pilot_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โœˆ|man_pilot_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โœˆ๏ธ|man_pilot_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โœˆ|man_pilot_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โœˆ๏ธ|man_pilot_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โœˆ|man_pilot_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โœˆ๏ธ|man_pilot_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โœˆ|man_pilot_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โœˆ๏ธ|man_pilot_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โœˆ|woman_pilot ๐Ÿ‘ฉโ€โœˆ๏ธ|woman_pilot ๐Ÿ‘ฉโ€โœˆ|woman_pilot_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โœˆ๏ธ|woman_pilot_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โœˆ|woman_pilot_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โœˆ๏ธ|woman_pilot_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โœˆ|woman_pilot_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โœˆ๏ธ|woman_pilot_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โœˆ|woman_pilot_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โœˆ๏ธ|woman_pilot_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โœˆ|woman_pilot_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โœˆ๏ธ|woman_pilot_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โœˆ|astronaut ๐Ÿง‘โ€๐Ÿš€|astronaut_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿš€|astronaut_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿš€|astronaut_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿš€|astronaut_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿš€|astronaut_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿš€|man_astronaut ๐Ÿ‘จโ€๐Ÿš€|man_astronaut_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿš€|man_astronaut_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿš€|man_astronaut_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš€|man_astronaut_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿš€|man_astronaut_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš€|woman_astronaut ๐Ÿ‘ฉโ€๐Ÿš€|woman_astronaut_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš€|woman_astronaut_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš€|woman_astronaut_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš€|woman_astronaut_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš€|woman_astronaut_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš€|firefighter ๐Ÿง‘โ€๐Ÿš’|firefighter_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿš’|firefighter_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿš’|firefighter_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿš’|firefighter_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿš’|firefighter_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿš’|man_firefighter ๐Ÿ‘จโ€๐Ÿš’|man_firefighter_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿš’|man_firefighter_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿš’|man_firefighter_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš’|man_firefighter_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿš’|man_firefighter_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš’|woman_firefighter ๐Ÿ‘ฉโ€๐Ÿš’|woman_firefighter_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš’|woman_firefighter_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš’|woman_firefighter_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’|woman_firefighter_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš’|woman_firefighter_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš’|police_officer ๐Ÿ‘ฎ|police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿป|police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผ|police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝ|police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพ|police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟ|man_police_officer ๐Ÿ‘ฎโ€โ™‚๏ธ|man_police_officer ๐Ÿ‘ฎโ€โ™‚|man_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™‚๏ธ|man_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™‚|man_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™‚๏ธ|man_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™‚|man_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™‚๏ธ|man_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™‚|man_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™‚๏ธ|man_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™‚|man_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™‚๏ธ|man_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™‚|woman_police_officer ๐Ÿ‘ฎโ€โ™€๏ธ|woman_police_officer ๐Ÿ‘ฎโ€โ™€|woman_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™€๏ธ|woman_police_officer_light_skin_tone ๐Ÿ‘ฎ๐Ÿปโ€โ™€|woman_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™€๏ธ|woman_police_officer_medium_light_skin_tone ๐Ÿ‘ฎ๐Ÿผโ€โ™€|woman_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™€๏ธ|woman_police_officer_medium_skin_tone ๐Ÿ‘ฎ๐Ÿฝโ€โ™€|woman_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™€๏ธ|woman_police_officer_medium_dark_skin_tone ๐Ÿ‘ฎ๐Ÿพโ€โ™€|woman_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™€๏ธ|woman_police_officer_dark_skin_tone ๐Ÿ‘ฎ๐Ÿฟโ€โ™€|detective ๐Ÿ•ต๏ธ|detective ๐Ÿ•ต|detective_light_skin_tone ๐Ÿ•ต๐Ÿป|detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผ|detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝ|detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพ|detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟ|man_detective ๐Ÿ•ต๏ธโ€โ™‚๏ธ|man_detective ๐Ÿ•ตโ€โ™‚๏ธ|man_detective ๐Ÿ•ต๏ธโ€โ™‚|man_detective ๐Ÿ•ตโ€โ™‚|man_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ|man_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™‚|man_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™‚๏ธ|man_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™‚|man_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™‚๏ธ|man_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™‚|man_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™‚๏ธ|man_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™‚|man_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™‚๏ธ|man_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™‚|woman_detective ๐Ÿ•ต๏ธโ€โ™€๏ธ|woman_detective ๐Ÿ•ตโ€โ™€๏ธ|woman_detective ๐Ÿ•ต๏ธโ€โ™€|woman_detective ๐Ÿ•ตโ€โ™€|woman_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™€๏ธ|woman_detective_light_skin_tone ๐Ÿ•ต๐Ÿปโ€โ™€|woman_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™€๏ธ|woman_detective_medium_light_skin_tone ๐Ÿ•ต๐Ÿผโ€โ™€|woman_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™€๏ธ|woman_detective_medium_skin_tone ๐Ÿ•ต๐Ÿฝโ€โ™€|woman_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™€๏ธ|woman_detective_medium_dark_skin_tone ๐Ÿ•ต๐Ÿพโ€โ™€|woman_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™€๏ธ|woman_detective_dark_skin_tone ๐Ÿ•ต๐Ÿฟโ€โ™€|guard ๐Ÿ’‚|guard_light_skin_tone ๐Ÿ’‚๐Ÿป|guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผ|guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝ|guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพ|guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟ|man_guard ๐Ÿ’‚โ€โ™‚๏ธ|man_guard ๐Ÿ’‚โ€โ™‚|man_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™‚๏ธ|man_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™‚|man_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™‚๏ธ|man_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™‚|man_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™‚๏ธ|man_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™‚|man_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™‚๏ธ|man_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™‚|man_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™‚๏ธ|man_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™‚|woman_guard ๐Ÿ’‚โ€โ™€๏ธ|woman_guard ๐Ÿ’‚โ€โ™€|woman_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™€๏ธ|woman_guard_light_skin_tone ๐Ÿ’‚๐Ÿปโ€โ™€|woman_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™€๏ธ|woman_guard_medium_light_skin_tone ๐Ÿ’‚๐Ÿผโ€โ™€|woman_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™€๏ธ|woman_guard_medium_skin_tone ๐Ÿ’‚๐Ÿฝโ€โ™€|woman_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™€๏ธ|woman_guard_medium_dark_skin_tone ๐Ÿ’‚๐Ÿพโ€โ™€|woman_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™€๏ธ|woman_guard_dark_skin_tone ๐Ÿ’‚๐Ÿฟโ€โ™€|ninja ๐Ÿฅท|ninja_light_skin_tone ๐Ÿฅท๐Ÿป|ninja_medium_light_skin_tone ๐Ÿฅท๐Ÿผ|ninja_medium_skin_tone ๐Ÿฅท๐Ÿฝ|ninja_medium_dark_skin_tone ๐Ÿฅท๐Ÿพ|ninja_dark_skin_tone ๐Ÿฅท๐Ÿฟ|construction_worker ๐Ÿ‘ท|construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿป|construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผ|construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝ|construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพ|construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟ|man_construction_worker ๐Ÿ‘ทโ€โ™‚๏ธ|man_construction_worker ๐Ÿ‘ทโ€โ™‚|man_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™‚๏ธ|man_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™‚|man_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™‚๏ธ|man_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™‚|man_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™‚๏ธ|man_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™‚|man_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™‚๏ธ|man_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™‚|man_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™‚๏ธ|man_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™‚|woman_construction_worker ๐Ÿ‘ทโ€โ™€๏ธ|woman_construction_worker ๐Ÿ‘ทโ€โ™€|woman_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™€๏ธ|woman_construction_worker_light_skin_tone ๐Ÿ‘ท๐Ÿปโ€โ™€|woman_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ|woman_construction_worker_medium_light_skin_tone ๐Ÿ‘ท๐Ÿผโ€โ™€|woman_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™€๏ธ|woman_construction_worker_medium_skin_tone ๐Ÿ‘ท๐Ÿฝโ€โ™€|woman_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™€๏ธ|woman_construction_worker_medium_dark_skin_tone ๐Ÿ‘ท๐Ÿพโ€โ™€|woman_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ|woman_construction_worker_dark_skin_tone ๐Ÿ‘ท๐Ÿฟโ€โ™€|person_with_crown ๐Ÿซ…|person_with_crown_light_skin_tone ๐Ÿซ…๐Ÿป|person_with_crown_medium_light_skin_tone ๐Ÿซ…๐Ÿผ|person_with_crown_medium_skin_tone ๐Ÿซ…๐Ÿฝ|person_with_crown_medium_dark_skin_tone ๐Ÿซ…๐Ÿพ|person_with_crown_dark_skin_tone ๐Ÿซ…๐Ÿฟ|prince ๐Ÿคด|prince_light_skin_tone ๐Ÿคด๐Ÿป|prince_medium_light_skin_tone ๐Ÿคด๐Ÿผ|prince_medium_skin_tone ๐Ÿคด๐Ÿฝ|prince_medium_dark_skin_tone ๐Ÿคด๐Ÿพ|prince_dark_skin_tone ๐Ÿคด๐Ÿฟ|princess ๐Ÿ‘ธ|princess_light_skin_tone ๐Ÿ‘ธ๐Ÿป|princess_medium_light_skin_tone ๐Ÿ‘ธ๐Ÿผ|princess_medium_skin_tone ๐Ÿ‘ธ๐Ÿฝ|princess_medium_dark_skin_tone ๐Ÿ‘ธ๐Ÿพ|princess_dark_skin_tone ๐Ÿ‘ธ๐Ÿฟ|person_wearing_turban ๐Ÿ‘ณ|person_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿป|person_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผ|person_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝ|person_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพ|person_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟ|man_wearing_turban ๐Ÿ‘ณโ€โ™‚๏ธ|man_wearing_turban ๐Ÿ‘ณโ€โ™‚|man_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™‚๏ธ|man_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™‚|man_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™‚๏ธ|man_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™‚|man_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™‚๏ธ|man_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™‚|man_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™‚๏ธ|man_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™‚|man_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™‚๏ธ|man_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™‚|woman_wearing_turban ๐Ÿ‘ณโ€โ™€๏ธ|woman_wearing_turban ๐Ÿ‘ณโ€โ™€|woman_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™€๏ธ|woman_wearing_turban_light_skin_tone ๐Ÿ‘ณ๐Ÿปโ€โ™€|woman_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™€๏ธ|woman_wearing_turban_medium_light_skin_tone ๐Ÿ‘ณ๐Ÿผโ€โ™€|woman_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™€๏ธ|woman_wearing_turban_medium_skin_tone ๐Ÿ‘ณ๐Ÿฝโ€โ™€|woman_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™€๏ธ|woman_wearing_turban_medium_dark_skin_tone ๐Ÿ‘ณ๐Ÿพโ€โ™€|woman_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™€๏ธ|woman_wearing_turban_dark_skin_tone ๐Ÿ‘ณ๐Ÿฟโ€โ™€|person_with_skullcap ๐Ÿ‘ฒ|person_with_skullcap_light_skin_tone ๐Ÿ‘ฒ๐Ÿป|person_with_skullcap_medium_light_skin_tone ๐Ÿ‘ฒ๐Ÿผ|person_with_skullcap_medium_skin_tone ๐Ÿ‘ฒ๐Ÿฝ|person_with_skullcap_medium_dark_skin_tone ๐Ÿ‘ฒ๐Ÿพ|person_with_skullcap_dark_skin_tone ๐Ÿ‘ฒ๐Ÿฟ|woman_with_headscarf ๐Ÿง•|woman_with_headscarf_light_skin_tone ๐Ÿง•๐Ÿป|woman_with_headscarf_medium_light_skin_tone ๐Ÿง•๐Ÿผ|woman_with_headscarf_medium_skin_tone ๐Ÿง•๐Ÿฝ|woman_with_headscarf_medium_dark_skin_tone ๐Ÿง•๐Ÿพ|woman_with_headscarf_dark_skin_tone ๐Ÿง•๐Ÿฟ|person_in_tuxedo ๐Ÿคต|person_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿป|person_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผ|person_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝ|person_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพ|person_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟ|man_in_tuxedo ๐Ÿคตโ€โ™‚๏ธ|man_in_tuxedo ๐Ÿคตโ€โ™‚|man_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™‚๏ธ|man_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™‚|man_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™‚๏ธ|man_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™‚|man_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™‚๏ธ|man_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™‚|man_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™‚๏ธ|man_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™‚|man_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™‚๏ธ|man_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™‚|woman_in_tuxedo ๐Ÿคตโ€โ™€๏ธ|woman_in_tuxedo ๐Ÿคตโ€โ™€|woman_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™€๏ธ|woman_in_tuxedo_light_skin_tone ๐Ÿคต๐Ÿปโ€โ™€|woman_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™€๏ธ|woman_in_tuxedo_medium_light_skin_tone ๐Ÿคต๐Ÿผโ€โ™€|woman_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™€๏ธ|woman_in_tuxedo_medium_skin_tone ๐Ÿคต๐Ÿฝโ€โ™€|woman_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™€๏ธ|woman_in_tuxedo_medium_dark_skin_tone ๐Ÿคต๐Ÿพโ€โ™€|woman_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™€๏ธ|woman_in_tuxedo_dark_skin_tone ๐Ÿคต๐Ÿฟโ€โ™€|person_with_veil ๐Ÿ‘ฐ|person_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿป|person_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผ|person_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝ|person_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพ|person_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟ|man_with_veil ๐Ÿ‘ฐโ€โ™‚๏ธ|man_with_veil ๐Ÿ‘ฐโ€โ™‚|man_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™‚๏ธ|man_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™‚|man_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™‚๏ธ|man_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™‚|man_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™‚๏ธ|man_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™‚|man_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™‚๏ธ|man_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™‚|man_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™‚๏ธ|man_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™‚|woman_with_veil ๐Ÿ‘ฐโ€โ™€๏ธ|woman_with_veil ๐Ÿ‘ฐโ€โ™€|woman_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™€๏ธ|woman_with_veil_light_skin_tone ๐Ÿ‘ฐ๐Ÿปโ€โ™€|woman_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™€๏ธ|woman_with_veil_medium_light_skin_tone ๐Ÿ‘ฐ๐Ÿผโ€โ™€|woman_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™€๏ธ|woman_with_veil_medium_skin_tone ๐Ÿ‘ฐ๐Ÿฝโ€โ™€|woman_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™€๏ธ|woman_with_veil_medium_dark_skin_tone ๐Ÿ‘ฐ๐Ÿพโ€โ™€|woman_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™€๏ธ|woman_with_veil_dark_skin_tone ๐Ÿ‘ฐ๐Ÿฟโ€โ™€|pregnant_woman ๐Ÿคฐ|pregnant_woman_light_skin_tone ๐Ÿคฐ๐Ÿป|pregnant_woman_medium_light_skin_tone ๐Ÿคฐ๐Ÿผ|pregnant_woman_medium_skin_tone ๐Ÿคฐ๐Ÿฝ|pregnant_woman_medium_dark_skin_tone ๐Ÿคฐ๐Ÿพ|pregnant_woman_dark_skin_tone ๐Ÿคฐ๐Ÿฟ|pregnant_man ๐Ÿซƒ|pregnant_man_light_skin_tone ๐Ÿซƒ๐Ÿป|pregnant_man_medium_light_skin_tone ๐Ÿซƒ๐Ÿผ|pregnant_man_medium_skin_tone ๐Ÿซƒ๐Ÿฝ|pregnant_man_medium_dark_skin_tone ๐Ÿซƒ๐Ÿพ|pregnant_man_dark_skin_tone ๐Ÿซƒ๐Ÿฟ|pregnant_person ๐Ÿซ„|pregnant_person_light_skin_tone ๐Ÿซ„๐Ÿป|pregnant_person_medium_light_skin_tone ๐Ÿซ„๐Ÿผ|pregnant_person_medium_skin_tone ๐Ÿซ„๐Ÿฝ|pregnant_person_medium_dark_skin_tone ๐Ÿซ„๐Ÿพ|pregnant_person_dark_skin_tone ๐Ÿซ„๐Ÿฟ|breast_feeding ๐Ÿคฑ|breast_feeding_light_skin_tone ๐Ÿคฑ๐Ÿป|breast_feeding_medium_light_skin_tone ๐Ÿคฑ๐Ÿผ|breast_feeding_medium_skin_tone ๐Ÿคฑ๐Ÿฝ|breast_feeding_medium_dark_skin_tone ๐Ÿคฑ๐Ÿพ|breast_feeding_dark_skin_tone ๐Ÿคฑ๐Ÿฟ|woman_feeding_baby ๐Ÿ‘ฉโ€๐Ÿผ|woman_feeding_baby_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿผ|woman_feeding_baby_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿผ|woman_feeding_baby_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿผ|woman_feeding_baby_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿผ|woman_feeding_baby_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿผ|man_feeding_baby ๐Ÿ‘จโ€๐Ÿผ|man_feeding_baby_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿผ|man_feeding_baby_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿผ|man_feeding_baby_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿผ|man_feeding_baby_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿผ|man_feeding_baby_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿผ|person_feeding_baby ๐Ÿง‘โ€๐Ÿผ|person_feeding_baby_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿผ|person_feeding_baby_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿผ|person_feeding_baby_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿผ|person_feeding_baby_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿผ|person_feeding_baby_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿผ|baby_angel ๐Ÿ‘ผ|baby_angel_light_skin_tone ๐Ÿ‘ผ๐Ÿป|baby_angel_medium_light_skin_tone ๐Ÿ‘ผ๐Ÿผ|baby_angel_medium_skin_tone ๐Ÿ‘ผ๐Ÿฝ|baby_angel_medium_dark_skin_tone ๐Ÿ‘ผ๐Ÿพ|baby_angel_dark_skin_tone ๐Ÿ‘ผ๐Ÿฟ|santa_claus ๐ŸŽ…|santa_claus_light_skin_tone ๐ŸŽ…๐Ÿป|santa_claus_medium_light_skin_tone ๐ŸŽ…๐Ÿผ|santa_claus_medium_skin_tone ๐ŸŽ…๐Ÿฝ|santa_claus_medium_dark_skin_tone ๐ŸŽ…๐Ÿพ|santa_claus_dark_skin_tone ๐ŸŽ…๐Ÿฟ|mrs_claus ๐Ÿคถ|mrs_claus_light_skin_tone ๐Ÿคถ๐Ÿป|mrs_claus_medium_light_skin_tone ๐Ÿคถ๐Ÿผ|mrs_claus_medium_skin_tone ๐Ÿคถ๐Ÿฝ|mrs_claus_medium_dark_skin_tone ๐Ÿคถ๐Ÿพ|mrs_claus_dark_skin_tone ๐Ÿคถ๐Ÿฟ|mx_claus ๐Ÿง‘โ€๐ŸŽ„|mx_claus_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐ŸŽ„|mx_claus_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐ŸŽ„|mx_claus_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐ŸŽ„|mx_claus_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐ŸŽ„|mx_claus_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐ŸŽ„|superhero ๐Ÿฆธ|superhero_light_skin_tone ๐Ÿฆธ๐Ÿป|superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผ|superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝ|superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพ|superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟ|man_superhero ๐Ÿฆธโ€โ™‚๏ธ|man_superhero ๐Ÿฆธโ€โ™‚|man_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™‚๏ธ|man_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™‚|man_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™‚๏ธ|man_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™‚|man_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™‚๏ธ|man_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™‚|man_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™‚๏ธ|man_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™‚|man_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™‚๏ธ|man_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™‚|woman_superhero ๐Ÿฆธโ€โ™€๏ธ|woman_superhero ๐Ÿฆธโ€โ™€|woman_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™€๏ธ|woman_superhero_light_skin_tone ๐Ÿฆธ๐Ÿปโ€โ™€|woman_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™€๏ธ|woman_superhero_medium_light_skin_tone ๐Ÿฆธ๐Ÿผโ€โ™€|woman_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™€๏ธ|woman_superhero_medium_skin_tone ๐Ÿฆธ๐Ÿฝโ€โ™€|woman_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™€๏ธ|woman_superhero_medium_dark_skin_tone ๐Ÿฆธ๐Ÿพโ€โ™€|woman_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™€๏ธ|woman_superhero_dark_skin_tone ๐Ÿฆธ๐Ÿฟโ€โ™€|supervillain ๐Ÿฆน|supervillain_light_skin_tone ๐Ÿฆน๐Ÿป|supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผ|supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝ|supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพ|supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟ|man_supervillain ๐Ÿฆนโ€โ™‚๏ธ|man_supervillain ๐Ÿฆนโ€โ™‚|man_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™‚๏ธ|man_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™‚|man_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™‚๏ธ|man_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™‚|man_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™‚๏ธ|man_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™‚|man_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™‚๏ธ|man_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™‚|man_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™‚๏ธ|man_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™‚|woman_supervillain ๐Ÿฆนโ€โ™€๏ธ|woman_supervillain ๐Ÿฆนโ€โ™€|woman_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™€๏ธ|woman_supervillain_light_skin_tone ๐Ÿฆน๐Ÿปโ€โ™€|woman_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™€๏ธ|woman_supervillain_medium_light_skin_tone ๐Ÿฆน๐Ÿผโ€โ™€|woman_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™€๏ธ|woman_supervillain_medium_skin_tone ๐Ÿฆน๐Ÿฝโ€โ™€|woman_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™€๏ธ|woman_supervillain_medium_dark_skin_tone ๐Ÿฆน๐Ÿพโ€โ™€|woman_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™€๏ธ|woman_supervillain_dark_skin_tone ๐Ÿฆน๐Ÿฟโ€โ™€|mage ๐Ÿง™|mage_light_skin_tone ๐Ÿง™๐Ÿป|mage_medium_light_skin_tone ๐Ÿง™๐Ÿผ|mage_medium_skin_tone ๐Ÿง™๐Ÿฝ|mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพ|mage_dark_skin_tone ๐Ÿง™๐Ÿฟ|man_mage ๐Ÿง™โ€โ™‚๏ธ|man_mage ๐Ÿง™โ€โ™‚|man_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™‚๏ธ|man_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™‚|man_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™‚๏ธ|man_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™‚|man_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™‚๏ธ|man_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™‚|man_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™‚๏ธ|man_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™‚|man_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™‚๏ธ|man_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™‚|woman_mage ๐Ÿง™โ€โ™€๏ธ|woman_mage ๐Ÿง™โ€โ™€|woman_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™€๏ธ|woman_mage_light_skin_tone ๐Ÿง™๐Ÿปโ€โ™€|woman_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™€๏ธ|woman_mage_medium_light_skin_tone ๐Ÿง™๐Ÿผโ€โ™€|woman_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™€๏ธ|woman_mage_medium_skin_tone ๐Ÿง™๐Ÿฝโ€โ™€|woman_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™€๏ธ|woman_mage_medium_dark_skin_tone ๐Ÿง™๐Ÿพโ€โ™€|woman_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™€๏ธ|woman_mage_dark_skin_tone ๐Ÿง™๐Ÿฟโ€โ™€|fairy ๐Ÿงš|fairy_light_skin_tone ๐Ÿงš๐Ÿป|fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผ|fairy_medium_skin_tone ๐Ÿงš๐Ÿฝ|fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพ|fairy_dark_skin_tone ๐Ÿงš๐Ÿฟ|man_fairy ๐Ÿงšโ€โ™‚๏ธ|man_fairy ๐Ÿงšโ€โ™‚|man_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™‚๏ธ|man_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™‚|man_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™‚๏ธ|man_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™‚|man_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™‚๏ธ|man_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™‚|man_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™‚๏ธ|man_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™‚|man_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™‚๏ธ|man_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™‚|woman_fairy ๐Ÿงšโ€โ™€๏ธ|woman_fairy ๐Ÿงšโ€โ™€|woman_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™€๏ธ|woman_fairy_light_skin_tone ๐Ÿงš๐Ÿปโ€โ™€|woman_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™€๏ธ|woman_fairy_medium_light_skin_tone ๐Ÿงš๐Ÿผโ€โ™€|woman_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™€๏ธ|woman_fairy_medium_skin_tone ๐Ÿงš๐Ÿฝโ€โ™€|woman_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™€๏ธ|woman_fairy_medium_dark_skin_tone ๐Ÿงš๐Ÿพโ€โ™€|woman_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™€๏ธ|woman_fairy_dark_skin_tone ๐Ÿงš๐Ÿฟโ€โ™€|vampire ๐Ÿง›|vampire_light_skin_tone ๐Ÿง›๐Ÿป|vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผ|vampire_medium_skin_tone ๐Ÿง›๐Ÿฝ|vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพ|vampire_dark_skin_tone ๐Ÿง›๐Ÿฟ|man_vampire ๐Ÿง›โ€โ™‚๏ธ|man_vampire ๐Ÿง›โ€โ™‚|man_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™‚๏ธ|man_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™‚|man_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™‚๏ธ|man_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™‚|man_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™‚๏ธ|man_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™‚|man_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™‚๏ธ|man_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™‚|man_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™‚๏ธ|man_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™‚|woman_vampire ๐Ÿง›โ€โ™€๏ธ|woman_vampire ๐Ÿง›โ€โ™€|woman_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™€๏ธ|woman_vampire_light_skin_tone ๐Ÿง›๐Ÿปโ€โ™€|woman_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™€๏ธ|woman_vampire_medium_light_skin_tone ๐Ÿง›๐Ÿผโ€โ™€|woman_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™€๏ธ|woman_vampire_medium_skin_tone ๐Ÿง›๐Ÿฝโ€โ™€|woman_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™€๏ธ|woman_vampire_medium_dark_skin_tone ๐Ÿง›๐Ÿพโ€โ™€|woman_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™€๏ธ|woman_vampire_dark_skin_tone ๐Ÿง›๐Ÿฟโ€โ™€|merperson ๐Ÿงœ|merperson_light_skin_tone ๐Ÿงœ๐Ÿป|merperson_medium_light_skin_tone ๐Ÿงœ๐Ÿผ|merperson_medium_skin_tone ๐Ÿงœ๐Ÿฝ|merperson_medium_dark_skin_tone ๐Ÿงœ๐Ÿพ|merperson_dark_skin_tone ๐Ÿงœ๐Ÿฟ|merman ๐Ÿงœโ€โ™‚๏ธ|merman ๐Ÿงœโ€โ™‚|merman_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™‚๏ธ|merman_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™‚|merman_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™‚๏ธ|merman_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™‚|merman_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™‚๏ธ|merman_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™‚|merman_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™‚๏ธ|merman_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™‚|merman_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™‚๏ธ|merman_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™‚|mermaid ๐Ÿงœโ€โ™€๏ธ|mermaid ๐Ÿงœโ€โ™€|mermaid_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™€๏ธ|mermaid_light_skin_tone ๐Ÿงœ๐Ÿปโ€โ™€|mermaid_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™€๏ธ|mermaid_medium_light_skin_tone ๐Ÿงœ๐Ÿผโ€โ™€|mermaid_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™€๏ธ|mermaid_medium_skin_tone ๐Ÿงœ๐Ÿฝโ€โ™€|mermaid_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™€๏ธ|mermaid_medium_dark_skin_tone ๐Ÿงœ๐Ÿพโ€โ™€|mermaid_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™€๏ธ|mermaid_dark_skin_tone ๐Ÿงœ๐Ÿฟโ€โ™€|elf ๐Ÿง|elf_light_skin_tone ๐Ÿง๐Ÿป|elf_medium_light_skin_tone ๐Ÿง๐Ÿผ|elf_medium_skin_tone ๐Ÿง๐Ÿฝ|elf_medium_dark_skin_tone ๐Ÿง๐Ÿพ|elf_dark_skin_tone ๐Ÿง๐Ÿฟ|man_elf ๐Ÿงโ€โ™‚๏ธ|man_elf ๐Ÿงโ€โ™‚|man_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚๏ธ|man_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚|man_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚๏ธ|man_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚|man_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚๏ธ|man_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚|man_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚๏ธ|man_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚|man_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚๏ธ|man_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚|woman_elf ๐Ÿงโ€โ™€๏ธ|woman_elf ๐Ÿงโ€โ™€|woman_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€๏ธ|woman_elf_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€|woman_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€๏ธ|woman_elf_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€|woman_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€๏ธ|woman_elf_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€|woman_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€๏ธ|woman_elf_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€|woman_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€๏ธ|woman_elf_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€|genie ๐Ÿงž|man_genie ๐Ÿงžโ€โ™‚๏ธ|man_genie ๐Ÿงžโ€โ™‚|woman_genie ๐Ÿงžโ€โ™€๏ธ|woman_genie ๐Ÿงžโ€โ™€|zombie ๐ŸงŸ|man_zombie ๐ŸงŸโ€โ™‚๏ธ|man_zombie ๐ŸงŸโ€โ™‚|woman_zombie ๐ŸงŸโ€โ™€๏ธ|woman_zombie ๐ŸงŸโ€โ™€|troll ๐ŸงŒ|person_getting_massage ๐Ÿ’†|person_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿป|person_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผ|person_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝ|person_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพ|person_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟ|man_getting_massage ๐Ÿ’†โ€โ™‚๏ธ|man_getting_massage ๐Ÿ’†โ€โ™‚|man_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™‚๏ธ|man_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™‚|man_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™‚๏ธ|man_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™‚|man_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™‚๏ธ|man_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™‚|man_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™‚๏ธ|man_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™‚|man_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ|man_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™‚|woman_getting_massage ๐Ÿ’†โ€โ™€๏ธ|woman_getting_massage ๐Ÿ’†โ€โ™€|woman_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™€๏ธ|woman_getting_massage_light_skin_tone ๐Ÿ’†๐Ÿปโ€โ™€|woman_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™€๏ธ|woman_getting_massage_medium_light_skin_tone ๐Ÿ’†๐Ÿผโ€โ™€|woman_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™€๏ธ|woman_getting_massage_medium_skin_tone ๐Ÿ’†๐Ÿฝโ€โ™€|woman_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™€๏ธ|woman_getting_massage_medium_dark_skin_tone ๐Ÿ’†๐Ÿพโ€โ™€|woman_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™€๏ธ|woman_getting_massage_dark_skin_tone ๐Ÿ’†๐Ÿฟโ€โ™€|person_getting_haircut ๐Ÿ’‡|person_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿป|person_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผ|person_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝ|person_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพ|person_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟ|man_getting_haircut ๐Ÿ’‡โ€โ™‚๏ธ|man_getting_haircut ๐Ÿ’‡โ€โ™‚|man_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™‚๏ธ|man_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™‚|man_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™‚๏ธ|man_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™‚|man_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™‚๏ธ|man_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™‚|man_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™‚๏ธ|man_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™‚|man_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™‚๏ธ|man_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™‚|woman_getting_haircut ๐Ÿ’‡โ€โ™€๏ธ|woman_getting_haircut ๐Ÿ’‡โ€โ™€|woman_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™€๏ธ|woman_getting_haircut_light_skin_tone ๐Ÿ’‡๐Ÿปโ€โ™€|woman_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™€๏ธ|woman_getting_haircut_medium_light_skin_tone ๐Ÿ’‡๐Ÿผโ€โ™€|woman_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™€๏ธ|woman_getting_haircut_medium_skin_tone ๐Ÿ’‡๐Ÿฝโ€โ™€|woman_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™€๏ธ|woman_getting_haircut_medium_dark_skin_tone ๐Ÿ’‡๐Ÿพโ€โ™€|woman_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™€๏ธ|woman_getting_haircut_dark_skin_tone ๐Ÿ’‡๐Ÿฟโ€โ™€|person_walking ๐Ÿšถ|person_walking_light_skin_tone ๐Ÿšถ๐Ÿป|person_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผ|person_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝ|person_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพ|person_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟ|man_walking ๐Ÿšถโ€โ™‚๏ธ|man_walking ๐Ÿšถโ€โ™‚|man_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚๏ธ|man_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚|man_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚๏ธ|man_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚|man_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚๏ธ|man_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚|man_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚๏ธ|man_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚|man_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚๏ธ|man_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚|woman_walking ๐Ÿšถโ€โ™€๏ธ|woman_walking ๐Ÿšถโ€โ™€|woman_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€๏ธ|woman_walking_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€|woman_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€๏ธ|woman_walking_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€|woman_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€๏ธ|woman_walking_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€|woman_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€๏ธ|woman_walking_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€|woman_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€๏ธ|woman_walking_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€|person_walking_facing_right ๐Ÿšถโ€โžก๏ธ|person_walking_facing_right ๐Ÿšถโ€โžก|person_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โžก๏ธ|person_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โžก|person_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โžก๏ธ|person_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โžก|person_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โžก๏ธ|person_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โžก|person_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โžก๏ธ|person_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โžก|person_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โžก๏ธ|person_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โžก|woman_walking_facing_right ๐Ÿšถโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right ๐Ÿšถโ€โ™€โ€โžก๏ธ|woman_walking_facing_right ๐Ÿšถโ€โ™€๏ธโ€โžก|woman_walking_facing_right ๐Ÿšถโ€โ™€โ€โžก|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก|woman_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™€โ€โžก|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก|woman_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™€โ€โžก|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก|woman_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™€โ€โžก|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก|woman_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™€โ€โžก|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€โ€โžก๏ธ|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก|woman_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™€โ€โžก|man_walking_facing_right ๐Ÿšถโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right ๐Ÿšถโ€โ™‚โ€โžก๏ธ|man_walking_facing_right ๐Ÿšถโ€โ™‚๏ธโ€โžก|man_walking_facing_right ๐Ÿšถโ€โ™‚โ€โžก|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก|man_walking_facing_right_light_skin_tone ๐Ÿšถ๐Ÿปโ€โ™‚โ€โžก|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก|man_walking_facing_right_medium_light_skin_tone ๐Ÿšถ๐Ÿผโ€โ™‚โ€โžก|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก|man_walking_facing_right_medium_skin_tone ๐Ÿšถ๐Ÿฝโ€โ™‚โ€โžก|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก|man_walking_facing_right_medium_dark_skin_tone ๐Ÿšถ๐Ÿพโ€โ™‚โ€โžก|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚โ€โžก๏ธ|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก|man_walking_facing_right_dark_skin_tone ๐Ÿšถ๐Ÿฟโ€โ™‚โ€โžก|person_standing ๐Ÿง|person_standing_light_skin_tone ๐Ÿง๐Ÿป|person_standing_medium_light_skin_tone ๐Ÿง๐Ÿผ|person_standing_medium_skin_tone ๐Ÿง๐Ÿฝ|person_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพ|person_standing_dark_skin_tone ๐Ÿง๐Ÿฟ|man_standing ๐Ÿงโ€โ™‚๏ธ|man_standing ๐Ÿงโ€โ™‚|man_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚๏ธ|man_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™‚|man_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚๏ธ|man_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™‚|man_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚๏ธ|man_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™‚|man_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚๏ธ|man_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™‚|man_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚๏ธ|man_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™‚|woman_standing ๐Ÿงโ€โ™€๏ธ|woman_standing ๐Ÿงโ€โ™€|woman_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€๏ธ|woman_standing_light_skin_tone ๐Ÿง๐Ÿปโ€โ™€|woman_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€๏ธ|woman_standing_medium_light_skin_tone ๐Ÿง๐Ÿผโ€โ™€|woman_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€๏ธ|woman_standing_medium_skin_tone ๐Ÿง๐Ÿฝโ€โ™€|woman_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€๏ธ|woman_standing_medium_dark_skin_tone ๐Ÿง๐Ÿพโ€โ™€|woman_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€๏ธ|woman_standing_dark_skin_tone ๐Ÿง๐Ÿฟโ€โ™€|person_kneeling ๐ŸงŽ|person_kneeling_light_skin_tone ๐ŸงŽ๐Ÿป|person_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผ|person_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝ|person_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพ|person_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟ|man_kneeling ๐ŸงŽโ€โ™‚๏ธ|man_kneeling ๐ŸงŽโ€โ™‚|man_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚๏ธ|man_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚|man_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚๏ธ|man_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚|man_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚๏ธ|man_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚|man_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚๏ธ|man_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚|man_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚๏ธ|man_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚|woman_kneeling ๐ŸงŽโ€โ™€๏ธ|woman_kneeling ๐ŸงŽโ€โ™€|woman_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€๏ธ|woman_kneeling_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€|woman_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€๏ธ|woman_kneeling_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€|woman_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€๏ธ|woman_kneeling_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€|woman_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€๏ธ|woman_kneeling_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€|woman_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€๏ธ|woman_kneeling_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€|person_kneeling_facing_right ๐ŸงŽโ€โžก๏ธ|person_kneeling_facing_right ๐ŸงŽโ€โžก|person_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โžก๏ธ|person_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โžก|person_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โžก๏ธ|person_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โžก|person_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โžก๏ธ|person_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โžก|person_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โžก๏ธ|person_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โžก|person_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โžก๏ธ|person_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โžก|woman_kneeling_facing_right ๐ŸงŽโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right ๐ŸงŽโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right ๐ŸงŽโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right ๐ŸงŽโ€โ™€โ€โžก|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™€โ€โžก|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™€โ€โžก|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™€โ€โžก|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™€โ€โžก|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€โ€โžก๏ธ|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก|woman_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™€โ€โžก|man_kneeling_facing_right ๐ŸงŽโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right ๐ŸงŽโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right ๐ŸงŽโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right ๐ŸงŽโ€โ™‚โ€โžก|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_light_skin_tone ๐ŸงŽ๐Ÿปโ€โ™‚โ€โžก|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_medium_light_skin_tone ๐ŸงŽ๐Ÿผโ€โ™‚โ€โžก|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_medium_skin_tone ๐ŸงŽ๐Ÿฝโ€โ™‚โ€โžก|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_medium_dark_skin_tone ๐ŸงŽ๐Ÿพโ€โ™‚โ€โžก|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚โ€โžก๏ธ|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก|man_kneeling_facing_right_dark_skin_tone ๐ŸงŽ๐Ÿฟโ€โ™‚โ€โžก|person_with_white_cane ๐Ÿง‘โ€๐Ÿฆฏ|person_with_white_cane_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฏ|person_with_white_cane_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฏ|person_with_white_cane_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏ|person_with_white_cane_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฏ|person_with_white_cane_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏ|person_with_white_cane_facing_right ๐Ÿง‘โ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right ๐Ÿง‘โ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก|person_with_white_cane_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ|person_with_white_cane_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก|man_with_white_cane ๐Ÿ‘จโ€๐Ÿฆฏ|man_with_white_cane_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏ|man_with_white_cane_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏ|man_with_white_cane_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏ|man_with_white_cane_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏ|man_with_white_cane_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏ|man_with_white_cane_facing_right ๐Ÿ‘จโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right ๐Ÿ‘จโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก|man_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ|man_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก|woman_with_white_cane ๐Ÿ‘ฉโ€๐Ÿฆฏ|woman_with_white_cane_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏ|woman_with_white_cane_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏ|woman_with_white_cane_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏ|woman_with_white_cane_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏ|woman_with_white_cane_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏ|woman_with_white_cane_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก|woman_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ|woman_with_white_cane_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก|person_in_motorized_wheelchair ๐Ÿง‘โ€๐Ÿฆผ|person_in_motorized_wheelchair_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆผ|person_in_motorized_wheelchair_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆผ|person_in_motorized_wheelchair_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆผ|person_in_motorized_wheelchair_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆผ|person_in_motorized_wheelchair_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆผ|person_in_motorized_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก|person_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ|person_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair ๐Ÿ‘จโ€๐Ÿฆผ|man_in_motorized_wheelchair_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผ|man_in_motorized_wheelchair_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผ|man_in_motorized_wheelchair_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผ|man_in_motorized_wheelchair_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผ|man_in_motorized_wheelchair_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผ|man_in_motorized_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก|man_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ|man_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair ๐Ÿ‘ฉโ€๐Ÿฆผ|woman_in_motorized_wheelchair_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผ|woman_in_motorized_wheelchair_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผ|woman_in_motorized_wheelchair_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผ|woman_in_motorized_wheelchair_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผ|woman_in_motorized_wheelchair_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผ|woman_in_motorized_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก|woman_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ|woman_in_motorized_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก|person_in_manual_wheelchair ๐Ÿง‘โ€๐Ÿฆฝ|person_in_manual_wheelchair_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฝ|person_in_manual_wheelchair_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฝ|person_in_manual_wheelchair_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝ|person_in_manual_wheelchair_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฝ|person_in_manual_wheelchair_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝ|person_in_manual_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right ๐Ÿง‘โ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก|person_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ|person_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair ๐Ÿ‘จโ€๐Ÿฆฝ|man_in_manual_wheelchair_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝ|man_in_manual_wheelchair_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝ|man_in_manual_wheelchair_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝ|man_in_manual_wheelchair_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝ|man_in_manual_wheelchair_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝ|man_in_manual_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right ๐Ÿ‘จโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก|man_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ|man_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair ๐Ÿ‘ฉโ€๐Ÿฆฝ|woman_in_manual_wheelchair_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝ|woman_in_manual_wheelchair_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝ|woman_in_manual_wheelchair_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝ|woman_in_manual_wheelchair_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝ|woman_in_manual_wheelchair_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝ|woman_in_manual_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right ๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก|woman_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ|woman_in_manual_wheelchair_facing_right_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก|person_running ๐Ÿƒ|person_running_light_skin_tone ๐Ÿƒ๐Ÿป|person_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผ|person_running_medium_skin_tone ๐Ÿƒ๐Ÿฝ|person_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพ|person_running_dark_skin_tone ๐Ÿƒ๐Ÿฟ|man_running ๐Ÿƒโ€โ™‚๏ธ|man_running ๐Ÿƒโ€โ™‚|man_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚๏ธ|man_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚|man_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚๏ธ|man_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚|man_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚๏ธ|man_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚|man_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚๏ธ|man_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚|man_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚๏ธ|man_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚|woman_running ๐Ÿƒโ€โ™€๏ธ|woman_running ๐Ÿƒโ€โ™€|woman_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€๏ธ|woman_running_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€|woman_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€๏ธ|woman_running_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€|woman_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€๏ธ|woman_running_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€|woman_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€๏ธ|woman_running_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€|woman_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€๏ธ|woman_running_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€|person_running_facing_right ๐Ÿƒโ€โžก๏ธ|person_running_facing_right ๐Ÿƒโ€โžก|person_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โžก๏ธ|person_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โžก|person_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โžก๏ธ|person_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โžก|person_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โžก๏ธ|person_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โžก|person_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โžก๏ธ|person_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โžก|person_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โžก๏ธ|person_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โžก|woman_running_facing_right ๐Ÿƒโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right ๐Ÿƒโ€โ™€โ€โžก๏ธ|woman_running_facing_right ๐Ÿƒโ€โ™€๏ธโ€โžก|woman_running_facing_right ๐Ÿƒโ€โ™€โ€โžก|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€โ€โžก๏ธ|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก|woman_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™€โ€โžก|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€โ€โžก๏ธ|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก|woman_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™€โ€โžก|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€โ€โžก๏ธ|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก|woman_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™€โ€โžก|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€โ€โžก๏ธ|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก|woman_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™€โ€โžก|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€โ€โžก๏ธ|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก|woman_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™€โ€โžก|man_running_facing_right ๐Ÿƒโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right ๐Ÿƒโ€โ™‚โ€โžก๏ธ|man_running_facing_right ๐Ÿƒโ€โ™‚๏ธโ€โžก|man_running_facing_right ๐Ÿƒโ€โ™‚โ€โžก|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚โ€โžก๏ธ|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก|man_running_facing_right_light_skin_tone ๐Ÿƒ๐Ÿปโ€โ™‚โ€โžก|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚โ€โžก๏ธ|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก|man_running_facing_right_medium_light_skin_tone ๐Ÿƒ๐Ÿผโ€โ™‚โ€โžก|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚โ€โžก๏ธ|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก|man_running_facing_right_medium_skin_tone ๐Ÿƒ๐Ÿฝโ€โ™‚โ€โžก|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚โ€โžก๏ธ|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก|man_running_facing_right_medium_dark_skin_tone ๐Ÿƒ๐Ÿพโ€โ™‚โ€โžก|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚โ€โžก๏ธ|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก|man_running_facing_right_dark_skin_tone ๐Ÿƒ๐Ÿฟโ€โ™‚โ€โžก|woman_dancing ๐Ÿ’ƒ|woman_dancing_light_skin_tone ๐Ÿ’ƒ๐Ÿป|woman_dancing_medium_light_skin_tone ๐Ÿ’ƒ๐Ÿผ|woman_dancing_medium_skin_tone ๐Ÿ’ƒ๐Ÿฝ|woman_dancing_medium_dark_skin_tone ๐Ÿ’ƒ๐Ÿพ|woman_dancing_dark_skin_tone ๐Ÿ’ƒ๐Ÿฟ|man_dancing ๐Ÿ•บ|man_dancing_light_skin_tone ๐Ÿ•บ๐Ÿป|man_dancing_medium_light_skin_tone ๐Ÿ•บ๐Ÿผ|man_dancing_medium_skin_tone ๐Ÿ•บ๐Ÿฝ|man_dancing_medium_dark_skin_tone ๐Ÿ•บ๐Ÿพ|man_dancing_dark_skin_tone ๐Ÿ•บ๐Ÿฟ|person_in_suit_levitating ๐Ÿ•ด๏ธ|person_in_suit_levitating ๐Ÿ•ด|person_in_suit_levitating_light_skin_tone ๐Ÿ•ด๐Ÿป|person_in_suit_levitating_medium_light_skin_tone ๐Ÿ•ด๐Ÿผ|person_in_suit_levitating_medium_skin_tone ๐Ÿ•ด๐Ÿฝ|person_in_suit_levitating_medium_dark_skin_tone ๐Ÿ•ด๐Ÿพ|person_in_suit_levitating_dark_skin_tone ๐Ÿ•ด๐Ÿฟ|people_with_bunny_ears ๐Ÿ‘ฏ|men_with_bunny_ears ๐Ÿ‘ฏโ€โ™‚๏ธ|men_with_bunny_ears ๐Ÿ‘ฏโ€โ™‚|women_with_bunny_ears ๐Ÿ‘ฏโ€โ™€๏ธ|women_with_bunny_ears ๐Ÿ‘ฏโ€โ™€|person_in_steamy_room ๐Ÿง–|person_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿป|person_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผ|person_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝ|person_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพ|person_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟ|man_in_steamy_room ๐Ÿง–โ€โ™‚๏ธ|man_in_steamy_room ๐Ÿง–โ€โ™‚|man_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™‚๏ธ|man_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™‚|man_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™‚๏ธ|man_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™‚|man_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™‚๏ธ|man_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™‚|man_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™‚๏ธ|man_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™‚|man_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™‚๏ธ|man_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™‚|woman_in_steamy_room ๐Ÿง–โ€โ™€๏ธ|woman_in_steamy_room ๐Ÿง–โ€โ™€|woman_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™€๏ธ|woman_in_steamy_room_light_skin_tone ๐Ÿง–๐Ÿปโ€โ™€|woman_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™€๏ธ|woman_in_steamy_room_medium_light_skin_tone ๐Ÿง–๐Ÿผโ€โ™€|woman_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™€๏ธ|woman_in_steamy_room_medium_skin_tone ๐Ÿง–๐Ÿฝโ€โ™€|woman_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™€๏ธ|woman_in_steamy_room_medium_dark_skin_tone ๐Ÿง–๐Ÿพโ€โ™€|woman_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™€๏ธ|woman_in_steamy_room_dark_skin_tone ๐Ÿง–๐Ÿฟโ€โ™€|person_climbing ๐Ÿง—|person_climbing_light_skin_tone ๐Ÿง—๐Ÿป|person_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผ|person_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝ|person_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพ|person_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟ|man_climbing ๐Ÿง—โ€โ™‚๏ธ|man_climbing ๐Ÿง—โ€โ™‚|man_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™‚๏ธ|man_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™‚|man_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™‚๏ธ|man_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™‚|man_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™‚๏ธ|man_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™‚|man_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™‚๏ธ|man_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™‚|man_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™‚๏ธ|man_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™‚|woman_climbing ๐Ÿง—โ€โ™€๏ธ|woman_climbing ๐Ÿง—โ€โ™€|woman_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™€๏ธ|woman_climbing_light_skin_tone ๐Ÿง—๐Ÿปโ€โ™€|woman_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™€๏ธ|woman_climbing_medium_light_skin_tone ๐Ÿง—๐Ÿผโ€โ™€|woman_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™€๏ธ|woman_climbing_medium_skin_tone ๐Ÿง—๐Ÿฝโ€โ™€|woman_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™€๏ธ|woman_climbing_medium_dark_skin_tone ๐Ÿง—๐Ÿพโ€โ™€|woman_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™€๏ธ|woman_climbing_dark_skin_tone ๐Ÿง—๐Ÿฟโ€โ™€|person_fencing ๐Ÿคบ|horse_racing ๐Ÿ‡|horse_racing_light_skin_tone ๐Ÿ‡๐Ÿป|horse_racing_medium_light_skin_tone ๐Ÿ‡๐Ÿผ|horse_racing_medium_skin_tone ๐Ÿ‡๐Ÿฝ|horse_racing_medium_dark_skin_tone ๐Ÿ‡๐Ÿพ|horse_racing_dark_skin_tone ๐Ÿ‡๐Ÿฟ|skier โ›ท๏ธ|skier โ›ท|snowboarder ๐Ÿ‚|snowboarder_light_skin_tone ๐Ÿ‚๐Ÿป|snowboarder_medium_light_skin_tone ๐Ÿ‚๐Ÿผ|snowboarder_medium_skin_tone ๐Ÿ‚๐Ÿฝ|snowboarder_medium_dark_skin_tone ๐Ÿ‚๐Ÿพ|snowboarder_dark_skin_tone ๐Ÿ‚๐Ÿฟ|person_golfing ๐ŸŒ๏ธ|person_golfing ๐ŸŒ|person_golfing_light_skin_tone ๐ŸŒ๐Ÿป|person_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผ|person_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝ|person_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพ|person_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟ|man_golfing ๐ŸŒ๏ธโ€โ™‚๏ธ|man_golfing ๐ŸŒโ€โ™‚๏ธ|man_golfing ๐ŸŒ๏ธโ€โ™‚|man_golfing ๐ŸŒโ€โ™‚|man_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™‚๏ธ|man_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™‚|man_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™‚๏ธ|man_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™‚|man_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™‚๏ธ|man_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™‚|man_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™‚๏ธ|man_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™‚|man_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™‚๏ธ|man_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™‚|woman_golfing ๐ŸŒ๏ธโ€โ™€๏ธ|woman_golfing ๐ŸŒโ€โ™€๏ธ|woman_golfing ๐ŸŒ๏ธโ€โ™€|woman_golfing ๐ŸŒโ€โ™€|woman_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™€๏ธ|woman_golfing_light_skin_tone ๐ŸŒ๐Ÿปโ€โ™€|woman_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™€๏ธ|woman_golfing_medium_light_skin_tone ๐ŸŒ๐Ÿผโ€โ™€|woman_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™€๏ธ|woman_golfing_medium_skin_tone ๐ŸŒ๐Ÿฝโ€โ™€|woman_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™€๏ธ|woman_golfing_medium_dark_skin_tone ๐ŸŒ๐Ÿพโ€โ™€|woman_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™€๏ธ|woman_golfing_dark_skin_tone ๐ŸŒ๐Ÿฟโ€โ™€|person_surfing ๐Ÿ„|person_surfing_light_skin_tone ๐Ÿ„๐Ÿป|person_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผ|person_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝ|person_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพ|person_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟ|man_surfing ๐Ÿ„โ€โ™‚๏ธ|man_surfing ๐Ÿ„โ€โ™‚|man_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™‚๏ธ|man_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™‚|man_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™‚๏ธ|man_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™‚|man_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™‚๏ธ|man_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™‚|man_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™‚๏ธ|man_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™‚|man_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™‚๏ธ|man_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™‚|woman_surfing ๐Ÿ„โ€โ™€๏ธ|woman_surfing ๐Ÿ„โ€โ™€|woman_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™€๏ธ|woman_surfing_light_skin_tone ๐Ÿ„๐Ÿปโ€โ™€|woman_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™€๏ธ|woman_surfing_medium_light_skin_tone ๐Ÿ„๐Ÿผโ€โ™€|woman_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™€๏ธ|woman_surfing_medium_skin_tone ๐Ÿ„๐Ÿฝโ€โ™€|woman_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™€๏ธ|woman_surfing_medium_dark_skin_tone ๐Ÿ„๐Ÿพโ€โ™€|woman_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™€๏ธ|woman_surfing_dark_skin_tone ๐Ÿ„๐Ÿฟโ€โ™€|person_rowing_boat ๐Ÿšฃ|person_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿป|person_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผ|person_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝ|person_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพ|person_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟ|man_rowing_boat ๐Ÿšฃโ€โ™‚๏ธ|man_rowing_boat ๐Ÿšฃโ€โ™‚|man_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™‚๏ธ|man_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™‚|man_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™‚๏ธ|man_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™‚|man_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™‚๏ธ|man_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™‚|man_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™‚๏ธ|man_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™‚|man_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™‚๏ธ|man_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™‚|woman_rowing_boat ๐Ÿšฃโ€โ™€๏ธ|woman_rowing_boat ๐Ÿšฃโ€โ™€|woman_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™€๏ธ|woman_rowing_boat_light_skin_tone ๐Ÿšฃ๐Ÿปโ€โ™€|woman_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™€๏ธ|woman_rowing_boat_medium_light_skin_tone ๐Ÿšฃ๐Ÿผโ€โ™€|woman_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™€๏ธ|woman_rowing_boat_medium_skin_tone ๐Ÿšฃ๐Ÿฝโ€โ™€|woman_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™€๏ธ|woman_rowing_boat_medium_dark_skin_tone ๐Ÿšฃ๐Ÿพโ€โ™€|woman_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™€๏ธ|woman_rowing_boat_dark_skin_tone ๐Ÿšฃ๐Ÿฟโ€โ™€|person_swimming ๐ŸŠ|person_swimming_light_skin_tone ๐ŸŠ๐Ÿป|person_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผ|person_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝ|person_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพ|person_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟ|man_swimming ๐ŸŠโ€โ™‚๏ธ|man_swimming ๐ŸŠโ€โ™‚|man_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™‚๏ธ|man_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™‚|man_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™‚๏ธ|man_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™‚|man_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™‚๏ธ|man_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™‚|man_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™‚๏ธ|man_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™‚|man_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™‚๏ธ|man_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™‚|woman_swimming ๐ŸŠโ€โ™€๏ธ|woman_swimming ๐ŸŠโ€โ™€|woman_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™€๏ธ|woman_swimming_light_skin_tone ๐ŸŠ๐Ÿปโ€โ™€|woman_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™€๏ธ|woman_swimming_medium_light_skin_tone ๐ŸŠ๐Ÿผโ€โ™€|woman_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™€๏ธ|woman_swimming_medium_skin_tone ๐ŸŠ๐Ÿฝโ€โ™€|woman_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™€๏ธ|woman_swimming_medium_dark_skin_tone ๐ŸŠ๐Ÿพโ€โ™€|woman_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™€๏ธ|woman_swimming_dark_skin_tone ๐ŸŠ๐Ÿฟโ€โ™€|person_bouncing_ball โ›น๏ธ|person_bouncing_ball โ›น|person_bouncing_ball_light_skin_tone โ›น๐Ÿป|person_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผ|person_bouncing_ball_medium_skin_tone โ›น๐Ÿฝ|person_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพ|person_bouncing_ball_dark_skin_tone โ›น๐Ÿฟ|man_bouncing_ball โ›น๏ธโ€โ™‚๏ธ|man_bouncing_ball โ›นโ€โ™‚๏ธ|man_bouncing_ball โ›น๏ธโ€โ™‚|man_bouncing_ball โ›นโ€โ™‚|man_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™‚๏ธ|man_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™‚|man_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™‚๏ธ|man_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™‚|man_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™‚๏ธ|man_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™‚|man_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™‚๏ธ|man_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™‚|man_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™‚๏ธ|man_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™‚|woman_bouncing_ball โ›น๏ธโ€โ™€๏ธ|woman_bouncing_ball โ›นโ€โ™€๏ธ|woman_bouncing_ball โ›น๏ธโ€โ™€|woman_bouncing_ball โ›นโ€โ™€|woman_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™€๏ธ|woman_bouncing_ball_light_skin_tone โ›น๐Ÿปโ€โ™€|woman_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™€๏ธ|woman_bouncing_ball_medium_light_skin_tone โ›น๐Ÿผโ€โ™€|woman_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™€๏ธ|woman_bouncing_ball_medium_skin_tone โ›น๐Ÿฝโ€โ™€|woman_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™€๏ธ|woman_bouncing_ball_medium_dark_skin_tone โ›น๐Ÿพโ€โ™€|woman_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™€๏ธ|woman_bouncing_ball_dark_skin_tone โ›น๐Ÿฟโ€โ™€|person_lifting_weights ๐Ÿ‹๏ธ|person_lifting_weights ๐Ÿ‹|person_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿป|person_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผ|person_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝ|person_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพ|person_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟ|man_lifting_weights ๐Ÿ‹๏ธโ€โ™‚๏ธ|man_lifting_weights ๐Ÿ‹โ€โ™‚๏ธ|man_lifting_weights ๐Ÿ‹๏ธโ€โ™‚|man_lifting_weights ๐Ÿ‹โ€โ™‚|man_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™‚๏ธ|man_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™‚|man_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™‚๏ธ|man_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™‚|man_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™‚๏ธ|man_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™‚|man_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™‚๏ธ|man_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™‚|man_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™‚๏ธ|man_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™‚|woman_lifting_weights ๐Ÿ‹๏ธโ€โ™€๏ธ|woman_lifting_weights ๐Ÿ‹โ€โ™€๏ธ|woman_lifting_weights ๐Ÿ‹๏ธโ€โ™€|woman_lifting_weights ๐Ÿ‹โ€โ™€|woman_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™€๏ธ|woman_lifting_weights_light_skin_tone ๐Ÿ‹๐Ÿปโ€โ™€|woman_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™€๏ธ|woman_lifting_weights_medium_light_skin_tone ๐Ÿ‹๐Ÿผโ€โ™€|woman_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™€๏ธ|woman_lifting_weights_medium_skin_tone ๐Ÿ‹๐Ÿฝโ€โ™€|woman_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™€๏ธ|woman_lifting_weights_medium_dark_skin_tone ๐Ÿ‹๐Ÿพโ€โ™€|woman_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™€๏ธ|woman_lifting_weights_dark_skin_tone ๐Ÿ‹๐Ÿฟโ€โ™€|person_biking ๐Ÿšด|person_biking_light_skin_tone ๐Ÿšด๐Ÿป|person_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผ|person_biking_medium_skin_tone ๐Ÿšด๐Ÿฝ|person_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพ|person_biking_dark_skin_tone ๐Ÿšด๐Ÿฟ|man_biking ๐Ÿšดโ€โ™‚๏ธ|man_biking ๐Ÿšดโ€โ™‚|man_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™‚๏ธ|man_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™‚|man_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™‚๏ธ|man_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™‚|man_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™‚๏ธ|man_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™‚|man_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™‚๏ธ|man_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™‚|man_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™‚๏ธ|man_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™‚|woman_biking ๐Ÿšดโ€โ™€๏ธ|woman_biking ๐Ÿšดโ€โ™€|woman_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™€๏ธ|woman_biking_light_skin_tone ๐Ÿšด๐Ÿปโ€โ™€|woman_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™€๏ธ|woman_biking_medium_light_skin_tone ๐Ÿšด๐Ÿผโ€โ™€|woman_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™€๏ธ|woman_biking_medium_skin_tone ๐Ÿšด๐Ÿฝโ€โ™€|woman_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™€๏ธ|woman_biking_medium_dark_skin_tone ๐Ÿšด๐Ÿพโ€โ™€|woman_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™€๏ธ|woman_biking_dark_skin_tone ๐Ÿšด๐Ÿฟโ€โ™€|person_mountain_biking ๐Ÿšต|person_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿป|person_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผ|person_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝ|person_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพ|person_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟ|man_mountain_biking ๐Ÿšตโ€โ™‚๏ธ|man_mountain_biking ๐Ÿšตโ€โ™‚|man_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™‚๏ธ|man_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™‚|man_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™‚๏ธ|man_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™‚|man_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™‚๏ธ|man_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™‚|man_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™‚๏ธ|man_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™‚|man_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™‚๏ธ|man_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™‚|woman_mountain_biking ๐Ÿšตโ€โ™€๏ธ|woman_mountain_biking ๐Ÿšตโ€โ™€|woman_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™€๏ธ|woman_mountain_biking_light_skin_tone ๐Ÿšต๐Ÿปโ€โ™€|woman_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™€๏ธ|woman_mountain_biking_medium_light_skin_tone ๐Ÿšต๐Ÿผโ€โ™€|woman_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™€๏ธ|woman_mountain_biking_medium_skin_tone ๐Ÿšต๐Ÿฝโ€โ™€|woman_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™€๏ธ|woman_mountain_biking_medium_dark_skin_tone ๐Ÿšต๐Ÿพโ€โ™€|woman_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™€๏ธ|woman_mountain_biking_dark_skin_tone ๐Ÿšต๐Ÿฟโ€โ™€|person_cartwheeling ๐Ÿคธ|person_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿป|person_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผ|person_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝ|person_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพ|person_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟ|man_cartwheeling ๐Ÿคธโ€โ™‚๏ธ|man_cartwheeling ๐Ÿคธโ€โ™‚|man_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™‚๏ธ|man_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™‚|man_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™‚๏ธ|man_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™‚|man_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™‚๏ธ|man_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™‚|man_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™‚๏ธ|man_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™‚|man_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™‚๏ธ|man_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™‚|woman_cartwheeling ๐Ÿคธโ€โ™€๏ธ|woman_cartwheeling ๐Ÿคธโ€โ™€|woman_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™€๏ธ|woman_cartwheeling_light_skin_tone ๐Ÿคธ๐Ÿปโ€โ™€|woman_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™€๏ธ|woman_cartwheeling_medium_light_skin_tone ๐Ÿคธ๐Ÿผโ€โ™€|woman_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™€๏ธ|woman_cartwheeling_medium_skin_tone ๐Ÿคธ๐Ÿฝโ€โ™€|woman_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™€๏ธ|woman_cartwheeling_medium_dark_skin_tone ๐Ÿคธ๐Ÿพโ€โ™€|woman_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™€๏ธ|woman_cartwheeling_dark_skin_tone ๐Ÿคธ๐Ÿฟโ€โ™€|people_wrestling ๐Ÿคผ|men_wrestling ๐Ÿคผโ€โ™‚๏ธ|men_wrestling ๐Ÿคผโ€โ™‚|women_wrestling ๐Ÿคผโ€โ™€๏ธ|women_wrestling ๐Ÿคผโ€โ™€|person_playing_water_polo ๐Ÿคฝ|person_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿป|person_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผ|person_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝ|person_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพ|person_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟ|man_playing_water_polo ๐Ÿคฝโ€โ™‚๏ธ|man_playing_water_polo ๐Ÿคฝโ€โ™‚|man_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™‚๏ธ|man_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™‚|man_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™‚๏ธ|man_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™‚|man_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™‚๏ธ|man_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™‚|man_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™‚๏ธ|man_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™‚|man_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™‚๏ธ|man_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™‚|woman_playing_water_polo ๐Ÿคฝโ€โ™€๏ธ|woman_playing_water_polo ๐Ÿคฝโ€โ™€|woman_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™€๏ธ|woman_playing_water_polo_light_skin_tone ๐Ÿคฝ๐Ÿปโ€โ™€|woman_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™€๏ธ|woman_playing_water_polo_medium_light_skin_tone ๐Ÿคฝ๐Ÿผโ€โ™€|woman_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™€๏ธ|woman_playing_water_polo_medium_skin_tone ๐Ÿคฝ๐Ÿฝโ€โ™€|woman_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™€๏ธ|woman_playing_water_polo_medium_dark_skin_tone ๐Ÿคฝ๐Ÿพโ€โ™€|woman_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™€๏ธ|woman_playing_water_polo_dark_skin_tone ๐Ÿคฝ๐Ÿฟโ€โ™€|person_playing_handball ๐Ÿคพ|person_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿป|person_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผ|person_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝ|person_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพ|person_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟ|man_playing_handball ๐Ÿคพโ€โ™‚๏ธ|man_playing_handball ๐Ÿคพโ€โ™‚|man_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™‚๏ธ|man_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™‚|man_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™‚๏ธ|man_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™‚|man_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™‚๏ธ|man_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™‚|man_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™‚๏ธ|man_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™‚|man_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™‚๏ธ|man_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™‚|woman_playing_handball ๐Ÿคพโ€โ™€๏ธ|woman_playing_handball ๐Ÿคพโ€โ™€|woman_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™€๏ธ|woman_playing_handball_light_skin_tone ๐Ÿคพ๐Ÿปโ€โ™€|woman_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™€๏ธ|woman_playing_handball_medium_light_skin_tone ๐Ÿคพ๐Ÿผโ€โ™€|woman_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™€๏ธ|woman_playing_handball_medium_skin_tone ๐Ÿคพ๐Ÿฝโ€โ™€|woman_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™€๏ธ|woman_playing_handball_medium_dark_skin_tone ๐Ÿคพ๐Ÿพโ€โ™€|woman_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™€๏ธ|woman_playing_handball_dark_skin_tone ๐Ÿคพ๐Ÿฟโ€โ™€|person_juggling ๐Ÿคน|person_juggling_light_skin_tone ๐Ÿคน๐Ÿป|person_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผ|person_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝ|person_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพ|person_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟ|man_juggling ๐Ÿคนโ€โ™‚๏ธ|man_juggling ๐Ÿคนโ€โ™‚|man_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™‚๏ธ|man_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™‚|man_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™‚๏ธ|man_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™‚|man_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™‚๏ธ|man_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™‚|man_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™‚๏ธ|man_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™‚|man_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™‚๏ธ|man_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™‚|woman_juggling ๐Ÿคนโ€โ™€๏ธ|woman_juggling ๐Ÿคนโ€โ™€|woman_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™€๏ธ|woman_juggling_light_skin_tone ๐Ÿคน๐Ÿปโ€โ™€|woman_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™€๏ธ|woman_juggling_medium_light_skin_tone ๐Ÿคน๐Ÿผโ€โ™€|woman_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™€๏ธ|woman_juggling_medium_skin_tone ๐Ÿคน๐Ÿฝโ€โ™€|woman_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™€๏ธ|woman_juggling_medium_dark_skin_tone ๐Ÿคน๐Ÿพโ€โ™€|woman_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™€๏ธ|woman_juggling_dark_skin_tone ๐Ÿคน๐Ÿฟโ€โ™€|person_in_lotus_position ๐Ÿง˜|person_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿป|person_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผ|person_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝ|person_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพ|person_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟ|man_in_lotus_position ๐Ÿง˜โ€โ™‚๏ธ|man_in_lotus_position ๐Ÿง˜โ€โ™‚|man_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™‚๏ธ|man_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™‚|man_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™‚๏ธ|man_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™‚|man_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™‚๏ธ|man_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™‚|man_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™‚๏ธ|man_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™‚|man_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™‚๏ธ|man_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™‚|woman_in_lotus_position ๐Ÿง˜โ€โ™€๏ธ|woman_in_lotus_position ๐Ÿง˜โ€โ™€|woman_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™€๏ธ|woman_in_lotus_position_light_skin_tone ๐Ÿง˜๐Ÿปโ€โ™€|woman_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™€๏ธ|woman_in_lotus_position_medium_light_skin_tone ๐Ÿง˜๐Ÿผโ€โ™€|woman_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™€๏ธ|woman_in_lotus_position_medium_skin_tone ๐Ÿง˜๐Ÿฝโ€โ™€|woman_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™€๏ธ|woman_in_lotus_position_medium_dark_skin_tone ๐Ÿง˜๐Ÿพโ€โ™€|woman_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™€๏ธ|woman_in_lotus_position_dark_skin_tone ๐Ÿง˜๐Ÿฟโ€โ™€|person_taking_bath ๐Ÿ›€|person_taking_bath_light_skin_tone ๐Ÿ›€๐Ÿป|person_taking_bath_medium_light_skin_tone ๐Ÿ›€๐Ÿผ|person_taking_bath_medium_skin_tone ๐Ÿ›€๐Ÿฝ|person_taking_bath_medium_dark_skin_tone ๐Ÿ›€๐Ÿพ|person_taking_bath_dark_skin_tone ๐Ÿ›€๐Ÿฟ|person_in_bed ๐Ÿ›Œ|person_in_bed_light_skin_tone ๐Ÿ›Œ๐Ÿป|person_in_bed_medium_light_skin_tone ๐Ÿ›Œ๐Ÿผ|person_in_bed_medium_skin_tone ๐Ÿ›Œ๐Ÿฝ|person_in_bed_medium_dark_skin_tone ๐Ÿ›Œ๐Ÿพ|person_in_bed_dark_skin_tone ๐Ÿ›Œ๐Ÿฟ|people_holding_hands ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘|people_holding_hands_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_medium_light_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_medium_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_medium_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|people_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿป|people_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ|people_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ|people_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ|people_holding_hands_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ|women_holding_hands ๐Ÿ‘ญ|women_holding_hands_light_skin_tone ๐Ÿ‘ญ๐Ÿป|women_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_medium_light_skin_tone ๐Ÿ‘ญ๐Ÿผ|women_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_medium_skin_tone ๐Ÿ‘ญ๐Ÿฝ|women_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_medium_dark_skin_tone ๐Ÿ‘ญ๐Ÿพ|women_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ|women_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป|women_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ|women_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ|women_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ|women_holding_hands_dark_skin_tone ๐Ÿ‘ญ๐Ÿฟ|woman_and_man_holding_hands ๐Ÿ‘ซ|woman_and_man_holding_hands_light_skin_tone ๐Ÿ‘ซ๐Ÿป|woman_and_man_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_medium_light_skin_tone ๐Ÿ‘ซ๐Ÿผ|woman_and_man_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_medium_skin_tone ๐Ÿ‘ซ๐Ÿฝ|woman_and_man_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_medium_dark_skin_tone ๐Ÿ‘ซ๐Ÿพ|woman_and_man_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|woman_and_man_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|woman_and_man_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|woman_and_man_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|woman_and_man_holding_hands_dark_skin_tone ๐Ÿ‘ซ๐Ÿฟ|men_holding_hands ๐Ÿ‘ฌ|men_holding_hands_light_skin_tone ๐Ÿ‘ฌ๐Ÿป|men_holding_hands_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_medium_light_skin_tone ๐Ÿ‘ฌ๐Ÿผ|men_holding_hands_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_medium_skin_tone ๐Ÿ‘ฌ๐Ÿฝ|men_holding_hands_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_medium_dark_skin_tone ๐Ÿ‘ฌ๐Ÿพ|men_holding_hands_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ|men_holding_hands_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป|men_holding_hands_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ|men_holding_hands_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ|men_holding_hands_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ|men_holding_hands_dark_skin_tone ๐Ÿ‘ฌ๐Ÿฟ|kiss ๐Ÿ’|kiss_light_skin_tone ๐Ÿ’๐Ÿป|kiss_medium_light_skin_tone ๐Ÿ’๐Ÿผ|kiss_medium_skin_tone ๐Ÿ’๐Ÿฝ|kiss_medium_dark_skin_tone ๐Ÿ’๐Ÿพ|kiss_dark_skin_tone ๐Ÿ’๐Ÿฟ|kiss_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ|kiss_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป|kiss_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ|kiss_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ|kiss_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ|kiss_woman_man ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_woman_man ๐Ÿ‘ฉโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_man_man ๐Ÿ‘จโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ|kiss_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป|kiss_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ|kiss_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ|kiss_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ|kiss_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ|kiss_woman_woman ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ|kiss_woman_woman ๐Ÿ‘ฉโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ|kiss_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป|kiss_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ|kiss_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ|kiss_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ|kiss_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|kiss_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart ๐Ÿ’‘|couple_with_heart_light_skin_tone ๐Ÿ’‘๐Ÿป|couple_with_heart_medium_light_skin_tone ๐Ÿ’‘๐Ÿผ|couple_with_heart_medium_skin_tone ๐Ÿ’‘๐Ÿฝ|couple_with_heart_medium_dark_skin_tone ๐Ÿ’‘๐Ÿพ|couple_with_heart_dark_skin_tone ๐Ÿ’‘๐Ÿฟ|couple_with_heart_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_light_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿปโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_light_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_light_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_light_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿผโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿฝโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_medium_dark_skin_tone_dark_skin_tone ๐Ÿง‘๐Ÿพโ€โคโ€๐Ÿง‘๐Ÿฟ|couple_with_heart_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_dark_skin_tone_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿป|couple_with_heart_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_dark_skin_tone_medium_light_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿผ|couple_with_heart_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_dark_skin_tone_medium_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿฝ|couple_with_heart_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿพ|couple_with_heart_person_person_dark_skin_tone_medium_dark_skin_tone ๐Ÿง‘๐Ÿฟโ€โคโ€๐Ÿง‘๐Ÿพ|couple_with_heart_woman_man ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ|couple_with_heart_woman_man ๐Ÿ‘ฉโ€โคโ€๐Ÿ‘จ|couple_with_heart_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_man_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ|couple_with_heart_man_man ๐Ÿ‘จโ€โคโ€๐Ÿ‘จ|couple_with_heart_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_light_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿปโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_light_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_light_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿผโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿฝโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘จ๐Ÿพโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_dark_skin_tone_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿป|couple_with_heart_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿผ|couple_with_heart_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_dark_skin_tone_medium_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฝ|couple_with_heart_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿพ|couple_with_heart_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_man_man_dark_skin_tone ๐Ÿ‘จ๐Ÿฟโ€โคโ€๐Ÿ‘จ๐Ÿฟ|couple_with_heart_woman_woman ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ|couple_with_heart_woman_woman ๐Ÿ‘ฉโ€โคโ€๐Ÿ‘ฉ|couple_with_heart_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_light_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿปโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_light_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_light_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_light_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿผโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฝโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_medium_dark_skin_tone_dark_skin_tone ๐Ÿ‘ฉ๐Ÿพโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_dark_skin_tone_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿป|couple_with_heart_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_dark_skin_tone_medium_light_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿผ|couple_with_heart_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_dark_skin_tone_medium_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿฝ|couple_with_heart_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_dark_skin_tone_medium_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿพ|couple_with_heart_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ|couple_with_heart_woman_woman_dark_skin_tone ๐Ÿ‘ฉ๐Ÿฟโ€โคโ€๐Ÿ‘ฉ๐Ÿฟ|family_man_woman_boy ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ|family_man_woman_girl ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง|family_man_woman_girl_boy ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_man_woman_boy_boy ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_man_woman_girl_girl ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_man_man_boy ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ|family_man_man_girl ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง|family_man_man_girl_boy ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_man_man_boy_boy ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_man_man_girl_girl ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_woman_woman_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ|family_woman_woman_girl ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง|family_woman_woman_girl_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_woman_woman_boy_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_woman_woman_girl_girl ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_man_boy ๐Ÿ‘จโ€๐Ÿ‘ฆ|family_man_boy_boy ๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_man_girl ๐Ÿ‘จโ€๐Ÿ‘ง|family_man_girl_boy ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_man_girl_girl ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง|family_woman_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฆ|family_woman_boy_boy ๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ|family_woman_girl ๐Ÿ‘ฉโ€๐Ÿ‘ง|family_woman_girl_boy ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ|family_woman_girl_girl ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง|speaking_head ๐Ÿ—ฃ๏ธ|speaking_head ๐Ÿ—ฃ|bust_in_silhouette ๐Ÿ‘ค|busts_in_silhouette ๐Ÿ‘ฅ|people_hugging ๐Ÿซ‚|family ๐Ÿ‘ช|family_adult_adult_child ๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’|family_adult_adult_child_child ๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’|family_adult_child ๐Ÿง‘โ€๐Ÿง’|family_adult_child_child ๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’|footprints ๐Ÿ‘ฃ|light_skin_tone ๐Ÿป|medium_light_skin_tone ๐Ÿผ|medium_skin_tone ๐Ÿฝ|medium_dark_skin_tone ๐Ÿพ|dark_skin_tone ๐Ÿฟ|red_hair ๐Ÿฆฐ|curly_hair ๐Ÿฆฑ|white_hair ๐Ÿฆณ|bald ๐Ÿฆฒ|monkey_face ๐Ÿต|monkey ๐Ÿ’|gorilla ๐Ÿฆ|orangutan ๐Ÿฆง|dog_face ๐Ÿถ|dog ๐Ÿ•|guide_dog ๐Ÿฆฎ|service_dog ๐Ÿ•โ€๐Ÿฆบ|poodle ๐Ÿฉ|wolf ๐Ÿบ|fox ๐ŸฆŠ|raccoon ๐Ÿฆ|cat_face ๐Ÿฑ|cat ๐Ÿˆ|black_cat ๐Ÿˆโ€โฌ›|lion ๐Ÿฆ|tiger_face ๐Ÿฏ|tiger ๐Ÿ…|leopard ๐Ÿ†|horse_face ๐Ÿด|moose ๐ŸซŽ|donkey ๐Ÿซ|horse ๐ŸŽ|unicorn ๐Ÿฆ„|zebra ๐Ÿฆ“|deer ๐ŸฆŒ|bison ๐Ÿฆฌ|cow_face ๐Ÿฎ|ox ๐Ÿ‚|water_buffalo ๐Ÿƒ|cow ๐Ÿ„|pig_face ๐Ÿท|pig ๐Ÿ–|boar ๐Ÿ—|pig_nose ๐Ÿฝ|ram ๐Ÿ|ewe ๐Ÿ‘|goat ๐Ÿ|camel ๐Ÿช|two_hump_camel ๐Ÿซ|llama ๐Ÿฆ™|giraffe ๐Ÿฆ’|elephant ๐Ÿ˜|mammoth ๐Ÿฆฃ|rhinoceros ๐Ÿฆ|hippopotamus ๐Ÿฆ›|mouse_face ๐Ÿญ|mouse ๐Ÿ|rat ๐Ÿ€|hamster ๐Ÿน|rabbit_face ๐Ÿฐ|rabbit ๐Ÿ‡|chipmunk ๐Ÿฟ๏ธ|chipmunk ๐Ÿฟ|beaver ๐Ÿฆซ|hedgehog ๐Ÿฆ”|bat ๐Ÿฆ‡|bear ๐Ÿป|polar_bear ๐Ÿปโ€โ„๏ธ|polar_bear ๐Ÿปโ€โ„|koala ๐Ÿจ|panda ๐Ÿผ|sloth ๐Ÿฆฅ|otter ๐Ÿฆฆ|skunk ๐Ÿฆจ|kangaroo ๐Ÿฆ˜|badger ๐Ÿฆก|paw_prints ๐Ÿพ|turkey ๐Ÿฆƒ|chicken ๐Ÿ”|rooster ๐Ÿ“|hatching_chick ๐Ÿฃ|baby_chick ๐Ÿค|front_facing_baby_chick ๐Ÿฅ|bird ๐Ÿฆ|penguin ๐Ÿง|dove ๐Ÿ•Š๏ธ|dove ๐Ÿ•Š|eagle ๐Ÿฆ…|duck ๐Ÿฆ†|swan ๐Ÿฆข|owl ๐Ÿฆ‰|dodo ๐Ÿฆค|feather ๐Ÿชถ|flamingo ๐Ÿฆฉ|peacock ๐Ÿฆš|parrot ๐Ÿฆœ|wing ๐Ÿชฝ|black_bird ๐Ÿฆโ€โฌ›|goose ๐Ÿชฟ|phoenix ๐Ÿฆโ€๐Ÿ”ฅ|frog ๐Ÿธ|crocodile ๐ŸŠ|turtle ๐Ÿข|lizard ๐ŸฆŽ|snake ๐Ÿ|dragon_face ๐Ÿฒ|dragon ๐Ÿ‰|sauropod ๐Ÿฆ•|t_rex ๐Ÿฆ–|spouting_whale ๐Ÿณ|whale ๐Ÿ‹|dolphin ๐Ÿฌ|seal ๐Ÿฆญ|fish ๐ŸŸ|tropical_fish ๐Ÿ |blowfish ๐Ÿก|shark ๐Ÿฆˆ|octopus ๐Ÿ™|spiral_shell ๐Ÿš|coral ๐Ÿชธ|jellyfish ๐Ÿชผ|snail ๐ŸŒ|butterfly ๐Ÿฆ‹|bug ๐Ÿ›|ant ๐Ÿœ|honeybee ๐Ÿ|beetle ๐Ÿชฒ|lady_beetle ๐Ÿž|cricket ๐Ÿฆ—|cockroach ๐Ÿชณ|spider ๐Ÿ•ท๏ธ|spider ๐Ÿ•ท|spider_web ๐Ÿ•ธ๏ธ|spider_web ๐Ÿ•ธ|scorpion ๐Ÿฆ‚|mosquito ๐ŸฆŸ|fly ๐Ÿชฐ|worm ๐Ÿชฑ|microbe ๐Ÿฆ |bouquet ๐Ÿ’|cherry_blossom ๐ŸŒธ|white_flower ๐Ÿ’ฎ|lotus ๐Ÿชท|rosette ๐Ÿต๏ธ|rosette ๐Ÿต|rose ๐ŸŒน|wilted_flower ๐Ÿฅ€|hibiscus ๐ŸŒบ|sunflower ๐ŸŒป|blossom ๐ŸŒผ|tulip ๐ŸŒท|hyacinth ๐Ÿชป|seedling ๐ŸŒฑ|potted_plant ๐Ÿชด|evergreen_tree ๐ŸŒฒ|deciduous_tree ๐ŸŒณ|palm_tree ๐ŸŒด|cactus ๐ŸŒต|sheaf_of_rice ๐ŸŒพ|herb ๐ŸŒฟ|shamrock โ˜˜๏ธ|shamrock โ˜˜|four_leaf_clover ๐Ÿ€|maple_leaf ๐Ÿ|fallen_leaf ๐Ÿ‚|leaf_fluttering_in_wind ๐Ÿƒ|empty_nest ๐Ÿชน|nest_with_eggs ๐Ÿชบ|mushroom ๐Ÿ„|grapes ๐Ÿ‡|melon ๐Ÿˆ|watermelon ๐Ÿ‰|tangerine ๐ŸŠ|lemon ๐Ÿ‹|lime ๐Ÿ‹โ€๐ŸŸฉ|banana ๐ŸŒ|pineapple ๐Ÿ|mango ๐Ÿฅญ|red_apple ๐ŸŽ|green_apple ๐Ÿ|pear ๐Ÿ|peach ๐Ÿ‘|cherries ๐Ÿ’|strawberry ๐Ÿ“|blueberries ๐Ÿซ|kiwi_fruit ๐Ÿฅ|tomato ๐Ÿ…|olive ๐Ÿซ’|coconut ๐Ÿฅฅ|avocado ๐Ÿฅ‘|eggplant ๐Ÿ†|potato ๐Ÿฅ”|carrot ๐Ÿฅ•|ear_of_corn ๐ŸŒฝ|hot_pepper ๐ŸŒถ๏ธ|hot_pepper ๐ŸŒถ|bell_pepper ๐Ÿซ‘|cucumber ๐Ÿฅ’|leafy_green ๐Ÿฅฌ|broccoli ๐Ÿฅฆ|garlic ๐Ÿง„|onion ๐Ÿง…|peanuts ๐Ÿฅœ|beans ๐Ÿซ˜|chestnut ๐ŸŒฐ|ginger_root ๐Ÿซš|pea_pod ๐Ÿซ›|brown_mushroom ๐Ÿ„โ€๐ŸŸซ|bread ๐Ÿž|croissant ๐Ÿฅ|baguette_bread ๐Ÿฅ–|flatbread ๐Ÿซ“|pretzel ๐Ÿฅจ|bagel ๐Ÿฅฏ|pancakes ๐Ÿฅž|waffle ๐Ÿง‡|cheese_wedge ๐Ÿง€|meat_on_bone ๐Ÿ–|poultry_leg ๐Ÿ—|cut_of_meat ๐Ÿฅฉ|bacon ๐Ÿฅ“|hamburger ๐Ÿ”|french_fries ๐ŸŸ|pizza ๐Ÿ•|hot_dog ๐ŸŒญ|sandwich ๐Ÿฅช|taco ๐ŸŒฎ|burrito ๐ŸŒฏ|tamale ๐Ÿซ”|stuffed_flatbread ๐Ÿฅ™|falafel ๐Ÿง†|egg ๐Ÿฅš|cooking ๐Ÿณ|shallow_pan_of_food ๐Ÿฅ˜|pot_of_food ๐Ÿฒ|fondue ๐Ÿซ•|bowl_with_spoon ๐Ÿฅฃ|green_salad ๐Ÿฅ—|popcorn ๐Ÿฟ|butter ๐Ÿงˆ|salt ๐Ÿง‚|canned_food ๐Ÿฅซ|bento_box ๐Ÿฑ|rice_cracker ๐Ÿ˜|rice_ball ๐Ÿ™|cooked_rice ๐Ÿš|curry_rice ๐Ÿ›|steaming_bowl ๐Ÿœ|spaghetti ๐Ÿ|roasted_sweet_potato ๐Ÿ |oden ๐Ÿข|sushi ๐Ÿฃ|fried_shrimp ๐Ÿค|fish_cake_with_swirl ๐Ÿฅ|moon_cake ๐Ÿฅฎ|dango ๐Ÿก|dumpling ๐ŸฅŸ|fortune_cookie ๐Ÿฅ |takeout_box ๐Ÿฅก|crab ๐Ÿฆ€|lobster ๐Ÿฆž|shrimp ๐Ÿฆ|squid ๐Ÿฆ‘|oyster ๐Ÿฆช|soft_ice_cream ๐Ÿฆ|shaved_ice ๐Ÿง|ice_cream ๐Ÿจ|doughnut ๐Ÿฉ|cookie ๐Ÿช|birthday_cake ๐ŸŽ‚|shortcake ๐Ÿฐ|cupcake ๐Ÿง|pie ๐Ÿฅง|chocolate_bar ๐Ÿซ|candy ๐Ÿฌ|lollipop ๐Ÿญ|custard ๐Ÿฎ|honey_pot ๐Ÿฏ|baby_bottle ๐Ÿผ|glass_of_milk ๐Ÿฅ›|hot_beverage โ˜•|teapot ๐Ÿซ–|teacup_without_handle ๐Ÿต|sake ๐Ÿถ|bottle_with_popping_cork ๐Ÿพ|wine_glass ๐Ÿท|cocktail_glass ๐Ÿธ|tropical_drink ๐Ÿน|beer_mug ๐Ÿบ|clinking_beer_mugs ๐Ÿป|clinking_glasses ๐Ÿฅ‚|tumbler_glass ๐Ÿฅƒ|pouring_liquid ๐Ÿซ—|cup_with_straw ๐Ÿฅค|bubble_tea ๐Ÿง‹|beverage_box ๐Ÿงƒ|mate ๐Ÿง‰|ice ๐ŸงŠ|chopsticks ๐Ÿฅข|fork_and_knife_with_plate ๐Ÿฝ๏ธ|fork_and_knife_with_plate ๐Ÿฝ|fork_and_knife ๐Ÿด|spoon ๐Ÿฅ„|kitchen_knife ๐Ÿ”ช|jar ๐Ÿซ™|amphora ๐Ÿบ|globe_showing_europe_africa ๐ŸŒ|globe_showing_americas ๐ŸŒŽ|globe_showing_asia_australia ๐ŸŒ|globe_with_meridians ๐ŸŒ|world_map ๐Ÿ—บ๏ธ|world_map ๐Ÿ—บ|map_of_japan ๐Ÿ—พ|compass ๐Ÿงญ|snow_capped_mountain ๐Ÿ”๏ธ|snow_capped_mountain ๐Ÿ”|mountain โ›ฐ๏ธ|mountain โ›ฐ|volcano ๐ŸŒ‹|mount_fuji ๐Ÿ—ป|camping ๐Ÿ•๏ธ|camping ๐Ÿ•|beach_with_umbrella ๐Ÿ–๏ธ|beach_with_umbrella ๐Ÿ–|desert ๐Ÿœ๏ธ|desert ๐Ÿœ|desert_island ๐Ÿ๏ธ|desert_island ๐Ÿ|national_park ๐Ÿž๏ธ|national_park ๐Ÿž|stadium ๐ŸŸ๏ธ|stadium ๐ŸŸ|classical_building ๐Ÿ›๏ธ|classical_building ๐Ÿ›|building_construction ๐Ÿ—๏ธ|building_construction ๐Ÿ—|brick ๐Ÿงฑ|rock ๐Ÿชจ|wood ๐Ÿชต|hut ๐Ÿ›–|houses ๐Ÿ˜๏ธ|houses ๐Ÿ˜|derelict_house ๐Ÿš๏ธ|derelict_house ๐Ÿš|house ๐Ÿ |house_with_garden ๐Ÿก|office_building ๐Ÿข|japanese_post_office ๐Ÿฃ|post_office ๐Ÿค|hospital ๐Ÿฅ|bank ๐Ÿฆ|hotel ๐Ÿจ|love_hotel ๐Ÿฉ|convenience_store ๐Ÿช|school ๐Ÿซ|department_store ๐Ÿฌ|factory ๐Ÿญ|japanese_castle ๐Ÿฏ|castle ๐Ÿฐ|wedding ๐Ÿ’’|tokyo_tower ๐Ÿ—ผ|statue_of_liberty ๐Ÿ—ฝ|church โ›ช|mosque ๐Ÿ•Œ|hindu_temple ๐Ÿ›•|synagogue ๐Ÿ•|shinto_shrine โ›ฉ๏ธ|shinto_shrine โ›ฉ|kaaba ๐Ÿ•‹|fountain โ›ฒ|tent โ›บ|foggy ๐ŸŒ|night_with_stars ๐ŸŒƒ|cityscape ๐Ÿ™๏ธ|cityscape ๐Ÿ™|sunrise_over_mountains ๐ŸŒ„|sunrise ๐ŸŒ…|cityscape_at_dusk ๐ŸŒ†|sunset ๐ŸŒ‡|bridge_at_night ๐ŸŒ‰|hot_springs โ™จ๏ธ|hot_springs โ™จ|carousel_horse ๐ŸŽ |playground_slide ๐Ÿ›|ferris_wheel ๐ŸŽก|roller_coaster ๐ŸŽข|barber_pole ๐Ÿ’ˆ|circus_tent ๐ŸŽช|locomotive ๐Ÿš‚|railway_car ๐Ÿšƒ|high_speed_train ๐Ÿš„|bullet_train ๐Ÿš…|train ๐Ÿš†|metro ๐Ÿš‡|light_rail ๐Ÿšˆ|station ๐Ÿš‰|tram ๐ŸšŠ|monorail ๐Ÿš|mountain_railway ๐Ÿšž|tram_car ๐Ÿš‹|bus ๐ŸšŒ|oncoming_bus ๐Ÿš|trolleybus ๐ŸšŽ|minibus ๐Ÿš|ambulance ๐Ÿš‘|fire_engine ๐Ÿš’|police_car ๐Ÿš“|oncoming_police_car ๐Ÿš”|taxi ๐Ÿš•|oncoming_taxi ๐Ÿš–|automobile ๐Ÿš—|oncoming_automobile ๐Ÿš˜|sport_utility_vehicle ๐Ÿš™|pickup_truck ๐Ÿ›ป|delivery_truck ๐Ÿšš|articulated_lorry ๐Ÿš›|tractor ๐Ÿšœ|racing_car ๐ŸŽ๏ธ|racing_car ๐ŸŽ|motorcycle ๐Ÿ๏ธ|motorcycle ๐Ÿ|motor_scooter ๐Ÿ›ต|manual_wheelchair ๐Ÿฆฝ|motorized_wheelchair ๐Ÿฆผ|auto_rickshaw ๐Ÿ›บ|bicycle ๐Ÿšฒ|kick_scooter ๐Ÿ›ด|skateboard ๐Ÿ›น|roller_skate ๐Ÿ›ผ|bus_stop ๐Ÿš|motorway ๐Ÿ›ฃ๏ธ|motorway ๐Ÿ›ฃ|railway_track ๐Ÿ›ค๏ธ|railway_track ๐Ÿ›ค|oil_drum ๐Ÿ›ข๏ธ|oil_drum ๐Ÿ›ข|fuel_pump โ›ฝ|wheel ๐Ÿ›ž|police_car_light ๐Ÿšจ|horizontal_traffic_light ๐Ÿšฅ|vertical_traffic_light ๐Ÿšฆ|stop_sign ๐Ÿ›‘|construction ๐Ÿšง|anchor โš“|ring_buoy ๐Ÿ›Ÿ|sailboat โ›ต|canoe ๐Ÿ›ถ|speedboat ๐Ÿšค|passenger_ship ๐Ÿ›ณ๏ธ|passenger_ship ๐Ÿ›ณ|ferry โ›ด๏ธ|ferry โ›ด|motor_boat ๐Ÿ›ฅ๏ธ|motor_boat ๐Ÿ›ฅ|ship ๐Ÿšข|airplane โœˆ๏ธ|airplane โœˆ|small_airplane ๐Ÿ›ฉ๏ธ|small_airplane ๐Ÿ›ฉ|airplane_departure ๐Ÿ›ซ|airplane_arrival ๐Ÿ›ฌ|parachute ๐Ÿช‚|seat ๐Ÿ’บ|helicopter ๐Ÿš|suspension_railway ๐ŸšŸ|mountain_cableway ๐Ÿš |aerial_tramway ๐Ÿšก|satellite ๐Ÿ›ฐ๏ธ|satellite ๐Ÿ›ฐ|rocket ๐Ÿš€|flying_saucer ๐Ÿ›ธ|bellhop_bell ๐Ÿ›Ž๏ธ|bellhop_bell ๐Ÿ›Ž|luggage ๐Ÿงณ|hourglass_done โŒ›|hourglass_not_done โณ|watch โŒš|alarm_clock โฐ|stopwatch โฑ๏ธ|stopwatch โฑ|timer_clock โฒ๏ธ|timer_clock โฒ|mantelpiece_clock ๐Ÿ•ฐ๏ธ|mantelpiece_clock ๐Ÿ•ฐ|twelve_o_clock ๐Ÿ•›|twelve_thirty ๐Ÿ•ง|one_o_clock ๐Ÿ•|one_thirty ๐Ÿ•œ|two_o_clock ๐Ÿ•‘|two_thirty ๐Ÿ•|three_o_clock ๐Ÿ•’|three_thirty ๐Ÿ•ž|four_o_clock ๐Ÿ•“|four_thirty ๐Ÿ•Ÿ|five_o_clock ๐Ÿ•”|five_thirty ๐Ÿ• |six_o_clock ๐Ÿ••|six_thirty ๐Ÿ•ก|seven_o_clock ๐Ÿ•–|seven_thirty ๐Ÿ•ข|eight_o_clock ๐Ÿ•—|eight_thirty ๐Ÿ•ฃ|nine_o_clock ๐Ÿ•˜|nine_thirty ๐Ÿ•ค|ten_o_clock ๐Ÿ•™|ten_thirty ๐Ÿ•ฅ|eleven_o_clock ๐Ÿ•š|eleven_thirty ๐Ÿ•ฆ|new_moon ๐ŸŒ‘|waxing_crescent_moon ๐ŸŒ’|first_quarter_moon ๐ŸŒ“|waxing_gibbous_moon ๐ŸŒ”|full_moon ๐ŸŒ•|waning_gibbous_moon ๐ŸŒ–|last_quarter_moon ๐ŸŒ—|waning_crescent_moon ๐ŸŒ˜|crescent_moon ๐ŸŒ™|new_moon_face ๐ŸŒš|first_quarter_moon_face ๐ŸŒ›|last_quarter_moon_face ๐ŸŒœ|thermometer ๐ŸŒก๏ธ|thermometer ๐ŸŒก|sun โ˜€๏ธ|sun โ˜€|full_moon_face ๐ŸŒ|sun_with_face ๐ŸŒž|ringed_planet ๐Ÿช|star โญ|glowing_star ๐ŸŒŸ|shooting_star ๐ŸŒ |milky_way ๐ŸŒŒ|cloud โ˜๏ธ|cloud โ˜|sun_behind_cloud โ›…|cloud_with_lightning_and_rain โ›ˆ๏ธ|cloud_with_lightning_and_rain โ›ˆ|sun_behind_small_cloud ๐ŸŒค๏ธ|sun_behind_small_cloud ๐ŸŒค|sun_behind_large_cloud ๐ŸŒฅ๏ธ|sun_behind_large_cloud ๐ŸŒฅ|sun_behind_rain_cloud ๐ŸŒฆ๏ธ|sun_behind_rain_cloud ๐ŸŒฆ|cloud_with_rain ๐ŸŒง๏ธ|cloud_with_rain ๐ŸŒง|cloud_with_snow ๐ŸŒจ๏ธ|cloud_with_snow ๐ŸŒจ|cloud_with_lightning ๐ŸŒฉ๏ธ|cloud_with_lightning ๐ŸŒฉ|tornado ๐ŸŒช๏ธ|tornado ๐ŸŒช|fog ๐ŸŒซ๏ธ|fog ๐ŸŒซ|wind_face ๐ŸŒฌ๏ธ|wind_face ๐ŸŒฌ|cyclone ๐ŸŒ€|rainbow ๐ŸŒˆ|closed_umbrella ๐ŸŒ‚|umbrella โ˜‚๏ธ|umbrella โ˜‚|umbrella_with_rain_drops โ˜”|umbrella_on_ground โ›ฑ๏ธ|umbrella_on_ground โ›ฑ|high_voltage โšก|snowflake โ„๏ธ|snowflake โ„|snowman โ˜ƒ๏ธ|snowman โ˜ƒ|snowman_without_snow โ›„|comet โ˜„๏ธ|comet โ˜„|fire ๐Ÿ”ฅ|droplet ๐Ÿ’ง|water_wave ๐ŸŒŠ|jack_o_lantern ๐ŸŽƒ|christmas_tree ๐ŸŽ„|fireworks ๐ŸŽ†|sparkler ๐ŸŽ‡|firecracker ๐Ÿงจ|sparkles โœจ|balloon ๐ŸŽˆ|party_popper ๐ŸŽ‰|confetti_ball ๐ŸŽŠ|tanabata_tree ๐ŸŽ‹|pine_decoration ๐ŸŽ|japanese_dolls ๐ŸŽŽ|carp_streamer ๐ŸŽ|wind_chime ๐ŸŽ|moon_viewing_ceremony ๐ŸŽ‘|red_envelope ๐Ÿงง|ribbon ๐ŸŽ€|wrapped_gift ๐ŸŽ|reminder_ribbon ๐ŸŽ—๏ธ|reminder_ribbon ๐ŸŽ—|admission_tickets ๐ŸŽŸ๏ธ|admission_tickets ๐ŸŽŸ|ticket ๐ŸŽซ|military_medal ๐ŸŽ–๏ธ|military_medal ๐ŸŽ–|trophy ๐Ÿ†|sports_medal ๐Ÿ…|1st_place_medal ๐Ÿฅ‡|2nd_place_medal ๐Ÿฅˆ|3rd_place_medal ๐Ÿฅ‰|soccer_ball โšฝ|baseball โšพ|softball ๐ŸฅŽ|basketball ๐Ÿ€|volleyball ๐Ÿ|american_football ๐Ÿˆ|rugby_football ๐Ÿ‰|tennis ๐ŸŽพ|flying_disc ๐Ÿฅ|bowling ๐ŸŽณ|cricket_game ๐Ÿ|field_hockey ๐Ÿ‘|ice_hockey ๐Ÿ’|lacrosse ๐Ÿฅ|ping_pong ๐Ÿ“|badminton ๐Ÿธ|boxing_glove ๐ŸฅŠ|martial_arts_uniform ๐Ÿฅ‹|goal_net ๐Ÿฅ…|flag_in_hole โ›ณ|ice_skate โ›ธ๏ธ|ice_skate โ›ธ|fishing_pole ๐ŸŽฃ|diving_mask ๐Ÿคฟ|running_shirt ๐ŸŽฝ|skis ๐ŸŽฟ|sled ๐Ÿ›ท|curling_stone ๐ŸฅŒ|bullseye ๐ŸŽฏ|yo_yo ๐Ÿช€|kite ๐Ÿช|water_pistol ๐Ÿ”ซ|pool_8_ball ๐ŸŽฑ|crystal_ball ๐Ÿ”ฎ|magic_wand ๐Ÿช„|video_game ๐ŸŽฎ|joystick ๐Ÿ•น๏ธ|joystick ๐Ÿ•น|slot_machine ๐ŸŽฐ|game_die ๐ŸŽฒ|puzzle_piece ๐Ÿงฉ|teddy_bear ๐Ÿงธ|pi_ata ๐Ÿช…|mirror_ball ๐Ÿชฉ|nesting_dolls ๐Ÿช†|spade_suit โ™ ๏ธ|spade_suit โ™ |heart_suit โ™ฅ๏ธ|heart_suit โ™ฅ|diamond_suit โ™ฆ๏ธ|diamond_suit โ™ฆ|club_suit โ™ฃ๏ธ|club_suit โ™ฃ|chess_pawn โ™Ÿ๏ธ|chess_pawn โ™Ÿ|joker ๐Ÿƒ|mahjong_red_dragon ๐Ÿ€„|flower_playing_cards ๐ŸŽด|performing_arts ๐ŸŽญ|framed_picture ๐Ÿ–ผ๏ธ|framed_picture ๐Ÿ–ผ|artist_palette ๐ŸŽจ|thread ๐Ÿงต|sewing_needle ๐Ÿชก|yarn ๐Ÿงถ|knot ๐Ÿชข|glasses ๐Ÿ‘“|sunglasses ๐Ÿ•ถ๏ธ|sunglasses ๐Ÿ•ถ|goggles ๐Ÿฅฝ|lab_coat ๐Ÿฅผ|safety_vest ๐Ÿฆบ|necktie ๐Ÿ‘”|t_shirt ๐Ÿ‘•|jeans ๐Ÿ‘–|scarf ๐Ÿงฃ|gloves ๐Ÿงค|coat ๐Ÿงฅ|socks ๐Ÿงฆ|dress ๐Ÿ‘—|kimono ๐Ÿ‘˜|sari ๐Ÿฅป|one_piece_swimsuit ๐Ÿฉฑ|briefs ๐Ÿฉฒ|shorts ๐Ÿฉณ|bikini ๐Ÿ‘™|woman_s_clothes ๐Ÿ‘š|folding_hand_fan ๐Ÿชญ|purse ๐Ÿ‘›|handbag ๐Ÿ‘œ|clutch_bag ๐Ÿ‘|shopping_bags ๐Ÿ›๏ธ|shopping_bags ๐Ÿ›|backpack ๐ŸŽ’|thong_sandal ๐Ÿฉด|man_s_shoe ๐Ÿ‘ž|running_shoe ๐Ÿ‘Ÿ|hiking_boot ๐Ÿฅพ|flat_shoe ๐Ÿฅฟ|high_heeled_shoe ๐Ÿ‘ |woman_s_sandal ๐Ÿ‘ก|ballet_shoes ๐Ÿฉฐ|woman_s_boot ๐Ÿ‘ข|hair_pick ๐Ÿชฎ|crown ๐Ÿ‘‘|woman_s_hat ๐Ÿ‘’|top_hat ๐ŸŽฉ|graduation_cap ๐ŸŽ“|billed_cap ๐Ÿงข|military_helmet ๐Ÿช–|rescue_worker_s_helmet โ›‘๏ธ|rescue_worker_s_helmet โ›‘|prayer_beads ๐Ÿ“ฟ|lipstick ๐Ÿ’„|ring ๐Ÿ’|gem_stone ๐Ÿ’Ž|muted_speaker ๐Ÿ”‡|speaker_low_volume ๐Ÿ”ˆ|speaker_medium_volume ๐Ÿ”‰|speaker_high_volume ๐Ÿ”Š|loudspeaker ๐Ÿ“ข|megaphone ๐Ÿ“ฃ|postal_horn ๐Ÿ“ฏ|bell ๐Ÿ””|bell_with_slash ๐Ÿ”•|musical_score ๐ŸŽผ|musical_note ๐ŸŽต|musical_notes ๐ŸŽถ|studio_microphone ๐ŸŽ™๏ธ|studio_microphone ๐ŸŽ™|level_slider ๐ŸŽš๏ธ|level_slider ๐ŸŽš|control_knobs ๐ŸŽ›๏ธ|control_knobs ๐ŸŽ›|microphone ๐ŸŽค|headphone ๐ŸŽง|radio ๐Ÿ“ป|saxophone ๐ŸŽท|accordion ๐Ÿช—|guitar ๐ŸŽธ|musical_keyboard ๐ŸŽน|trumpet ๐ŸŽบ|violin ๐ŸŽป|banjo ๐Ÿช•|drum ๐Ÿฅ|long_drum ๐Ÿช˜|maracas ๐Ÿช‡|flute ๐Ÿชˆ|mobile_phone ๐Ÿ“ฑ|mobile_phone_with_arrow ๐Ÿ“ฒ|telephone โ˜Ž๏ธ|telephone โ˜Ž|telephone_receiver ๐Ÿ“ž|pager ๐Ÿ“Ÿ|fax_machine ๐Ÿ“ |battery ๐Ÿ”‹|low_battery ๐Ÿชซ|electric_plug ๐Ÿ”Œ|laptop ๐Ÿ’ป|desktop_computer ๐Ÿ–ฅ๏ธ|desktop_computer ๐Ÿ–ฅ|printer ๐Ÿ–จ๏ธ|printer ๐Ÿ–จ|keyboard โŒจ๏ธ|keyboard โŒจ|computer_mouse ๐Ÿ–ฑ๏ธ|computer_mouse ๐Ÿ–ฑ|trackball ๐Ÿ–ฒ๏ธ|trackball ๐Ÿ–ฒ|computer_disk ๐Ÿ’ฝ|floppy_disk ๐Ÿ’พ|optical_disk ๐Ÿ’ฟ|dvd ๐Ÿ“€|abacus ๐Ÿงฎ|movie_camera ๐ŸŽฅ|film_frames ๐ŸŽž๏ธ|film_frames ๐ŸŽž|film_projector ๐Ÿ“ฝ๏ธ|film_projector ๐Ÿ“ฝ|clapper_board ๐ŸŽฌ|television ๐Ÿ“บ|camera ๐Ÿ“ท|camera_with_flash ๐Ÿ“ธ|video_camera ๐Ÿ“น|videocassette ๐Ÿ“ผ|magnifying_glass_tilted_left ๐Ÿ”|magnifying_glass_tilted_right ๐Ÿ”Ž|candle ๐Ÿ•ฏ๏ธ|candle ๐Ÿ•ฏ|light_bulb ๐Ÿ’ก|flashlight ๐Ÿ”ฆ|red_paper_lantern ๐Ÿฎ|diya_lamp ๐Ÿช”|notebook_with_decorative_cover ๐Ÿ“”|closed_book ๐Ÿ“•|open_book ๐Ÿ“–|green_book ๐Ÿ“—|blue_book ๐Ÿ“˜|orange_book ๐Ÿ“™|books ๐Ÿ“š|notebook ๐Ÿ““|ledger ๐Ÿ“’|page_with_curl ๐Ÿ“ƒ|scroll ๐Ÿ“œ|page_facing_up ๐Ÿ“„|newspaper ๐Ÿ“ฐ|rolled_up_newspaper ๐Ÿ—ž๏ธ|rolled_up_newspaper ๐Ÿ—ž|bookmark_tabs ๐Ÿ“‘|bookmark ๐Ÿ”–|label ๐Ÿท๏ธ|label ๐Ÿท|money_bag ๐Ÿ’ฐ|coin ๐Ÿช™|yen_banknote ๐Ÿ’ด|dollar_banknote ๐Ÿ’ต|euro_banknote ๐Ÿ’ถ|pound_banknote ๐Ÿ’ท|money_with_wings ๐Ÿ’ธ|credit_card ๐Ÿ’ณ|receipt ๐Ÿงพ|chart_increasing_with_yen ๐Ÿ’น|envelope โœ‰๏ธ|envelope โœ‰|e_mail ๐Ÿ“ง|incoming_envelope ๐Ÿ“จ|envelope_with_arrow ๐Ÿ“ฉ|outbox_tray ๐Ÿ“ค|inbox_tray ๐Ÿ“ฅ|package ๐Ÿ“ฆ|closed_mailbox_with_raised_flag ๐Ÿ“ซ|closed_mailbox_with_lowered_flag ๐Ÿ“ช|open_mailbox_with_raised_flag ๐Ÿ“ฌ|open_mailbox_with_lowered_flag ๐Ÿ“ญ|postbox ๐Ÿ“ฎ|ballot_box_with_ballot ๐Ÿ—ณ๏ธ|ballot_box_with_ballot ๐Ÿ—ณ|pencil โœ๏ธ|pencil โœ|black_nib โœ’๏ธ|black_nib โœ’|fountain_pen ๐Ÿ–‹๏ธ|fountain_pen ๐Ÿ–‹|pen ๐Ÿ–Š๏ธ|pen ๐Ÿ–Š|paintbrush ๐Ÿ–Œ๏ธ|paintbrush ๐Ÿ–Œ|crayon ๐Ÿ–๏ธ|crayon ๐Ÿ–|memo ๐Ÿ“|briefcase ๐Ÿ’ผ|file_folder ๐Ÿ“|open_file_folder ๐Ÿ“‚|card_index_dividers ๐Ÿ—‚๏ธ|card_index_dividers ๐Ÿ—‚|calendar ๐Ÿ“…|tear_off_calendar ๐Ÿ“†|spiral_notepad ๐Ÿ—’๏ธ|spiral_notepad ๐Ÿ—’|spiral_calendar ๐Ÿ—“๏ธ|spiral_calendar ๐Ÿ—“|card_index ๐Ÿ“‡|chart_increasing ๐Ÿ“ˆ|chart_decreasing ๐Ÿ“‰|bar_chart ๐Ÿ“Š|clipboard ๐Ÿ“‹|pushpin ๐Ÿ“Œ|round_pushpin ๐Ÿ“|paperclip ๐Ÿ“Ž|linked_paperclips ๐Ÿ–‡๏ธ|linked_paperclips ๐Ÿ–‡|straight_ruler ๐Ÿ“|triangular_ruler ๐Ÿ“|scissors โœ‚๏ธ|scissors โœ‚|card_file_box ๐Ÿ—ƒ๏ธ|card_file_box ๐Ÿ—ƒ|file_cabinet ๐Ÿ—„๏ธ|file_cabinet ๐Ÿ—„|wastebasket ๐Ÿ—‘๏ธ|wastebasket ๐Ÿ—‘|locked ๐Ÿ”’|unlocked ๐Ÿ”“|locked_with_pen ๐Ÿ”|locked_with_key ๐Ÿ”|key ๐Ÿ”‘|old_key ๐Ÿ—๏ธ|old_key ๐Ÿ—|hammer ๐Ÿ”จ|axe ๐Ÿช“|pick โ›๏ธ|pick โ›|hammer_and_pick โš’๏ธ|hammer_and_pick โš’|hammer_and_wrench ๐Ÿ› ๏ธ|hammer_and_wrench ๐Ÿ› |dagger ๐Ÿ—ก๏ธ|dagger ๐Ÿ—ก|crossed_swords โš”๏ธ|crossed_swords โš”|bomb ๐Ÿ’ฃ|boomerang ๐Ÿชƒ|bow_and_arrow ๐Ÿน|shield ๐Ÿ›ก๏ธ|shield ๐Ÿ›ก|carpentry_saw ๐Ÿชš|wrench ๐Ÿ”ง|screwdriver ๐Ÿช›|nut_and_bolt ๐Ÿ”ฉ|gear โš™๏ธ|gear โš™|clamp ๐Ÿ—œ๏ธ|clamp ๐Ÿ—œ|balance_scale โš–๏ธ|balance_scale โš–|white_cane ๐Ÿฆฏ|link ๐Ÿ”—|broken_chain โ›“๏ธโ€๐Ÿ’ฅ|broken_chain โ›“โ€๐Ÿ’ฅ|chains โ›“๏ธ|chains โ›“|hook ๐Ÿช|toolbox ๐Ÿงฐ|magnet ๐Ÿงฒ|ladder ๐Ÿชœ|alembic โš—๏ธ|alembic โš—|test_tube ๐Ÿงช|petri_dish ๐Ÿงซ|dna ๐Ÿงฌ|microscope ๐Ÿ”ฌ|telescope ๐Ÿ”ญ|satellite_antenna ๐Ÿ“ก|syringe ๐Ÿ’‰|drop_of_blood ๐Ÿฉธ|pill ๐Ÿ’Š|adhesive_bandage ๐Ÿฉน|crutch ๐Ÿฉผ|stethoscope ๐Ÿฉบ|x_ray ๐Ÿฉป|door ๐Ÿšช|elevator ๐Ÿ›—|mirror ๐Ÿชž|window ๐ŸชŸ|bed ๐Ÿ›๏ธ|bed ๐Ÿ›|couch_and_lamp ๐Ÿ›‹๏ธ|couch_and_lamp ๐Ÿ›‹|chair ๐Ÿช‘|toilet ๐Ÿšฝ|plunger ๐Ÿช |shower ๐Ÿšฟ|bathtub ๐Ÿ›|mouse_trap ๐Ÿชค|razor ๐Ÿช’|lotion_bottle ๐Ÿงด|safety_pin ๐Ÿงท|broom ๐Ÿงน|basket ๐Ÿงบ|roll_of_paper ๐Ÿงป|bucket ๐Ÿชฃ|soap ๐Ÿงผ|bubbles ๐Ÿซง|toothbrush ๐Ÿชฅ|sponge ๐Ÿงฝ|fire_extinguisher ๐Ÿงฏ|shopping_cart ๐Ÿ›’|cigarette ๐Ÿšฌ|coffin โšฐ๏ธ|coffin โšฐ|headstone ๐Ÿชฆ|funeral_urn โšฑ๏ธ|funeral_urn โšฑ|nazar_amulet ๐Ÿงฟ|hamsa ๐Ÿชฌ|moai ๐Ÿ—ฟ|placard ๐Ÿชง|identification_card ๐Ÿชช|atm_sign ๐Ÿง|litter_in_bin_sign ๐Ÿšฎ|potable_water ๐Ÿšฐ|wheelchair_symbol โ™ฟ|men_s_room ๐Ÿšน|women_s_room ๐Ÿšบ|restroom ๐Ÿšป|baby_symbol ๐Ÿšผ|water_closet ๐Ÿšพ|passport_control ๐Ÿ›‚|customs ๐Ÿ›ƒ|baggage_claim ๐Ÿ›„|left_luggage ๐Ÿ›…|warning โš ๏ธ|warning โš |children_crossing ๐Ÿšธ|no_entry โ›”|prohibited ๐Ÿšซ|no_bicycles ๐Ÿšณ|no_smoking ๐Ÿšญ|no_littering ๐Ÿšฏ|non_potable_water ๐Ÿšฑ|no_pedestrians ๐Ÿšท|no_mobile_phones ๐Ÿ“ต|no_one_under_eighteen ๐Ÿ”ž|radioactive โ˜ข๏ธ|radioactive โ˜ข|biohazard โ˜ฃ๏ธ|biohazard โ˜ฃ|up_arrow โฌ†๏ธ|up_arrow โฌ†|up_right_arrow โ†—๏ธ|up_right_arrow โ†—|right_arrow โžก๏ธ|right_arrow โžก|down_right_arrow โ†˜๏ธ|down_right_arrow โ†˜|down_arrow โฌ‡๏ธ|down_arrow โฌ‡|down_left_arrow โ†™๏ธ|down_left_arrow โ†™|left_arrow โฌ…๏ธ|left_arrow โฌ…|up_left_arrow โ†–๏ธ|up_left_arrow โ†–|up_down_arrow โ†•๏ธ|up_down_arrow โ†•|left_right_arrow โ†”๏ธ|left_right_arrow โ†”|right_arrow_curving_left โ†ฉ๏ธ|right_arrow_curving_left โ†ฉ|left_arrow_curving_right โ†ช๏ธ|left_arrow_curving_right โ†ช|right_arrow_curving_up โคด๏ธ|right_arrow_curving_up โคด|right_arrow_curving_down โคต๏ธ|right_arrow_curving_down โคต|clockwise_vertical_arrows ๐Ÿ”ƒ|counterclockwise_arrows_button ๐Ÿ”„|back_arrow ๐Ÿ”™|end_arrow ๐Ÿ”š|on_arrow ๐Ÿ”›|soon_arrow ๐Ÿ”œ|top_arrow ๐Ÿ”|place_of_worship ๐Ÿ›|atom_symbol โš›๏ธ|atom_symbol โš›|om ๐Ÿ•‰๏ธ|om ๐Ÿ•‰|star_of_david โœก๏ธ|star_of_david โœก|wheel_of_dharma โ˜ธ๏ธ|wheel_of_dharma โ˜ธ|yin_yang โ˜ฏ๏ธ|yin_yang โ˜ฏ|latin_cross โœ๏ธ|latin_cross โœ|orthodox_cross โ˜ฆ๏ธ|orthodox_cross โ˜ฆ|star_and_crescent โ˜ช๏ธ|star_and_crescent โ˜ช|peace_symbol โ˜ฎ๏ธ|peace_symbol โ˜ฎ|menorah ๐Ÿ•Ž|dotted_six_pointed_star ๐Ÿ”ฏ|khanda ๐Ÿชฏ|aries โ™ˆ|taurus โ™‰|gemini โ™Š|cancer โ™‹|leo โ™Œ|virgo โ™|libra โ™Ž|scorpio โ™|sagittarius โ™|capricorn โ™‘|aquarius โ™’|pisces โ™“|ophiuchus โ›Ž|shuffle_tracks_button ๐Ÿ”€|repeat_button ๐Ÿ”|repeat_single_button ๐Ÿ”‚|play_button โ–ถ๏ธ|play_button โ–ถ|fast_forward_button โฉ|next_track_button โญ๏ธ|next_track_button โญ|play_or_pause_button โฏ๏ธ|play_or_pause_button โฏ|reverse_button โ—€๏ธ|reverse_button โ—€|fast_reverse_button โช|last_track_button โฎ๏ธ|last_track_button โฎ|upwards_button ๐Ÿ”ผ|fast_up_button โซ|downwards_button ๐Ÿ”ฝ|fast_down_button โฌ|pause_button โธ๏ธ|pause_button โธ|stop_button โน๏ธ|stop_button โน|record_button โบ๏ธ|record_button โบ|eject_button โ๏ธ|eject_button โ|cinema ๐ŸŽฆ|dim_button ๐Ÿ”…|bright_button ๐Ÿ”†|antenna_bars ๐Ÿ“ถ|wireless ๐Ÿ›œ|vibration_mode ๐Ÿ“ณ|mobile_phone_off ๐Ÿ“ด|female_sign โ™€๏ธ|female_sign โ™€|male_sign โ™‚๏ธ|male_sign โ™‚|transgender_symbol โšง๏ธ|transgender_symbol โšง|multiply โœ–๏ธ|multiply โœ–|plus โž•|minus โž–|divide โž—|heavy_equals_sign ๐ŸŸฐ|infinity โ™พ๏ธ|infinity โ™พ|double_exclamation_mark โ€ผ๏ธ|double_exclamation_mark โ€ผ|exclamation_question_mark โ‰๏ธ|exclamation_question_mark โ‰|red_question_mark โ“|white_question_mark โ”|white_exclamation_mark โ•|red_exclamation_mark โ—|wavy_dash ใ€ฐ๏ธ|wavy_dash ใ€ฐ|currency_exchange ๐Ÿ’ฑ|heavy_dollar_sign ๐Ÿ’ฒ|medical_symbol โš•๏ธ|medical_symbol โš•|recycling_symbol โ™ป๏ธ|recycling_symbol โ™ป|fleur_de_lis โšœ๏ธ|fleur_de_lis โšœ|trident_emblem ๐Ÿ”ฑ|name_badge ๐Ÿ“›|japanese_symbol_for_beginner ๐Ÿ”ฐ|hollow_red_circle โญ•|check_mark_button โœ…|check_box_with_check โ˜‘๏ธ|check_box_with_check โ˜‘|check_mark โœ”๏ธ|check_mark โœ”|cross_mark โŒ|cross_mark_button โŽ|curly_loop โžฐ|double_curly_loop โžฟ|part_alternation_mark ใ€ฝ๏ธ|part_alternation_mark ใ€ฝ|eight_spoked_asterisk โœณ๏ธ|eight_spoked_asterisk โœณ|eight_pointed_star โœด๏ธ|eight_pointed_star โœด|sparkle โ‡๏ธ|sparkle โ‡|copyright ยฉ๏ธ|copyright ยฉ|registered ยฎ๏ธ|registered ยฎ|trade_mark โ„ข๏ธ|trade_mark โ„ข|keycap_ #๏ธโƒฃ|keycap_ #โƒฃ|keycap_ *๏ธโƒฃ|keycap_ *โƒฃ|keycap_0 0๏ธโƒฃ|keycap_0 0โƒฃ|keycap_1 1๏ธโƒฃ|keycap_1 1โƒฃ|keycap_2 2๏ธโƒฃ|keycap_2 2โƒฃ|keycap_3 3๏ธโƒฃ|keycap_3 3โƒฃ|keycap_4 4๏ธโƒฃ|keycap_4 4โƒฃ|keycap_5 5๏ธโƒฃ|keycap_5 5โƒฃ|keycap_6 6๏ธโƒฃ|keycap_6 6โƒฃ|keycap_7 7๏ธโƒฃ|keycap_7 7โƒฃ|keycap_8 8๏ธโƒฃ|keycap_8 8โƒฃ|keycap_9 9๏ธโƒฃ|keycap_9 9โƒฃ|keycap_10 ๐Ÿ”Ÿ|input_latin_uppercase ๐Ÿ” |input_latin_lowercase ๐Ÿ”ก|input_numbers ๐Ÿ”ข|input_symbols ๐Ÿ”ฃ|input_latin_letters ๐Ÿ”ค|a_button_blood_type_ ๐Ÿ…ฐ๏ธ|a_button_blood_type_ ๐Ÿ…ฐ|ab_button_blood_type_ ๐Ÿ†Ž|b_button_blood_type_ ๐Ÿ…ฑ๏ธ|b_button_blood_type_ ๐Ÿ…ฑ|cl_button ๐Ÿ†‘|cool_button ๐Ÿ†’|free_button ๐Ÿ†“|information โ„น๏ธ|information โ„น|id_button ๐Ÿ†”|circled_m โ“‚๏ธ|circled_m โ“‚|new_button ๐Ÿ†•|ng_button ๐Ÿ†–|o_button_blood_type_ ๐Ÿ…พ๏ธ|o_button_blood_type_ ๐Ÿ…พ|ok_button ๐Ÿ†—|p_button ๐Ÿ…ฟ๏ธ|p_button ๐Ÿ…ฟ|sos_button ๐Ÿ†˜|up_button ๐Ÿ†™|vs_button ๐Ÿ†š|japanese_here_button ๐Ÿˆ|japanese_service_charge_button ๐Ÿˆ‚๏ธ|japanese_service_charge_button ๐Ÿˆ‚|japanese_monthly_amount_button ๐Ÿˆท๏ธ|japanese_monthly_amount_button ๐Ÿˆท|japanese_not_free_of_charge_button ๐Ÿˆถ|japanese_reserved_button ๐Ÿˆฏ|japanese_bargain_button ๐Ÿ‰|japanese_discount_button ๐Ÿˆน|japanese_free_of_charge_button ๐Ÿˆš|japanese_prohibited_button ๐Ÿˆฒ|japanese_acceptable_button ๐Ÿ‰‘|japanese_application_button ๐Ÿˆธ|japanese_passing_grade_button ๐Ÿˆด|japanese_vacancy_button ๐Ÿˆณ|japanese_congratulations_button ใŠ—๏ธ|japanese_congratulations_button ใŠ—|japanese_secret_button ใŠ™๏ธ|japanese_secret_button ใŠ™|japanese_open_for_business_button ๐Ÿˆบ|japanese_no_vacancy_button ๐Ÿˆต|red_circle ๐Ÿ”ด|orange_circle ๐ŸŸ |yellow_circle ๐ŸŸก|green_circle ๐ŸŸข|blue_circle ๐Ÿ”ต|purple_circle ๐ŸŸฃ|brown_circle ๐ŸŸค|black_circle โšซ|white_circle โšช|red_square ๐ŸŸฅ|orange_square ๐ŸŸง|yellow_square ๐ŸŸจ|green_square ๐ŸŸฉ|blue_square ๐ŸŸฆ|purple_square ๐ŸŸช|brown_square ๐ŸŸซ|black_large_square โฌ›|white_large_square โฌœ|black_medium_square โ—ผ๏ธ|black_medium_square โ—ผ|white_medium_square โ—ป๏ธ|white_medium_square โ—ป|black_medium_small_square โ—พ|white_medium_small_square โ—ฝ|black_small_square โ–ช๏ธ|black_small_square โ–ช|white_small_square โ–ซ๏ธ|white_small_square โ–ซ|large_orange_diamond ๐Ÿ”ถ|large_blue_diamond ๐Ÿ”ท|small_orange_diamond ๐Ÿ”ธ|small_blue_diamond ๐Ÿ”น|red_triangle_pointed_up ๐Ÿ”บ|red_triangle_pointed_down ๐Ÿ”ป|diamond_with_a_dot ๐Ÿ’ |radio_button ๐Ÿ”˜|white_square_button ๐Ÿ”ณ|black_square_button ๐Ÿ”ฒ|chequered_flag ๐Ÿ|triangular_flag ๐Ÿšฉ|crossed_flags ๐ŸŽŒ|black_flag ๐Ÿด|white_flag ๐Ÿณ๏ธ|white_flag ๐Ÿณ|rainbow_flag ๐Ÿณ๏ธโ€๐ŸŒˆ|rainbow_flag ๐Ÿณโ€๐ŸŒˆ|transgender_flag ๐Ÿณ๏ธโ€โšง๏ธ|transgender_flag ๐Ÿณโ€โšง๏ธ|transgender_flag ๐Ÿณ๏ธโ€โšง|transgender_flag ๐Ÿณโ€โšง|pirate_flag ๐Ÿดโ€โ˜ ๏ธ|pirate_flag ๐Ÿดโ€โ˜ |flag_ascension_island ๐Ÿ‡ฆ๐Ÿ‡จ|flag_andorra ๐Ÿ‡ฆ๐Ÿ‡ฉ|flag_united_arab_emirates ๐Ÿ‡ฆ๐Ÿ‡ช|flag_afghanistan ๐Ÿ‡ฆ๐Ÿ‡ซ|flag_antigua_barbuda ๐Ÿ‡ฆ๐Ÿ‡ฌ|flag_anguilla ๐Ÿ‡ฆ๐Ÿ‡ฎ|flag_albania ๐Ÿ‡ฆ๐Ÿ‡ฑ|flag_armenia ๐Ÿ‡ฆ๐Ÿ‡ฒ|flag_angola ๐Ÿ‡ฆ๐Ÿ‡ด|flag_antarctica ๐Ÿ‡ฆ๐Ÿ‡ถ|flag_argentina ๐Ÿ‡ฆ๐Ÿ‡ท|flag_american_samoa ๐Ÿ‡ฆ๐Ÿ‡ธ|flag_austria ๐Ÿ‡ฆ๐Ÿ‡น|flag_australia ๐Ÿ‡ฆ๐Ÿ‡บ|flag_aruba ๐Ÿ‡ฆ๐Ÿ‡ผ|flag_land_islands ๐Ÿ‡ฆ๐Ÿ‡ฝ|flag_azerbaijan ๐Ÿ‡ฆ๐Ÿ‡ฟ|flag_bosnia_herzegovina ๐Ÿ‡ง๐Ÿ‡ฆ|flag_barbados ๐Ÿ‡ง๐Ÿ‡ง|flag_bangladesh ๐Ÿ‡ง๐Ÿ‡ฉ|flag_belgium ๐Ÿ‡ง๐Ÿ‡ช|flag_burkina_faso ๐Ÿ‡ง๐Ÿ‡ซ|flag_bulgaria ๐Ÿ‡ง๐Ÿ‡ฌ|flag_bahrain ๐Ÿ‡ง๐Ÿ‡ญ|flag_burundi ๐Ÿ‡ง๐Ÿ‡ฎ|flag_benin ๐Ÿ‡ง๐Ÿ‡ฏ|flag_st_barth_lemy ๐Ÿ‡ง๐Ÿ‡ฑ|flag_bermuda ๐Ÿ‡ง๐Ÿ‡ฒ|flag_brunei ๐Ÿ‡ง๐Ÿ‡ณ|flag_bolivia ๐Ÿ‡ง๐Ÿ‡ด|flag_caribbean_netherlands ๐Ÿ‡ง๐Ÿ‡ถ|flag_brazil ๐Ÿ‡ง๐Ÿ‡ท|flag_bahamas ๐Ÿ‡ง๐Ÿ‡ธ|flag_bhutan ๐Ÿ‡ง๐Ÿ‡น|flag_bouvet_island ๐Ÿ‡ง๐Ÿ‡ป|flag_botswana ๐Ÿ‡ง๐Ÿ‡ผ|flag_belarus ๐Ÿ‡ง๐Ÿ‡พ|flag_belize ๐Ÿ‡ง๐Ÿ‡ฟ|flag_canada ๐Ÿ‡จ๐Ÿ‡ฆ|flag_cocos_keeling_islands ๐Ÿ‡จ๐Ÿ‡จ|flag_congo_kinshasa ๐Ÿ‡จ๐Ÿ‡ฉ|flag_central_african_republic ๐Ÿ‡จ๐Ÿ‡ซ|flag_congo_brazzaville ๐Ÿ‡จ๐Ÿ‡ฌ|flag_switzerland ๐Ÿ‡จ๐Ÿ‡ญ|flag_c_te_d_ivoire ๐Ÿ‡จ๐Ÿ‡ฎ|flag_cook_islands ๐Ÿ‡จ๐Ÿ‡ฐ|flag_chile ๐Ÿ‡จ๐Ÿ‡ฑ|flag_cameroon ๐Ÿ‡จ๐Ÿ‡ฒ|flag_china ๐Ÿ‡จ๐Ÿ‡ณ|flag_colombia ๐Ÿ‡จ๐Ÿ‡ด|flag_clipperton_island ๐Ÿ‡จ๐Ÿ‡ต|flag_costa_rica ๐Ÿ‡จ๐Ÿ‡ท|flag_cuba ๐Ÿ‡จ๐Ÿ‡บ|flag_cape_verde ๐Ÿ‡จ๐Ÿ‡ป|flag_cura_ao ๐Ÿ‡จ๐Ÿ‡ผ|flag_christmas_island ๐Ÿ‡จ๐Ÿ‡ฝ|flag_cyprus ๐Ÿ‡จ๐Ÿ‡พ|flag_czechia ๐Ÿ‡จ๐Ÿ‡ฟ|flag_germany ๐Ÿ‡ฉ๐Ÿ‡ช|flag_diego_garcia ๐Ÿ‡ฉ๐Ÿ‡ฌ|flag_djibouti ๐Ÿ‡ฉ๐Ÿ‡ฏ|flag_denmark ๐Ÿ‡ฉ๐Ÿ‡ฐ|flag_dominica ๐Ÿ‡ฉ๐Ÿ‡ฒ|flag_dominican_republic ๐Ÿ‡ฉ๐Ÿ‡ด|flag_algeria ๐Ÿ‡ฉ๐Ÿ‡ฟ|flag_ceuta_melilla ๐Ÿ‡ช๐Ÿ‡ฆ|flag_ecuador ๐Ÿ‡ช๐Ÿ‡จ|flag_estonia ๐Ÿ‡ช๐Ÿ‡ช|flag_egypt ๐Ÿ‡ช๐Ÿ‡ฌ|flag_western_sahara ๐Ÿ‡ช๐Ÿ‡ญ|flag_eritrea ๐Ÿ‡ช๐Ÿ‡ท|flag_spain ๐Ÿ‡ช๐Ÿ‡ธ|flag_ethiopia ๐Ÿ‡ช๐Ÿ‡น|flag_european_union ๐Ÿ‡ช๐Ÿ‡บ|flag_finland ๐Ÿ‡ซ๐Ÿ‡ฎ|flag_fiji ๐Ÿ‡ซ๐Ÿ‡ฏ|flag_falkland_islands ๐Ÿ‡ซ๐Ÿ‡ฐ|flag_micronesia ๐Ÿ‡ซ๐Ÿ‡ฒ|flag_faroe_islands ๐Ÿ‡ซ๐Ÿ‡ด|flag_france ๐Ÿ‡ซ๐Ÿ‡ท|flag_gabon ๐Ÿ‡ฌ๐Ÿ‡ฆ|flag_united_kingdom ๐Ÿ‡ฌ๐Ÿ‡ง|flag_grenada ๐Ÿ‡ฌ๐Ÿ‡ฉ|flag_georgia ๐Ÿ‡ฌ๐Ÿ‡ช|flag_french_guiana ๐Ÿ‡ฌ๐Ÿ‡ซ|flag_guernsey ๐Ÿ‡ฌ๐Ÿ‡ฌ|flag_ghana ๐Ÿ‡ฌ๐Ÿ‡ญ|flag_gibraltar ๐Ÿ‡ฌ๐Ÿ‡ฎ|flag_greenland ๐Ÿ‡ฌ๐Ÿ‡ฑ|flag_gambia ๐Ÿ‡ฌ๐Ÿ‡ฒ|flag_guinea ๐Ÿ‡ฌ๐Ÿ‡ณ|flag_guadeloupe ๐Ÿ‡ฌ๐Ÿ‡ต|flag_equatorial_guinea ๐Ÿ‡ฌ๐Ÿ‡ถ|flag_greece ๐Ÿ‡ฌ๐Ÿ‡ท|flag_south_georgia_south_sandwich_islands ๐Ÿ‡ฌ๐Ÿ‡ธ|flag_guatemala ๐Ÿ‡ฌ๐Ÿ‡น|flag_guam ๐Ÿ‡ฌ๐Ÿ‡บ|flag_guinea_bissau ๐Ÿ‡ฌ๐Ÿ‡ผ|flag_guyana ๐Ÿ‡ฌ๐Ÿ‡พ|flag_hong_kong_sar_china ๐Ÿ‡ญ๐Ÿ‡ฐ|flag_heard_mcdonald_islands ๐Ÿ‡ญ๐Ÿ‡ฒ|flag_honduras ๐Ÿ‡ญ๐Ÿ‡ณ|flag_croatia ๐Ÿ‡ญ๐Ÿ‡ท|flag_haiti ๐Ÿ‡ญ๐Ÿ‡น|flag_hungary ๐Ÿ‡ญ๐Ÿ‡บ|flag_canary_islands ๐Ÿ‡ฎ๐Ÿ‡จ|flag_indonesia ๐Ÿ‡ฎ๐Ÿ‡ฉ|flag_ireland ๐Ÿ‡ฎ๐Ÿ‡ช|flag_israel ๐Ÿ‡ฎ๐Ÿ‡ฑ|flag_isle_of_man ๐Ÿ‡ฎ๐Ÿ‡ฒ|flag_india ๐Ÿ‡ฎ๐Ÿ‡ณ|flag_british_indian_ocean_territory ๐Ÿ‡ฎ๐Ÿ‡ด|flag_iraq ๐Ÿ‡ฎ๐Ÿ‡ถ|flag_iran ๐Ÿ‡ฎ๐Ÿ‡ท|flag_iceland ๐Ÿ‡ฎ๐Ÿ‡ธ|flag_italy ๐Ÿ‡ฎ๐Ÿ‡น|flag_jersey ๐Ÿ‡ฏ๐Ÿ‡ช|flag_jamaica ๐Ÿ‡ฏ๐Ÿ‡ฒ|flag_jordan ๐Ÿ‡ฏ๐Ÿ‡ด|flag_japan ๐Ÿ‡ฏ๐Ÿ‡ต|flag_kenya ๐Ÿ‡ฐ๐Ÿ‡ช|flag_kyrgyzstan ๐Ÿ‡ฐ๐Ÿ‡ฌ|flag_cambodia ๐Ÿ‡ฐ๐Ÿ‡ญ|flag_kiribati ๐Ÿ‡ฐ๐Ÿ‡ฎ|flag_comoros ๐Ÿ‡ฐ๐Ÿ‡ฒ|flag_st_kitts_nevis ๐Ÿ‡ฐ๐Ÿ‡ณ|flag_north_korea ๐Ÿ‡ฐ๐Ÿ‡ต|flag_south_korea ๐Ÿ‡ฐ๐Ÿ‡ท|flag_kuwait ๐Ÿ‡ฐ๐Ÿ‡ผ|flag_cayman_islands ๐Ÿ‡ฐ๐Ÿ‡พ|flag_kazakhstan ๐Ÿ‡ฐ๐Ÿ‡ฟ|flag_laos ๐Ÿ‡ฑ๐Ÿ‡ฆ|flag_lebanon ๐Ÿ‡ฑ๐Ÿ‡ง|flag_st_lucia ๐Ÿ‡ฑ๐Ÿ‡จ|flag_liechtenstein ๐Ÿ‡ฑ๐Ÿ‡ฎ|flag_sri_lanka ๐Ÿ‡ฑ๐Ÿ‡ฐ|flag_liberia ๐Ÿ‡ฑ๐Ÿ‡ท|flag_lesotho ๐Ÿ‡ฑ๐Ÿ‡ธ|flag_lithuania ๐Ÿ‡ฑ๐Ÿ‡น|flag_luxembourg ๐Ÿ‡ฑ๐Ÿ‡บ|flag_latvia ๐Ÿ‡ฑ๐Ÿ‡ป|flag_libya ๐Ÿ‡ฑ๐Ÿ‡พ|flag_morocco ๐Ÿ‡ฒ๐Ÿ‡ฆ|flag_monaco ๐Ÿ‡ฒ๐Ÿ‡จ|flag_moldova ๐Ÿ‡ฒ๐Ÿ‡ฉ|flag_montenegro ๐Ÿ‡ฒ๐Ÿ‡ช|flag_st_martin ๐Ÿ‡ฒ๐Ÿ‡ซ|flag_madagascar ๐Ÿ‡ฒ๐Ÿ‡ฌ|flag_marshall_islands ๐Ÿ‡ฒ๐Ÿ‡ญ|flag_north_macedonia ๐Ÿ‡ฒ๐Ÿ‡ฐ|flag_mali ๐Ÿ‡ฒ๐Ÿ‡ฑ|flag_myanmar_burma_ ๐Ÿ‡ฒ๐Ÿ‡ฒ|flag_mongolia ๐Ÿ‡ฒ๐Ÿ‡ณ|flag_macao_sar_china ๐Ÿ‡ฒ๐Ÿ‡ด|flag_northern_mariana_islands ๐Ÿ‡ฒ๐Ÿ‡ต|flag_martinique ๐Ÿ‡ฒ๐Ÿ‡ถ|flag_mauritania ๐Ÿ‡ฒ๐Ÿ‡ท|flag_montserrat ๐Ÿ‡ฒ๐Ÿ‡ธ|flag_malta ๐Ÿ‡ฒ๐Ÿ‡น|flag_mauritius ๐Ÿ‡ฒ๐Ÿ‡บ|flag_maldives ๐Ÿ‡ฒ๐Ÿ‡ป|flag_malawi ๐Ÿ‡ฒ๐Ÿ‡ผ|flag_mexico ๐Ÿ‡ฒ๐Ÿ‡ฝ|flag_malaysia ๐Ÿ‡ฒ๐Ÿ‡พ|flag_mozambique ๐Ÿ‡ฒ๐Ÿ‡ฟ|flag_namibia ๐Ÿ‡ณ๐Ÿ‡ฆ|flag_new_caledonia ๐Ÿ‡ณ๐Ÿ‡จ|flag_niger ๐Ÿ‡ณ๐Ÿ‡ช|flag_norfolk_island ๐Ÿ‡ณ๐Ÿ‡ซ|flag_nigeria ๐Ÿ‡ณ๐Ÿ‡ฌ|flag_nicaragua ๐Ÿ‡ณ๐Ÿ‡ฎ|flag_netherlands ๐Ÿ‡ณ๐Ÿ‡ฑ|flag_norway ๐Ÿ‡ณ๐Ÿ‡ด|flag_nepal ๐Ÿ‡ณ๐Ÿ‡ต|flag_nauru ๐Ÿ‡ณ๐Ÿ‡ท|flag_niue ๐Ÿ‡ณ๐Ÿ‡บ|flag_new_zealand ๐Ÿ‡ณ๐Ÿ‡ฟ|flag_oman ๐Ÿ‡ด๐Ÿ‡ฒ|flag_panama ๐Ÿ‡ต๐Ÿ‡ฆ|flag_peru ๐Ÿ‡ต๐Ÿ‡ช|flag_french_polynesia ๐Ÿ‡ต๐Ÿ‡ซ|flag_papua_new_guinea ๐Ÿ‡ต๐Ÿ‡ฌ|flag_philippines ๐Ÿ‡ต๐Ÿ‡ญ|flag_pakistan ๐Ÿ‡ต๐Ÿ‡ฐ|flag_poland ๐Ÿ‡ต๐Ÿ‡ฑ|flag_st_pierre_miquelon ๐Ÿ‡ต๐Ÿ‡ฒ|flag_pitcairn_islands ๐Ÿ‡ต๐Ÿ‡ณ|flag_puerto_rico ๐Ÿ‡ต๐Ÿ‡ท|flag_palestinian_territories ๐Ÿ‡ต๐Ÿ‡ธ|flag_portugal ๐Ÿ‡ต๐Ÿ‡น|flag_palau ๐Ÿ‡ต๐Ÿ‡ผ|flag_paraguay ๐Ÿ‡ต๐Ÿ‡พ|flag_qatar ๐Ÿ‡ถ๐Ÿ‡ฆ|flag_r_union ๐Ÿ‡ท๐Ÿ‡ช|flag_romania ๐Ÿ‡ท๐Ÿ‡ด|flag_serbia ๐Ÿ‡ท๐Ÿ‡ธ|flag_russia ๐Ÿ‡ท๐Ÿ‡บ|flag_rwanda ๐Ÿ‡ท๐Ÿ‡ผ|flag_saudi_arabia ๐Ÿ‡ธ๐Ÿ‡ฆ|flag_solomon_islands ๐Ÿ‡ธ๐Ÿ‡ง|flag_seychelles ๐Ÿ‡ธ๐Ÿ‡จ|flag_sudan ๐Ÿ‡ธ๐Ÿ‡ฉ|flag_sweden ๐Ÿ‡ธ๐Ÿ‡ช|flag_singapore ๐Ÿ‡ธ๐Ÿ‡ฌ|flag_st_helena ๐Ÿ‡ธ๐Ÿ‡ญ|flag_slovenia ๐Ÿ‡ธ๐Ÿ‡ฎ|flag_svalbard_jan_mayen ๐Ÿ‡ธ๐Ÿ‡ฏ|flag_slovakia ๐Ÿ‡ธ๐Ÿ‡ฐ|flag_sierra_leone ๐Ÿ‡ธ๐Ÿ‡ฑ|flag_san_marino ๐Ÿ‡ธ๐Ÿ‡ฒ|flag_senegal ๐Ÿ‡ธ๐Ÿ‡ณ|flag_somalia ๐Ÿ‡ธ๐Ÿ‡ด|flag_suriname ๐Ÿ‡ธ๐Ÿ‡ท|flag_south_sudan ๐Ÿ‡ธ๐Ÿ‡ธ|flag_s_o_tom_pr_ncipe ๐Ÿ‡ธ๐Ÿ‡น|flag_el_salvador ๐Ÿ‡ธ๐Ÿ‡ป|flag_sint_maarten ๐Ÿ‡ธ๐Ÿ‡ฝ|flag_syria ๐Ÿ‡ธ๐Ÿ‡พ|flag_eswatini ๐Ÿ‡ธ๐Ÿ‡ฟ|flag_tristan_da_cunha ๐Ÿ‡น๐Ÿ‡ฆ|flag_turks_caicos_islands ๐Ÿ‡น๐Ÿ‡จ|flag_chad ๐Ÿ‡น๐Ÿ‡ฉ|flag_french_southern_territories ๐Ÿ‡น๐Ÿ‡ซ|flag_togo ๐Ÿ‡น๐Ÿ‡ฌ|flag_thailand ๐Ÿ‡น๐Ÿ‡ญ|flag_tajikistan ๐Ÿ‡น๐Ÿ‡ฏ|flag_tokelau ๐Ÿ‡น๐Ÿ‡ฐ|flag_timor_leste ๐Ÿ‡น๐Ÿ‡ฑ|flag_turkmenistan ๐Ÿ‡น๐Ÿ‡ฒ|flag_tunisia ๐Ÿ‡น๐Ÿ‡ณ|flag_tonga ๐Ÿ‡น๐Ÿ‡ด|flag_t_rkiye ๐Ÿ‡น๐Ÿ‡ท|flag_trinidad_tobago ๐Ÿ‡น๐Ÿ‡น|flag_tuvalu ๐Ÿ‡น๐Ÿ‡ป|flag_taiwan ๐Ÿ‡น๐Ÿ‡ผ|flag_tanzania ๐Ÿ‡น๐Ÿ‡ฟ|flag_ukraine ๐Ÿ‡บ๐Ÿ‡ฆ|flag_uganda ๐Ÿ‡บ๐Ÿ‡ฌ|flag_u_s_outlying_islands ๐Ÿ‡บ๐Ÿ‡ฒ|flag_united_nations ๐Ÿ‡บ๐Ÿ‡ณ|flag_united_states ๐Ÿ‡บ๐Ÿ‡ธ|flag_uruguay ๐Ÿ‡บ๐Ÿ‡พ|flag_uzbekistan ๐Ÿ‡บ๐Ÿ‡ฟ|flag_vatican_city ๐Ÿ‡ป๐Ÿ‡ฆ|flag_st_vincent_grenadines ๐Ÿ‡ป๐Ÿ‡จ|flag_venezuela ๐Ÿ‡ป๐Ÿ‡ช|flag_british_virgin_islands ๐Ÿ‡ป๐Ÿ‡ฌ|flag_u_s_virgin_islands ๐Ÿ‡ป๐Ÿ‡ฎ|flag_vietnam ๐Ÿ‡ป๐Ÿ‡ณ|flag_vanuatu ๐Ÿ‡ป๐Ÿ‡บ|flag_wallis_futuna ๐Ÿ‡ผ๐Ÿ‡ซ|flag_samoa ๐Ÿ‡ผ๐Ÿ‡ธ|flag_kosovo ๐Ÿ‡ฝ๐Ÿ‡ฐ|flag_yemen ๐Ÿ‡พ๐Ÿ‡ช|flag_mayotte ๐Ÿ‡พ๐Ÿ‡น|flag_south_africa ๐Ÿ‡ฟ๐Ÿ‡ฆ|flag_zambia ๐Ÿ‡ฟ๐Ÿ‡ฒ|flag_zimbabwe ๐Ÿ‡ฟ๐Ÿ‡ผ|flag_england ๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ|flag_scotland ๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ|flag_wales ๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ" diff --git a/plugs/emoji/emoji.ts b/plugs/emoji/emoji.ts index 4578c479..9034ea63 100644 --- a/plugs/emoji/emoji.ts +++ b/plugs/emoji/emoji.ts @@ -8,9 +8,11 @@ let emojiConfig: EmojiConfig = { aliases: [] }; const emojis = emojiBlob.split("|").map((line) => line.split(" ")); -export function emojiCompleter( - { linePrefix, pos, parentNodes }: CompleteEvent, -) { +export function emojiCompleter({ + linePrefix, + pos, + parentNodes, +}: CompleteEvent) { void updateConfig(); // no need to await, will be ready for completion by next keystrokes const match = /:([\w]+)$/.exec(linePrefix); @@ -20,8 +22,8 @@ export function emojiCompleter( // Check if we're not in a Lua directive or space-lua block if ( - parentNodes.find((node) => - node === "LuaDirective" || node.startsWith("FencedCode") + parentNodes.find( + (node) => node === "LuaDirective" || node.startsWith("FencedCode"), ) ) { return; diff --git a/plugs/index/api.ts b/plugs/index/api.ts index 4bb155f0..265b0327 100644 --- a/plugs/index/api.ts +++ b/plugs/index/api.ts @@ -49,8 +49,9 @@ export async function patchFrontmatter( if (patchedFrontmatter) { // Replace the frontmatter with the patched frontmatter in the original string - return "---\n" + patchedFrontmatter + "\n---" + - text.slice(frontmatter[0].to); + return ( + "---\n" + patchedFrontmatter + "\n---" + text.slice(frontmatter[0].to) + ); } else { // Nothing left, let's just return the text content return text.slice(frontmatter[0].to! + 1); // +1 to skip the initial \n diff --git a/plugs/index/attribute.ts b/plugs/index/attribute.ts index fe2637a1..b98be4e2 100644 --- a/plugs/index/attribute.ts +++ b/plugs/index/attribute.ts @@ -14,9 +14,7 @@ import YAML from "js-yaml"; * @param tree to clean attributes from * @return */ -export function collectAttributes( - tree: ParseTree, -): Record { +export function collectAttributes(tree: ParseTree): Record { const attributes: Record = {}; traverseTree(tree, (n) => { diff --git a/plugs/index/cheap_yaml.test.ts b/plugs/index/cheap_yaml.test.ts index bf58c584..a8aa8a2c 100644 --- a/plugs/index/cheap_yaml.test.ts +++ b/plugs/index/cheap_yaml.test.ts @@ -6,8 +6,14 @@ test("cheap yaml", () => { expect(determineTags("hank: bla")).toEqual([]); expect(determineTags("tags: template")).toEqual(["template"]); expect(determineTags("tags: bla,template")).toEqual(["bla", "template"]); - expect(determineTags("tags:\n- bla\n- template")).toEqual(["bla", "template"]); + expect(determineTags("tags:\n- bla\n- template")).toEqual([ + "bla", + "template", + ]); expect(determineTags(`tags: "#bla,#template"`)).toEqual(["bla", "template"]); expect(determineTags(`tags: '#bla, #template'`)).toEqual(["bla", "template"]); - expect(determineTags(`tags:\n- "#bla"\n- template`)).toEqual(["bla", "template"]); + expect(determineTags(`tags:\n- "#bla"\n- template`)).toEqual([ + "bla", + "template", + ]); }); diff --git a/plugs/index/cheap_yaml.ts b/plugs/index/cheap_yaml.ts index 2c7ec5b0..0542fed2 100644 --- a/plugs/index/cheap_yaml.ts +++ b/plugs/index/cheap_yaml.ts @@ -19,9 +19,7 @@ export function determineTags(yamlText: string): string[] { inTagsSection = true; // 'template' there? Yay! if (value) { - tags.push( - ...value.split(/,\s*|\s+/).map((t) => t.replace(/^#/, "")), - ); + tags.push(...value.split(/,\s*|\s+/).map((t) => t.replace(/^#/, ""))); } } else { inTagsSection = false; diff --git a/plugs/index/complete.ts b/plugs/index/complete.ts index 1102c4ef..cd3f1a45 100644 --- a/plugs/index/complete.ts +++ b/plugs/index/complete.ts @@ -50,9 +50,7 @@ function humanReadableSchemaType(type: any): string { * Task state completion */ export async function completeTaskState(completeEvent: CompleteEvent) { - const taskMatch = /([-*]\s+\[)([^[\]]+)$/.exec( - completeEvent.linePrefix, - ); + const taskMatch = /([-*]\s+\[)([^[\]]+)$/.exec(completeEvent.linePrefix); if (!taskMatch) { return null; } diff --git a/plugs/index/frontmatter.ts b/plugs/index/frontmatter.ts index 791ca5f8..d0922246 100644 --- a/plugs/index/frontmatter.ts +++ b/plugs/index/frontmatter.ts @@ -61,7 +61,8 @@ export function extractFrontMatter( collectedTags.add(tagname); if ( - options.removeTags === true || options.removeTags?.includes(tagname) + options.removeTags === true || + options.removeTags?.includes(tagname) ) { // Ugly hack to remove the hashtag child.children![0].text = ""; @@ -114,7 +115,8 @@ export function extractFrontMatter( } // If nothing is left, let's just delete this whole block if ( - Object.keys(newData).length === 0 || options.removeFrontMatterSection + Object.keys(newData).length === 0 || + options.removeFrontMatterSection ) { return null; } @@ -145,12 +147,14 @@ export function extractFrontMatter( try { frontmatter.tags = [ - ...new Set([...tags.map((t) => { - // Always treat tags as strings - const tagAsString = String(t); - // Strip # from tags - return tagAsString.replace(/^#/, ""); - })]), + ...new Set([ + ...tags.map((t) => { + // Always treat tags as strings + const tagAsString = String(t); + // Strip # from tags + return tagAsString.replace(/^#/, ""); + }), + ]), ]; } catch (e) { console.error("Error while processing tags", e); @@ -166,7 +170,7 @@ const attributeRegex = /^[\w\-_]+$/; export async function frontmatterComplete(completeEvent: CompleteEvent) { const frontmatterCode = completeEvent.parentNodes.find((nt) => - nt.startsWith("FrontMatter:") + nt.startsWith("FrontMatter:"), ); if (!frontmatterCode) { diff --git a/plugs/index/header.test.ts b/plugs/index/header.test.ts index ee48412f..1e39d7ac 100644 --- a/plugs/index/header.test.ts +++ b/plugs/index/header.test.ts @@ -25,11 +25,7 @@ test("Test header indexing", async () => { perm: "rw", }; - const headers = await indexHeaders( - pageMeta, - frontmatter, - tree, - ); + const headers = await indexHeaders(pageMeta, frontmatter, tree); // 1 data 1 tag expect(headers.length).toEqual(3); diff --git a/plugs/index/header.ts b/plugs/index/header.ts index 7fc2bcb1..be453936 100644 --- a/plugs/index/header.ts +++ b/plugs/index/header.ts @@ -35,12 +35,10 @@ export function indexHeaders( ): Promise { const headers: ObjectValue[] = []; - for ( - const n of collectNodesMatching( - tree, - (t) => !!t.type?.startsWith("ATXHeading"), - ) - ) { + for (const n of collectNodesMatching( + tree, + (t) => !!t.type?.startsWith("ATXHeading"), + )) { const level = +n.type!.substring("ATXHeading".length); const name = renderToText(n).slice(level + 1); const tags = collectTags(n); diff --git a/plugs/index/indexer.ts b/plugs/index/indexer.ts index 3c7a1385..f3569d48 100644 --- a/plugs/index/indexer.ts +++ b/plugs/index/indexer.ts @@ -41,22 +41,25 @@ export const allIndexers: IndexerFunction[] = [ * Ad-hoc index a piece of markdown text * @return a list of indexed objects */ -export async function indexMarkdown(text: string, pageMeta: PageMeta = { - ref: "", - tag: "", - name: "", - perm: "ro", - lastModified: "", - created: "", -}): Promise> { +export async function indexMarkdown( + text: string, + pageMeta: PageMeta = { + ref: "", + tag: "", + name: "", + perm: "ro", + lastModified: "", + created: "", + }, +): Promise> { const tree = await markdown.parseMarkdown(text); const frontmatter = extractFrontMatter(tree); const index = await Promise.all( - allIndexers.filter((indexer) => indexer !== pageIndexPage).map( - (indexer) => { + allIndexers + .filter((indexer) => indexer !== pageIndexPage) + .map((indexer) => { return indexer(pageMeta, frontmatter, tree, text); - }, - ), + }), ); return index.flat(); } @@ -67,9 +70,11 @@ export async function indexPage({ name, tree, meta, text }: IndexTreeEvent) { // console.log("Now going to index page", name); // Index the page - const indexResults = await Promise.all(allIndexers.map((indexer) => { - return indexer(meta, frontmatter, tree, text); - })); + const indexResults = await Promise.all( + allIndexers.map((indexer) => { + return indexer(meta, frontmatter, tree, text); + }), + ); // console.log("Found these objects", index.flat()); diff --git a/plugs/index/item.test.ts b/plugs/index/item.test.ts index 1b4fb738..a06a678a 100644 --- a/plugs/index/item.test.ts +++ b/plugs/index/item.test.ts @@ -42,7 +42,8 @@ test("Test item indexing", async () => { expect(items[1].name).toEqual("Item 1.1"); expect(new Set(items[1].tags)).toEqual(new Set(["tag3", "tag1"])); - expect(new Set(items[1].itags)).toEqual(new Set(["tag3", "tag2", "tag1", "item"]), + expect(new Set(items[1].itags)).toEqual( + new Set(["tag3", "tag2", "tag1", "item"]), ); expect(items[1].parent).toEqual(items[0].ref); diff --git a/plugs/index/item.ts b/plugs/index/item.ts index 087d4f17..b4ed4d95 100644 --- a/plugs/index/item.ts +++ b/plugs/index/item.ts @@ -32,13 +32,11 @@ export type ItemObject = ObjectValue< export type TaskObject = ObjectValue< // "Inherit" everyting from item - & ItemObject - // And add a few more attributes - & { + ItemObject & { + // And add a few more attributes done: boolean; state: string; - } - & Record + } & Record >; const completeStates = ["x", "X"]; @@ -48,14 +46,8 @@ export async function indexItems( frontmatter: FrontMatter, tree: ParseTree, ) { - const shouldIndexAllItems = await system.getConfig( - "index.item.all", - true, - ); - const shouldIndexAllTasks = await system.getConfig( - "index.task.all", - true, - ); + const shouldIndexAllItems = await system.getConfig("index.item.all", true); + const shouldIndexAllTasks = await system.getConfig("index.task.all", true); let items: ObjectValue[] = []; @@ -69,11 +61,7 @@ export async function indexItems( return true; } - items.push(extractItemFromNode( - pageMeta.name, - n, - frontmatter, - )); + items.push(extractItemFromNode(pageMeta.name, n, frontmatter)); // Traversal continue into child items (potentially) return false; @@ -175,17 +163,14 @@ export function enrichItemFromParents( // Merge tags item.itags = [ ...new Set([ - ...item.itags || [], - ...(parentItem.itags!.filter((t) => !["item", "task"].includes(t))), + ...(item.itags || []), + ...parentItem.itags!.filter((t) => !["item", "task"].includes(t)), ]), ]; // And links const ilinks = [ - ...new Set([ - ...item.ilinks || [], - ...parentItem.ilinks || [], - ]), + ...new Set([...(item.ilinks || []), ...(parentItem.ilinks || [])]), ]; if (ilinks.length > 0) { item.ilinks = ilinks; diff --git a/plugs/index/link.ts b/plugs/index/link.ts index 88f6fa1d..3a77cbff 100644 --- a/plugs/index/link.ts +++ b/plugs/index/link.ts @@ -216,7 +216,7 @@ export async function indexLinks( // Now let's check which are aspiring pages for (const link of objects.slice()) { if (link.toPage) { - if (!await space.fileExists(`${link.toPage}.md`)) { + if (!(await space.fileExists(`${link.toPage}.md`))) { objects.push({ ref: `${name}@${link.pos}`, tag: "aspiring-page", @@ -259,13 +259,15 @@ export function collectPageLinks(n: ParseTree): string[] { return [...links]; } -export async function getBackLinks( - name: string, -): Promise { - return (await index.queryLuaObjects("link", { - objectVariable: "_", - where: await lua.parseExpression(`_.toPage == name or _.toFile == name`), - }, { - name, - })); +export async function getBackLinks(name: string): Promise { + return await index.queryLuaObjects( + "link", + { + objectVariable: "_", + where: await lua.parseExpression(`_.toPage == name or _.toFile == name`), + }, + { + name, + }, + ); } diff --git a/plugs/index/lint.ts b/plugs/index/lint.ts index f8526420..2589e313 100644 --- a/plugs/index/lint.ts +++ b/plugs/index/lint.ts @@ -17,19 +17,13 @@ import { allIndexers } from "./indexer.ts"; /** * Lint YAML syntax in frontmatter and fenced code blocks */ -export function lintYAML( - { tree, name }: LintEvent, -): LintDiagnostic[] { +export function lintYAML({ tree, name }: LintEvent): LintDiagnostic[] { const diagnostics: LintDiagnostic[] = []; traverseTree(tree, (node) => { if (node.type === "FrontMatterCode") { const yamlText = renderToText(node); - const lintResult = lintYamlBlock( - yamlText, - node.from!, - name, - ); + const lintResult = lintYamlBlock(yamlText, node.from!, name); if (lintResult) { diagnostics.push(lintResult); } @@ -42,18 +36,13 @@ export function lintYAML( } const codeLang = codeInfo.children![0].text!; // All known YAML formats - if ( - ["yaml"].includes(codeLang) || codeLang.startsWith("#") - ) { + if (["yaml"].includes(codeLang) || codeLang.startsWith("#")) { const codeText = findNodeOfType(node, "CodeText"); if (!codeText) { return true; } const yamlCode = renderToText(codeText); - const lintResult = lintYamlBlock( - yamlCode, - codeText.from!, - ); + const lintResult = lintYamlBlock(yamlCode, codeText.from!); if (lintResult) { diagnostics.push(lintResult); } @@ -164,24 +153,33 @@ export async function lintLua({ tree }: LintEvent): Promise { /** * Lint objects in the page */ -export async function lintObjects( - { tree, pageMeta: meta, text, name }: LintEvent, -): Promise { +export async function lintObjects({ + tree, + pageMeta: meta, + text, + name, +}: LintEvent): Promise { const frontmatter = extractFrontMatter(tree); // Index the page - const allObjects = (await Promise.all(allIndexers.map((indexer) => { - return indexer(meta, frontmatter, tree, text); - }))).flat(); + const allObjects = ( + await Promise.all( + allIndexers.map((indexer) => { + return indexer(meta, frontmatter, tree, text); + }), + ) + ).flat(); const result = await index.validateObjects(name, allObjects); // If validation failed, return the error if (result?.object?.range) { - return [{ - from: result.object.range[0], - to: result.object.range[1], - severity: "error", - message: result.error, - }]; + return [ + { + from: result.object.range[0], + to: result.object.range[1], + severity: "error", + message: result.error, + }, + ]; } return []; } diff --git a/plugs/index/page.ts b/plugs/index/page.ts index 411490da..80eec36a 100644 --- a/plugs/index/page.ts +++ b/plugs/index/page.ts @@ -34,7 +34,7 @@ export async function indexPage( ...pageMeta, }; - combinedPageMeta.tags = [...new Set([...frontmatter.tags || []])]; + combinedPageMeta.tags = [...new Set([...(frontmatter.tags || [])])]; combinedPageMeta.tag = "page"; @@ -95,7 +95,8 @@ export async function lintFrontmatter(): Promise { const codeLang = codeInfo.children![0].text!; // All known YAML formats if ( - codeLang === "template" || codeLang === "yaml" || + codeLang === "template" || + codeLang === "yaml" || codeLang.startsWith("#") ) { const codeText = findNodeOfType(node, "CodeText"); @@ -153,15 +154,14 @@ export async function loadPageObject(pageName?: string): Promise { created: "", } as PageMeta; } - return (await index.getObjectByRef( - pageName, - "page", - pageName, - )) || { - ref: pageName, - name: pageName, - tags: ["page"], - lastModified: "", - created: "", - } as PageMeta; + return ( + (await index.getObjectByRef(pageName, "page", pageName)) || + ({ + ref: pageName, + name: pageName, + tags: ["page"], + lastModified: "", + created: "", + } as PageMeta) + ); } diff --git a/plugs/index/paragraph.ts b/plugs/index/paragraph.ts index fcabd60a..ee6f756f 100644 --- a/plugs/index/paragraph.ts +++ b/plugs/index/paragraph.ts @@ -28,10 +28,7 @@ export async function indexParagraphs( frontmatter: FrontMatter, tree: ParseTree, ) { - const shouldIndexAll = await system.getConfig( - "index.paragraph.all", - false, - ); + const shouldIndexAll = await system.getConfig("index.paragraph.all", false); const objects: ParagraphObject[] = []; diff --git a/plugs/index/queue.ts b/plugs/index/queue.ts index 888b9b36..2e880e6e 100644 --- a/plugs/index/queue.ts +++ b/plugs/index/queue.ts @@ -67,7 +67,8 @@ async function updateIndexProgressInUI() { let totalQueued = await totalItemsQueued(); while (totalQueued > 0) { const percentage = Math.round( - (maximumObservedQueueSize - totalQueued) / maximumObservedQueueSize * 100, + ((maximumObservedQueueSize - totalQueued) / maximumObservedQueueSize) * + 100, ); if (percentage > 0 && percentage <= 99) { await editor.showProgress(percentage, "index"); diff --git a/plugs/index/refactor.ts b/plugs/index/refactor.ts index 245753ea..33f29704 100644 --- a/plugs/index/refactor.ts +++ b/plugs/index/refactor.ts @@ -32,15 +32,18 @@ import { notFoundError } from "@silverbulletmd/silverbullet/constants"; * @returns True if the rename succeeded; otherwise, false. */ export async function renamePageCommand(cmdDef: any) { - const oldName: string = cmdDef.oldPage || await editor.getCurrentPage(); - let newName: string = cmdDef.page || - await editor.prompt(`Rename ${oldName} to:`, oldName); + const oldName: string = cmdDef.oldPage || (await editor.getCurrentPage()); + let newName: string = + cmdDef.page || (await editor.prompt(`Rename ${oldName} to:`, oldName)); if (newName === undefined) { return false; } newName = newName.trim(); if (newName === "") { - void editor.flashNotification("Must provide a non-empty page title.", "error"); + void editor.flashNotification( + "Must provide a non-empty page title.", + "error", + ); return false; } const pageList: [string, string][] = [[`${oldName}.md`, `${newName}.md`]]; @@ -77,7 +80,10 @@ export async function renamePageLinkCommand() { } newName = newName.trim(); if (newName === "") { - void editor.flashNotification("Must provide a non-empty page title.", "error"); + void editor.flashNotification( + "Must provide a non-empty page title.", + "error", + ); return false; } const pageList: [string, string][] = [[`${oldName}.md`, `${newName}.md`]]; @@ -93,9 +99,9 @@ export async function renamePageLinkCommand() { * @returns True if the rename succeeded; otherwise, false. */ export async function renameDocumentCommand(cmdDef: any) { - const oldName: string = cmdDef.oldDocument || await editor.getCurrentPath(); - let newName: string = cmdDef.document || - await editor.prompt(`Rename ${oldName} to:`, oldName); + const oldName: string = cmdDef.oldDocument || (await editor.getCurrentPath()); + let newName: string = + cmdDef.document || (await editor.prompt(`Rename ${oldName} to:`, oldName)); if (newName === undefined) { return false; } @@ -132,26 +138,28 @@ export async function batchRenameFiles(fileList: [string, string][]) { try { // Pre-flight checks - await Promise.all(fileList.map(async ([_oldName, newName]) => { - try { - // It's a FILEname not a PAGEname. - if (!isValidPath(newName)) { - throw new Error(`Name invalid: ${newName}`); + await Promise.all( + fileList.map(async ([_oldName, newName]) => { + try { + // It's a FILEname not a PAGEname. + if (!isValidPath(newName)) { + throw new Error(`Name invalid: ${newName}`); + } + // Check if target file already exists + await space.getFileMeta(newName); + // If we got here, the file exists, so we error out + throw new Error( + `${newName} already exists, cannot rename to existing file.`, + ); + } catch (e: any) { + if (e.message === notFoundError.message) { + // Expected not found error, so we can continue + } else { + throw e; + } } - // Check if target file already exists - await space.getFileMeta(newName); - // If we got here, the file exists, so we error out - throw new Error( - `${newName} already exists, cannot rename to existing file.`, - ); - } catch (e: any) { - if (e.message === notFoundError.message) { - // Expected not found error, so we can continue - } else { - throw e; - } - } - })); + }), + ); // All new names are available, proceeding with rename for (const [oldName, newName] of fileList) { @@ -188,12 +196,16 @@ async function renamePage(oldName: string, newName: string) { const documentsToMove = new Set(); // Links only need to be updated if the folder changes if (oldFolder !== newFolder) { - const linksInPage = await index.queryLuaObjects("link", { - objectVariable: "_", - where: await lua.parseExpression(`_.page == oldName`), - }, { - oldName, - }); + const linksInPage = await index.queryLuaObjects( + "link", + { + objectVariable: "_", + where: await lua.parseExpression(`_.page == oldName`), + }, + { + oldName, + }, + ); const linksToUpdate: ObjectValue[] = []; for (const link of linksInPage) { @@ -238,9 +250,10 @@ async function renamePage(oldName: string, newName: string) { // Move documents along with page const batchRenameDocuments: [string, string][] = []; for (const document of documentsToMove) { - const newAttName = oldFolder.length === 0 - ? `${newFolder}/${document}` - : document.replace(oldFolder, newFolder).replace(/^\//, ""); + const newAttName = + oldFolder.length === 0 + ? `${newFolder}/${document}` + : document.replace(oldFolder, newFolder).replace(/^\//, ""); batchRenameDocuments.push([document, newAttName]); } if (batchRenameDocuments.length > 0) { @@ -258,7 +271,7 @@ async function renamePage(oldName: string, newName: string) { const updatedRefences = await updateBacklinks(oldName, newName); // Navigate to new page if currently viewing old page - if (await editor.getCurrentPage() === oldName) { + if ((await editor.getCurrentPage()) === oldName) { // Wait for index queue to be processed so that widgets are updated with up-to-date information await mq.awaitEmptyQueue("indexQueue"); await editor.navigate(newName, true); @@ -275,15 +288,12 @@ async function renamePage(oldName: string, newName: string) { } // Rename a document and update any backlinks -async function renameDocument( - oldPath: string, - newPath: string, -) { +async function renameDocument(oldPath: string, newPath: string) { // Move the file const oldFile = await space.readDocument(oldPath); const newFileMeta = await space.writeDocument(newPath, oldFile); - if (await editor.getCurrentPath() === oldPath) { + if ((await editor.getCurrentPath()) === oldPath) { await editor.navigate(newPath, true); } @@ -316,8 +326,8 @@ async function renameDocument( * @returns True if the rename succeeded; otherwise, false. */ export async function renamePrefixCommand(cmdDef: any) { - const oldPrefix = cmdDef.oldPrefix ?? - await editor.prompt("Prefix to rename:", ""); + const oldPrefix = + cmdDef.oldPrefix ?? (await editor.prompt("Prefix to rename:", "")); if (oldPrefix === undefined) { return false; } @@ -328,33 +338,37 @@ export async function renamePrefixCommand(cmdDef: any) { void editor.flashNotification("Must provide a non-empty prefix.", "error"); return false; } - const newPrefix = cmdDef.newPrefix ?? - await editor.prompt("New prefix:", oldPrefix); + const newPrefix = + cmdDef.newPrefix ?? (await editor.prompt("New prefix:", oldPrefix)); if (newPrefix === undefined) { return false; } const allDocuments = await space.listDocuments(); const allPages = await space.listPages(); - let allAffectedFiles = allDocuments.map((file) => file.name).filter(( - file, - ) => file.startsWith(oldPrefix)); + let allAffectedFiles = allDocuments + .map((file) => file.name) + .filter((file) => file.startsWith(oldPrefix)); allAffectedFiles = allAffectedFiles.concat( - allPages.map((page) => `${page.name}.md`).filter((page) => - page.startsWith(oldPrefix) - ), + allPages + .map((page) => `${page.name}.md`) + .filter((page) => page.startsWith(oldPrefix)), ); if ( - cmdDef.disableConfirmation !== true && !(await editor.confirm( + cmdDef.disableConfirmation !== true && + !(await editor.confirm( `This will affect ${allAffectedFiles.length} files. Are you sure?`, )) ) { return false; } - const allNewNames: [string, string][] = allAffectedFiles.map((name) => // This may seem naive, but it's actually fine, because we're only renaming the first occurrence (which will be the prefix) - [name, name.replace(oldPrefix, newPrefix)]); + const allNewNames: [string, string][] = allAffectedFiles.map( + ( + name, // This may seem naive, but it's actually fine, because we're only renaming the first occurrence (which will be the prefix) + ) => [name, name.replace(oldPrefix, newPrefix)], + ); await batchRenameFiles(allNewNames); } @@ -377,7 +391,10 @@ export async function extractToPageCommand() { } newName = newName.trim(); if (newName === "") { - void editor.flashNotification("Must provide a non-empty page title.", "error"); + void editor.flashNotification( + "Must provide a non-empty page title.", + "error", + ); } try { diff --git a/plugs/index/snippet.test.ts b/plugs/index/snippet.test.ts index d99b9993..47906d87 100644 --- a/plugs/index/snippet.test.ts +++ b/plugs/index/snippet.test.ts @@ -12,24 +12,24 @@ test("extractSnippetByIndentation", () => { * Item 2.1`; // Unindented line with children - expect( - extractSnippet("test", text, text.indexOf("* Item 1")), - ).toEqual("* Item 1\n * Item 1.1\n * Item 1.1.1\n * Item 1.1.1.1\n * Item 1.1.2\n * Item 1.2"); + expect(extractSnippet("test", text, text.indexOf("* Item 1"))).toEqual( + "* Item 1\n * Item 1.1\n * Item 1.1.1\n * Item 1.1.1.1\n * Item 1.1.2\n * Item 1.2", + ); // Indented line with children (left-aligned) - expect( - extractSnippet("test", text, text.indexOf("Item 1.1")), - ).toEqual("* Item 1.1\n * Item 1.1.1\n * Item 1.1.1.1\n * Item 1.1.2"); + expect(extractSnippet("test", text, text.indexOf("Item 1.1"))).toEqual( + "* Item 1.1\n * Item 1.1.1\n * Item 1.1.1.1\n * Item 1.1.2", + ); // Deeply nested line (left-aligned) - expect( - extractSnippet("test", text, text.indexOf("Item 1.1.1.1")), - ).toEqual("* Item 1.1.1.1"); + expect(extractSnippet("test", text, text.indexOf("Item 1.1.1.1"))).toEqual( + "* Item 1.1.1.1", + ); // Stops at sibling - expect( - extractSnippet("test", text, text.indexOf("Item 1.2")), - ).toEqual("* Item 1.2"); + expect(extractSnippet("test", text, text.indexOf("Item 1.2"))).toEqual( + "* Item 1.2", + ); // Stops at empty line const text2 = `* Item A @@ -37,16 +37,16 @@ test("extractSnippetByIndentation", () => { Content * Item A.2`; - expect( - extractSnippet("test", text2, text2.indexOf("Item A.1")), - ).toEqual("* Item A.1\n Content"); + expect(extractSnippet("test", text2, text2.indexOf("Item A.1"))).toEqual( + "* Item A.1\n Content", + ); // Header extracts only itself and strips # marker const text3 = `## Sub Header More text`; - expect( - extractSnippet("test", text3, text3.indexOf("## Sub Header")), - ).toEqual("Sub Header"); + expect(extractSnippet("test", text3, text3.indexOf("## Sub Header"))).toEqual( + "Sub Header", + ); // maxLines limits the number of lines const text4 = `* Item X @@ -54,15 +54,15 @@ More text`; * Item X.2 * Item X.3 * Item X.4`; - expect( - extractSnippet("test", text4, text4.indexOf("* Item X"), 3), - ).toEqual("* Item X\n * Item X.1\n * Item X.2\n..."); + expect(extractSnippet("test", text4, text4.indexOf("* Item X"), 3)).toEqual( + "* Item X\n * Item X.1\n * Item X.2\n...", + ); // maxLines limits the number of lines const text5 = ` * Item X * [ ] Hello`; const taskPos = text5.indexOf("* [ ]"); - expect( - extractSnippet("test", text5, text4.indexOf("* Item X")), - ).toEqual(`* Item X\n * [ ] [[test@${taskPos}]] Hello`); + expect(extractSnippet("test", text5, text4.indexOf("* Item X"))).toEqual( + `* Item X\n * [ ] [[test@${taskPos}]] Hello`, + ); }); diff --git a/plugs/index/snippet.ts b/plugs/index/snippet.ts index 54212e2d..d92f4b02 100644 --- a/plugs/index/snippet.ts +++ b/plugs/index/snippet.ts @@ -26,11 +26,7 @@ export function extractSnippet( const snippetLines = [targetLine.substring(targetIndent)]; // Add all subsequent lines that have greater indentation than the target line - for ( - let i = targetLineIndex + 1; - i < lines.length; - i++ - ) { + for (let i = targetLineIndex + 1; i < lines.length; i++) { let line = lines[i]; const lineIndent = getIndentationLevel(line); @@ -50,15 +46,13 @@ export function extractSnippet( } // Find tasks that don't have a page reference, and add one - const taskMatch = line.match( - /^(\s*)([*-]\s+\[[^\]]+\]\s+)([^[][^[].+)$/, - ); + const taskMatch = line.match(/^(\s*)([*-]\s+\[[^\]]+\]\s+)([^[][^[].+)$/); if (taskMatch) { - const pos = lineLengths.slice(0, i).reduce((acc, len) => - acc + len + 1, 0) + + const pos = + lineLengths.slice(0, i).reduce((acc, len) => acc + len + 1, 0) + taskMatch[1].length; - line = taskMatch[1] + taskMatch[2] + `[[${pageName}@${pos}]] ` + - taskMatch[3]; + line = + taskMatch[1] + taskMatch[2] + `[[${pageName}@${pos}]] ` + taskMatch[3]; } snippetLines.push(line.substring(targetIndent)); } diff --git a/plugs/index/table.ts b/plugs/index/table.ts index a3a98fa2..19a46d55 100644 --- a/plugs/index/table.ts +++ b/plugs/index/table.ts @@ -15,13 +15,12 @@ import type { import { extractHashtag } from "@silverbulletmd/silverbullet/lib/tags"; import type { FrontMatter } from "./frontmatter.ts"; -type TableRowObject = - & ObjectValue<{ - tableref: string; - page: string; - pos: number; - }> - & Record; +type TableRowObject = ObjectValue<{ + tableref: string; + page: string; + pos: number; +}> & + Record; /** * Replace any invalid characters in a string so it can serve as indexed field name in a query @@ -38,7 +37,10 @@ function cleanHeaderFieldName(str: string): string { * @returns text of all child nodes */ function concatChildrenTexts(nodes: ParseTree[]): string { - return nodes.map((c) => c.text).join("").trim(); + return nodes + .map((c) => c.text) + .join("") + .trim(); } /** @@ -47,7 +49,10 @@ function concatChildrenTexts(nodes: ParseTree[]): string { * @returns text, preserving links, of all child nodes */ function concatChildrenTextsPreserveLinks(nodes: ParseTree[]): string { - return nodes.map((c) => renderToText(c)).join("").trim(); + return nodes + .map((c) => renderToText(c)) + .join("") + .trim(); } export function indexTables( @@ -60,50 +65,48 @@ export function indexTables( collectNodesMatching( tree, (t) => !!t.type?.startsWith("Table") && t.type !== "TableConstructor", - ).forEach( - (table) => { - const rows = collectNodesOfType(table, "TableRow"); - const header = collectNodesOfType(table, "TableHeader")[0]; - normalizeTableRow(header); - const headerHasLeadingDelim = - header.children?.[0]?.type === "TableDelimiter"; - const headerLabels = collectNodesOfType(header, "TableCell").map((cell) => - concatChildrenTexts(cell.children!) - ).map(cleanHeaderFieldName); + ).forEach((table) => { + const rows = collectNodesOfType(table, "TableRow"); + const header = collectNodesOfType(table, "TableHeader")[0]; + normalizeTableRow(header); + const headerHasLeadingDelim = + header.children?.[0]?.type === "TableDelimiter"; + const headerLabels = collectNodesOfType(header, "TableCell") + .map((cell) => concatChildrenTexts(cell.children!)) + .map(cleanHeaderFieldName); - for (const row of rows) { - const tags = new Set(); - collectNodesOfType(row, "Hashtag").forEach((h) => { - tags.add(extractHashtag(h.children![0].text!)); + for (const row of rows) { + const tags = new Set(); + collectNodesOfType(row, "Hashtag").forEach((h) => { + tags.add(extractHashtag(h.children![0].text!)); + }); + + normalizeTableRow(row, headerLabels.length, headerHasLeadingDelim); + + const cells = collectNodesOfType(row, "TableCell"); + + const tableRow: TableRowObject = { + tableref: `${pageMeta.name}@${table.from}`, + ref: `${pageMeta.name}@${row.from}`, + tag: "table", + tags: [...tags], + page: pageMeta.name, + pos: row.from!, + range: [row.from!, row.to!], + }; + cells.forEach((c, i) => { + replaceNodesMatching(c, (tree) => { + if (tree.type === "Hashtag") { + return null; + } }); - - normalizeTableRow(row, headerLabels.length, headerHasLeadingDelim); - - const cells = collectNodesOfType(row, "TableCell"); - - const tableRow: TableRowObject = { - tableref: `${pageMeta.name}@${table.from}`, - ref: `${pageMeta.name}@${row.from}`, - tag: "table", - tags: [...tags], - page: pageMeta.name, - pos: row.from!, - range: [row.from!, row.to!], - }; - cells.forEach((c, i) => { - replaceNodesMatching(c, (tree) => { - if (tree.type === "Hashtag") { - return null; - } - }); - const content = concatChildrenTextsPreserveLinks(c.children!); - const label = headerLabels[i]; - tableRow[label!] = content; - }); - result.push(tableRow); - } - }, - ); + const content = concatChildrenTextsPreserveLinks(c.children!); + const label = headerLabels[i]; + tableRow[label!] = content; + }); + result.push(tableRow); + } + }); return Promise.resolve(result); } diff --git a/plugs/index/tags.ts b/plugs/index/tags.ts index ea146216..574cd774 100644 --- a/plugs/index/tags.ts +++ b/plugs/index/tags.ts @@ -47,16 +47,18 @@ export function indexTags( tags.add(`${tagName}:page`); } }); - return Promise.resolve([...tags].map((tag) => { - const [tagName, parent] = tag.split(":"); - return { - ref: tag, - tag: "tag", - name: tagName, - page: pageMeta.name, - parent, - }; - })); + return Promise.resolve( + [...tags].map((tag) => { + const [tagName, parent] = tag.split(":"); + return { + ref: tag, + tag: "tag", + name: tagName, + page: pageMeta.name, + parent, + }; + }), + ); } export async function tagComplete(completeEvent: CompleteEvent) { @@ -73,13 +75,10 @@ export async function tagComplete(completeEvent: CompleteEvent) { } // Query all tags with a matching parent - const allTags: string[] = await index.queryLuaObjects( - "tag", - { - distinct: true, - select: { type: "Variable", name: "name", ctx: {} as any }, - }, - ); + const allTags: string[] = await index.queryLuaObjects("tag", { + distinct: true, + select: { type: "Variable", name: "name", ctx: {} as any }, + }); return { from: completeEvent.pos - match[0].length, @@ -91,7 +90,7 @@ export async function tagComplete(completeEvent: CompleteEvent) { } export function updateITags(obj: ObjectValue, frontmatter: FrontMatter) { - const itags = new Set([obj.tag, ...frontmatter.tags || []]); + const itags = new Set([obj.tag, ...(frontmatter.tags || [])]); for (const tag of obj.tags || []) { itags.add(tag); } diff --git a/plugs/index/task.ts b/plugs/index/task.ts index 5920c6db..cc2fc2af 100644 --- a/plugs/index/task.ts +++ b/plugs/index/task.ts @@ -153,7 +153,9 @@ export async function updateTaskState( const ref = parseToRef(path); if ( - !ref || !ref.details || !isMarkdownPath(ref.path) || + !ref || + !ref.details || + !isMarkdownPath(ref.path) || (ref.details.type !== "linecolumn" && ref.details.type !== "position") ) { console.log("No position found in page ref, skipping", ref); @@ -164,13 +166,14 @@ export async function updateTaskState( // In current page, just update the task marker with dispatch const editorText = await editor.getText(); - const targetPos = ref.details.type === "position" - ? ref.details.pos - : getOffsetFromLineColumn( - editorText, - ref.details.line, - ref.details.column, - ); + const targetPos = + ref.details.type === "position" + ? ref.details.pos + : getOffsetFromLineColumn( + editorText, + ref.details.line, + ref.details.column, + ); // Check if the task state marker is still there const targetText = editorText.substring( @@ -178,10 +181,7 @@ export async function updateTaskState( targetPos + 3 + oldState.length, ); if (targetText !== oldState) { - console.error( - "Reference not a task marker, out of date?", - targetText, - ); + console.error("Reference not a task marker, out of date?", targetText); return; } await editor.dispatch({ @@ -196,28 +196,19 @@ export async function updateTaskState( let text = await space.readPage(pageName); const referenceMdTree = await markdown.parseMarkdown(text); - const targetPos = ref.details.type === "position" - ? ref.details.pos - : getOffsetFromLineColumn( - text, - ref.details.line, - ref.details.column, - ); + const targetPos = + ref.details.type === "position" + ? ref.details.pos + : getOffsetFromLineColumn(text, ref.details.line, ref.details.column); const itemNode = nodeAtPos(referenceMdTree, targetPos + 1); if (!itemNode) { - console.error( - "Reference not a valid item, out of date?", - itemNode, - ); + console.error("Reference not a valid item, out of date?", itemNode); return; } const taskStateNode = findNodeOfType(itemNode, "TaskState"); if (!taskStateNode) { - console.error( - "Cannot find a task state", - taskStateNode, - ); + console.error("Cannot find a task state", taskStateNode); return; } taskStateNode.children![1].text = newState; @@ -262,9 +253,10 @@ export async function taskCycleCommand() { await editor.flashNotification("No task at cursor"); return; } - const taskNode = node.type === "Task" - ? node - : findParentMatching(node!, (n) => n.type === "Task"); + const taskNode = + node.type === "Task" + ? node + : findParentMatching(node!, (n) => n.type === "Task"); if (taskNode) { const taskState = findNodeOfType(taskNode!, "TaskState"); @@ -304,8 +296,10 @@ export function removeCompletedTasksFromTree( // Just finding and removing one task at a time and then repeating until nothing changes while (true) { const completedTaskNode = findNodeMatching(tree, (node) => { - return node.type === "Task" && - allCompletedStates.includes(node.children![0].children![1].text!); + return ( + node.type === "Task" && + allCompletedStates.includes(node.children![0].children![1].text!) + ); }); if (completedTaskNode) { // Ok got one, let's remove it @@ -316,14 +310,15 @@ export function removeCompletedTasksFromTree( // Also remove the adjacent whitespace/newline separator text node. // Prefer the following separator; if none, remove the preceding one. const nextChild = bulletListNode.children![listItemIdx + 1]; - const prevChild = listItemIdx > 0 - ? bulletListNode.children![listItemIdx - 1] - : undefined; + const prevChild = + listItemIdx > 0 ? bulletListNode.children![listItemIdx - 1] : undefined; if (nextChild && !nextChild.type && nextChild.text?.startsWith("\n")) { // Remove item and following separator bulletListNode.children!.splice(listItemIdx, 2); } else if ( - prevChild && !prevChild.type && prevChild.text?.startsWith("\n") + prevChild && + !prevChild.type && + prevChild.text?.startsWith("\n") ) { // Remove preceding separator and item bulletListNode.children!.splice(listItemIdx - 1, 2); @@ -345,9 +340,7 @@ export function removeCompletedTasksFromTree( if (blNext && !blNext.type && blNext.text?.startsWith("\n")) { // Remove BulletList and following separator parentChildren.splice(blIdx, 2); - } else if ( - blPrev && !blPrev.type && blPrev.text?.startsWith("\n") - ) { + } else if (blPrev && !blPrev.type && blPrev.text?.startsWith("\n")) { // Remove preceding separator and BulletList parentChildren.splice(blIdx - 1, 2); } else { @@ -366,9 +359,9 @@ export async function removeCompletedTasksCommand() { addParentPointers(tree); const allCompletedStates = completeStates.concat( - Object.values(await config.get("taskStates", {})).filter((ts: any) => - ts.done - ).map((ts: any) => ts.name), + Object.values(await config.get("taskStates", {})) + .filter((ts: any) => ts.done) + .map((ts: any) => ts.name), ); removeCompletedTasksFromTree(tree, allCompletedStates); diff --git a/plugs/plug-manager/plugmanager.ts b/plugs/plug-manager/plugmanager.ts index 6dc4c0d4..2e866bda 100644 --- a/plugs/plug-manager/plugmanager.ts +++ b/plugs/plug-manager/plugmanager.ts @@ -109,14 +109,10 @@ export async function updatePlugsCommand() { // Validate the extracted path if (builtinPlugPaths.includes(path)) { - throw new Error( - `Plug '${path}' is conflicting with a built-in plug`, - ); + throw new Error(`Plug '${path}' is conflicting with a built-in plug`); } if (allCustomPlugPaths.includes(path)) { - throw new Error( - `Plug '${path}' defined by more than one URI`, - ); + throw new Error(`Plug '${path}' defined by more than one URI`); } allCustomPlugPaths.push(path); @@ -140,7 +136,10 @@ export async function updatePlugsCommand() { await editor.flashNotification("All done!"); void system.reloadPlugs(); } catch (e: any) { - void editor.flashNotification(`Error updating plugs: ${e.message}`, "error"); + void editor.flashNotification( + `Error updating plugs: ${e.message}`, + "error", + ); } } @@ -183,9 +182,7 @@ export async function getPlugGithubRelease( ); } if (req.status !== 200) { - throw new Error( - `Could not fetch release manifest from ${identifier}`, - ); + throw new Error(`Could not fetch release manifest from ${identifier}`); } releaseInfo = await req.json(); version = releaseInfo.tag_name; @@ -198,7 +195,7 @@ export async function getPlugGithubRelease( for (const asset of releaseInfo.assets ?? []) { if ( asset.name === `${repo}.plug.js` || - shortName && asset.name === `${shortName}.plug.js` + (shortName && asset.name === `${shortName}.plug.js`) ) { assetName = asset.name; break; @@ -212,8 +209,7 @@ export async function getPlugGithubRelease( ); } - const finalUrl = - `//github.com/${owner}/${repo}/releases/download/${version}/${assetName}`; + const finalUrl = `//github.com/${owner}/${repo}/releases/download/${version}/${assetName}`; const code = await getPlugHTTPS(finalUrl); return { code: typeof code === "string" ? code : code.code, diff --git a/plugs/sync/sync.ts b/plugs/sync/sync.ts index aea3e130..57965fc5 100644 --- a/plugs/sync/sync.ts +++ b/plugs/sync/sync.ts @@ -26,14 +26,12 @@ export async function spaceSyncComplete(message: { operations: number }) { void index.ensureFullIndex(); } -export async function updateSyncStatus( - event: { - status: { - filesProcessed: number; - totalFiles: number; - }; - }, -) { +export async function updateSyncStatus(event: { + status: { + filesProcessed: number; + totalFiles: number; + }; +}) { // Update the status in the UI const percentage = Math.round( (event.status.filesProcessed / event.status.totalFiles) * 100, @@ -42,10 +40,7 @@ export async function updateSyncStatus( // Just hide it await editor.showProgress(); } else { - await editor.showProgress( - percentage, - "sync", - ); + await editor.showProgress(percentage, "sync"); } }