commit b1a375fb0642cbc161a8da3a85c76c04d83fb4d1 Author: Charlie Svitlik Date: Sun Jan 21 02:35:35 2024 -0500 Initial commit of cl-deck-builder2. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08b60af --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.fasl +*.dx32fsl +*.dx64fsl +*.lx32fsl +*.lx64fsl +*.x86f +*~ +.#* +*.sqlite +*.sqlite3 +system-index.txt \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..00672a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +ARG TARGETARCH TARGETPLATFORM TARGETVARIANT +FROM --platform=$TARGETPLATFORM docker.io/$TARGETARCH${TARGETVARIANT}/alpine:3.18.2 + +RUN addgroup -g 1000 quicklisp && adduser -D -G quicklisp -u 1000 quicklisp + +RUN set -eux; \ + apk add --no-cache \ + cgit \ + curl \ + gcc \ + git \ + imagemagick \ + libev \ + make \ + musl-dev \ + py3-markdown \ + py3-pygments \ + python3 \ + sbcl \ + sqlite \ + sqlite-libs \ + texlive \ + texlive-xetex \ + texmf-dist-latexextra \ + zxing && \ + rm -rf /var/cache/apk/* + +WORKDIR /home/quicklisp/ + +USER quicklisp + +RUN curl -O https://beta.quicklisp.org/quicklisp.lisp +RUN sbcl --noinform --no-userinit --no-sysinit --non-interactive \ + --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" \ + --eval "(ql-util:without-prompting (ql:add-to-init-file))" + +RUN sbcl --non-interactive \ + --eval "(ql:quickload '(:slynk :slynk/arglists :slynk/fancy-inspector :slynk/indentation :slynk/mrepl :slynk/profiler :slynk/trace-dialog))" + +RUN sbcl --non-interactive \ + --eval "(ql:quickload '(:assoc-utils \ + :caveman2 :clack :clack-errors :datafly :djula :envy :hunchentoot :woo \ + :can :mito-auth :lack :lack-middleware-clack-errors :lack-middleware-csrf \ + :cl-csv :cl-json :cl-org-mode :cl-pass :dbd-sqlite3 :documentation-utils \ + :documentation-utils-extensions :lispqr :mito :mito-attachment :ratify \ + :sxql :trivial-download :verbose :websocket-driver :cl-markup :hermetic \ + :inferior-shell :psychiq :djula-gettext :gettext))" + +RUN mkdir -p /home/quicklisp/public + +COPY --chown=quicklisp:quicklisp . /home/quicklisp/quicklisp/local-projects/cl-deck-builder2/ + +RUN sbcl --non-interactive \ + --eval "(ql:register-local-projects)" \ + --eval "(ql:quickload :cl-deck-builder2)" + +EXPOSE 4005 5005 + +VOLUME ["/home/quicklisp/public/"] + +CMD ["sbcl", "--load", "/home/quicklisp/quicklisp/local-projects/cl-deck-builder2/preamble.lisp", "--eval", "(cl-deck-builder2:main)"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e0ec3e2 --- /dev/null +++ b/Makefile @@ -0,0 +1,134 @@ +################################################################################ +# Target Host Architecture +ARCH ?= x86_64 + +# docker / podman +DOCKER ?= podman +DOCKER_COMPOSE ?= podman-compose +# Other programs +LISP ?= sbcl +RM ?= rm -f + +################################################################################ +# Git Revision Info +GIT_REV ?= $(shell git rev-parse master) +# Influences the Docker Image Name +IMAGE_NAME ?= cl-deck-builder2-$(GIT_REV) + +# For bind to this external address for docker-run +EXTERNAL_ADDR ?= 5005 +EXTERNAL_SLYNK_ADDR ?= 4005 + +# Platform passed to Dockerfile +PLATFORM.x86_64 ?= linux/amd64 +PLATFORM.aarch64 ?= linux/arm64/v8 +PLATFORM ?= $(PLATFORM.$(ARCH)) + +################################################################################ +# Code Borrwed from openbookstore +# List lisp files, unless they contains a # +SRC += $(shell find src/ -name '*.lisp' -a ! -name '*#*') +HTML += $(shell find templates/ -name '*.html' -a ! -name '*#*') +DEPS += $(SRC) $(HTML) $(wildcard *.asd) + +# list of supported locales +LOCALES := en_US +# Example of how the variable should look after adding a new locale: +# LOCALES := en_US en_GB fr_FR es_ES + +# list of .po files (computed from the LOCALES variable) +PO_FILES := $(foreach locale,$(LOCALES),locale/$(locale)/LC_MESSAGES/cl-deck-builder2.po) +# list of .mo files (computed from the LOCALES variable) +MO_FILES := $(foreach locale,$(LOCALES),locale/$(locale)/LC_MESSAGES/cl-deck-builder2.mo) + +################################################################################ +all: + +docker-build: Dockerfile test + $(DOCKER) buildx build --platform $(PLATFORM) -t $(IMAGE_NAME) . + +docker-run: + $(DOCKER) run --platform $(PLATFORM) \ + --rm -it -P -p $(EXTERNAL_SLYNK_ADDR):4005 -p $(EXTERNAL_ADDR):5005 \ + --userns=keep-id -v $(HOME)/public:/home/quicklisp/public:z $(IMAGE_NAME) + +docker-clean: + -$(DOCKER) image rm -f $(IMAGE_NAME) + +clean-fasl: + -find -name '*.fasl' | xargs $(RM) -fv + +clean: clean-fasl + -find -name '*~' | xargs $(RM) -fv + $(RM) -rf bin/ + +# I use a feature flag, bc using djula:*recompile-templates-on-change* +# requires to load Djula before our app, and it isn't exactly the same meaning. +build-lisp-image: ${MO_FILES} + $(LISP) --non-interactive \ + --eval '(ql:quickload "deploy")' \ + --load cl-deck-builder2.asd \ + --eval '(push :djula-binary *features*)' \ + --eval '(ql:quickload :cl-deck-builder2)' \ + --eval '(asdf:make :cl-deck-builder2)' + +# This must use a custom-built SBCL with a special parameter, +# see linux-packaging README. +# I have it under ~/.local/bin/bin/sbcl +build-package: + $(LISP) --non-interactive \ + --load cl-deck-builder2.asd \ + --eval '(ql:quickload :cl-deck-builder2)' \ + --eval '(setf *debugger-hook* (lambda (c h) (declare (ignore h)) (format t "~A~%" c) (sb-ext:quit :unix-status -1)))' \ + --eval '(asdf:make :cl-deck-builder2)' + +# build-gui: +# $(LISP) --non-interactive \ +# --load cl-deck-builder2.asd \ +# --eval '(ql:quickload :bookshops/gui)' \ +# --eval '(asdf:make :bookshops/gui)' + +test: + $(LISP) --non-interactive \ + --load cl-deck-builder2.asd \ + --load cl-deck-builder2-test.asd \ + --eval "(ql:quickload '(:cl-deck-builder2 :cl-deck-builder2-test))" \ + --eval "(asdf:test-system :cl-deck-builder2)" + +.PHONY: tr +tr: ${MO_FILES} + +PO_TEMPLATE_DIR := locale/templates/LC_MESSAGES +PO_TEMPLATE := ${PO_TEMPLATE_DIR}/cl-deck-builder2.pot + +# Rule to extract translatable strings from SRC +${PO_TEMPLATE_DIR}/lisp.pot: $(SRC) + mkdir -p $(@D) + xgettext -k_ -kN_ --language=lisp -o $@ $^ + +# Rule to extract translatable strings from djula templates +${PO_TEMPLATE_DIR}/djula.pot: $(HTML) src/i18n.lisp + $(LISP) --non-interactive \ + --eval '(ql:quickload "deploy")' \ + --eval '(asdf:load-asd (truename "cl-deck-builder2.asd"))' \ + --eval '(push :djula-binary *features*)' \ + --eval '(ql:quickload :cl-deck-builder2)' \ + --eval '(cl-deck-builder2.i18n:update-djula.pot)' + +# Rule to combine djula.pot and lisp.pot into cl-deck-builder2.pot +${PO_TEMPLATE}: ${PO_TEMPLATE_DIR}/djula.pot ${PO_TEMPLATE_DIR}/lisp.pot + msgcat --use-first $^ > $@ + +# Rule to generate or update the .po files from the .pot file +locale/%/LC_MESSAGES/cl-deck-builder2.po: ${PO_TEMPLATE} + mkdir -p $(@D) + [ -f $@ ] || msginit --locale=$* \ + --no-translator \ + -i $< \ + -o $@ \ + && msgmerge --update $@ $< + +# Rule to create the .mo files from the .po files +locale/%/LC_MESSAGES/cl-deck-builder2.mo: locale/%/LC_MESSAGES/cl-deck-builder2.po + mkdir -p $(@D) + msgfmt -o $@ $< diff --git a/README.md b/README.md new file mode 100644 index 0000000..b38fd64 --- /dev/null +++ b/README.md @@ -0,0 +1,172 @@ +# cl-deck-builder2 + +## About + +This is a project to... + +- Build TCG decks: "The Deck Builder" +- Keep track of TCG inventory: "The Inventory Manager" +- Support sale of TCG Inventory via "The Deck Builder" + +It's a [Caveman2](https://github.com/fukamachi/caveman) web app, written in [Common Lisp](https://lisp-lang.org/). + +The entire application may be built and run inside a [Docker](https://www.docker.com/) container. + +You may access a live instance at [https://phntsm.ddns.net/](https://phntsm.ddns.net/). + +## Usage + +There are three components to this project. "The Inventory Manager", +"The Deck Builder," and "The User Interface". The Deck Builder sources +in-stock products from the Inventory Manager. When the Deck Builder +constructs a Deck, it keeps a record of which cards are located in +which decks. When a Deck is purchased, this information can be updated +via a web interface. The User Interface is a convenient front-end. + +The project may be used by loading a Docker image, loading the code +directly into a Lisp sytem like [SBCL](http://sbcl.org/), or using a +development environment like [Portacle](https://portacle.github.io/). + +Once loaded, follow the code below to start the development +environment, or, if you're using the Docker image, simply type: + +```bash +% make docker-build +% make docker-run +``` + +## Installation + +First, set up [SBCL](http://sbcl.org/) and [Quicklisp](https://www.quicklisp.org/beta/). + +If you're using [Portacle](https://portacle.github.io/), you should be all set already, just clone the repository. + +Clone the master repository from +[http://phntsm.ddns.net/git/cl-deck-builder.git/](http://phntsm.ddns.net/git/cl-deck-builder.git/): + +```bash +% cd ~/quicklisp/local-projects/ +git clone http://phntsm.ddns.net/git/cl-deck-builder.git/ +``` + +```lisp +;; You may have to push whatever directory you're in to *LOCAL-PROJECT-DIRECTORIES* +(push #P"~/code/cl-deck-builder2/" ql:*local-project-directories*) + +;; Load cl-deck-builder2 and tests +(ql:quickload '(:cl-deck-builder2 :cl-deck-builder2-test)) + +;; cl-deck-builder2.asd finds src/package.lisp which contains package +;; definition - switch to this package for development. +(in-package :cl-deck-builder2) + +;; Start the server: +;; in src/main.lisp we define a custom function, MY/START, that will +;; INVOKE-RESTART if the server is already running. +;; +;; IPv4 +(my/start :address "0.0.0.0" :port 5001 :server :woo) +;; IPv6 +(my/start :address "::" :port 5001 :server :woo) + +;; For development session: +(use-package '(:cl-deck-builder2.db + :cl-deck-builder2.draw + :cl-deck-builder2.models + :cl-deck-builder2.toolkit + :cl-deck-builder2-test)) +``` + +### Requirements + +You'll need Quicklisp, and the dependencies listed in the ASDF System +Definition file, as well as the following system libraries installed. + +```bash + # Install Software + % apt install build-essential git jq sbcl + % apt install nginx fcgiwrap libev-dev sqlite3 libsqlite3-dev collectd imagemagick ruby ruby-sidekiq + + # For source code hosting + % apt install cgit python3-pygments python3-markdown +``` + +- [What is build-essential Ubuntu, how to install and use it?](https://linuxhint.com/install-build-essential-ubuntu/) +- [git](https://git-scm.com/): Version Control +- [jq](https://jqlang.github.io/jq/): Parsin JSON files on the command line +- [SBCL](http://sbcl.org/): Lisp Implementation +- [NGINX](https://www.nginx.com/): Web Server for HTTP, HTTPS, IPv4, IPv6, other services can be forwarded behind NGINX as well +- [fcgiwrap](https://www.nginx.com/resources/wiki/start/topics/examples/fcgiwrap/): Simple FastCGI wrapper for CGI scripts (The project runs as a CGI script) +- [libev](http://software.schmorp.de/pkg/libev.html): Event loop used by [woo](https://github.com/fukamachi/woo) +- [sqlite3](https://www.sqlite.org/index.html): In-memory database store +- [collectd](https://collectd.org/): collectd is a daemon which collects system and application performance metrics periodically and provides mechanisms to store the values in a variety of ways, for example in RRD files. This also nets us [RRDtool](https://oss.oetiker.ch/rrdtool/) which we can use for charts and graphs. +- [ImageMagick](https://www.imagemagick.org/): We use this to generate deck images and other various image manipulations. +- [Ruby](https://www.ruby-lang.org/en/): We use the [Sidekiq](https://sidekiq.org/) job scheduler, which is written in Ruby. + +You may want to set up [cgit](https://git.zx2c4.com/cgit/) git web. + +There is also a `Dockerfile` with a `Makefile`. + +## Author + +_Bubblegumdrop _ + +## Copyright + +Copyright (c) 2024 Bubblegumdrop + +## License + +GPLv3+ + +## Localization Workflow + +`lisp.pot` contains the string to translate that were extracted from +the `.lisp` files. + +`djula.pot` contains the strings to transtlate that were extracted +from the djula templates. + +gettext is the default translation engine. + +### Update Translations + +To build the translations: + + # Extract all {_ ... _} sequences from Djula templates + make locale/templates/LC_MESSAGES/djula.pot + + # Extract all (_ ... _) sequences from LISP code + make locale/templates/LC_MESSAGES/lisp.pot + + # Produce combined project template for translation + make locale/templates/LC_MESSAGES/cl-deck-builder2.pot + + ... make your translation edits to cl-deck-builder2.pot ... + +### Compile Translations + +To Produce the `.mo` files, add your language to `LOCALES` in the +`Makefile` then update the translations with the `tr` target: + + LOCALES := en_US ja_JP + + # Produce the translations + make tr + +The default `LANG` is currently `en_US` in `view.lisp`: + + (defun render (template-path &optional env (lang "en_US")) + "Use Djula to render a template." + (let ((template (gethash template-path *template-registry*))) + (unless template + (setf template (djula:compile-template* (princ-to-string template-path))) + (setf (gethash template-path *template-registry*) template)) + (with-locale (lang) + (apply #'djula:render-template* + template nil + env)))) + +## TODO + +See [todo.md](doc/todo.md) diff --git a/app.lisp b/app.lisp new file mode 100644 index 0000000..cd946f2 --- /dev/null +++ b/app.lisp @@ -0,0 +1,76 @@ +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.app + (:use #:cl) + (:import-from #:mito-attachment + #:*storage* + #:s3-storage + #:disk-storage + #:disk-storage-mount-path) + (:import-from #:lack.builder + #:builder) + (:import-from #:ppcre + #:scan) + (:import-from #:cl-deck-builder2.web + #:*web* + #:make-chat-server) + (:import-from #:cl-deck-builder2.config + #:config + #:productionp + #:*public-directory* + #:*static-directory*) + (:import-from #:cl-deck-builder2.toolkit + #:git-revision) + (:documentation "The package the app file executes in. Passed to ~CLACK:CLACKUP~ function.")) + +(in-package #:cl-deck-builder2.app) + +(defparameter *git-revision* (git-revision)) + +(setf *storage* + (if (productionp) + ;; Store files in AWS S3 for production environment + (make-instance 's3-storage + :bucket "mito-attachment-example" + :endpoint "s3-ap-northeast-1.amazonaws.com" + :access-key (uiop:getenv "AWS_ACCESS_KEY") + :secret-key (uiop:getenv "AWS_SECRET_KEY")) + ;; Store files in local filesystem for development environment + (make-instance 'disk-storage + :bucket "mito-attachment-example" + :directory (merge-pathnames "attachment" *public-directory*)))) + +(builder + (:static + :path (lambda (path) + (if (ppcre:scan "^(?:/img/|/css/|/js/|/webfonts/|/robot\\.txt$|/favicon\\.(ico|png)|/manifest\\.json$)" path) + path + nil)) + :root *static-directory*) + (:static + :path "/public/" + :root *public-directory*) + (if (productionp) + nil + :accesslog) + (if (getf (config) :error-log) + `(:backtrace + :output ,(getf (config) :error-log)) + nil) + :session + :csrf + (when (typep *storage* 'disk-storage) + `(:mount ,(disk-storage-mount-path *storage*) ,*storage*)) + (:clack-errors :debug t) + (if (productionp) + nil + (lambda (app) + (lambda (env) + (let ((datafly:*trace-sql* t)) + (setf (getf djula:*default-template-arguments* :git-revision) *git-revision* + (getf djula:*default-template-arguments* :server-name) "phntsm.ddns.net") + (funcall app env))))) + (:mount "/label-maker" cl-deck-builder2.web.label-maker:*web*) + (:mount "/tinymce" cl-deck-builder2.web.tinymce:*web*) + (:mount "/chat-server" #'cl-deck-builder2.web.sse:make-chat-server) + *web*) diff --git a/cl-deck-builder2-test.asd b/cl-deck-builder2-test.asd new file mode 100644 index 0000000..a821044 --- /dev/null +++ b/cl-deck-builder2-test.asd @@ -0,0 +1,11 @@ +(asdf:defsystem "cl-deck-builder2-test" + :author "Bubblegumdrop" + :license "Do Not Redistribute" + :depends-on ("cl-deck-builder2" + "rove") + :components ((:module "tests" + :components + ((:file "packages") + (:file "cl-deck-builder2")))) + :description "Test system for cl-deck-builder2" + :perform (asdf:test-op (op c) (uiop:symbol-call :rove :run c))) diff --git a/cl-deck-builder2.asd b/cl-deck-builder2.asd new file mode 100644 index 0000000..6e0a4fc --- /dev/null +++ b/cl-deck-builder2.asd @@ -0,0 +1,250 @@ +;;;; cl-deck-builder2.asd +;;;; +;;;; ASDF System Definition File for "The Deck Builder" Common Lisp Project. +;;;; +;;;; You can read more about ASDF here: +;;;; +;;;; + +;; (require "asdf") ;; for CI + +(pushnew :verbose-no-init *features*) + +(asdf:defsystem "cl-deck-builder2" + :version "0.4.0" + :author "Bubblegumdrop" + :license "Do Not Redistribute" + :depends-on ("clack" + "lack" + "caveman2" + "envy" + "cl-ppcre" + "uiop" + + ;; for @route annotation + ;; "cl-syntax-annot" + + ;; HTML Template + "djula" + "djula-gettext" + "gettext" + + ;; for DB + "mito" + "mito-auth" + "mito-attachment" + "datafly" + "sxql" + "dbd-sqlite3" + + ;; for Models + "assoc-utils" ; AGET is very handy + "cl-csv" ; CSV import + "cl-json" ; JSON import + "local-time" + "split-sequence" + "can" + + ;; Documentation! + ;; "documentation-utils" + ;; "documentation-utils-extensions" + "cl-org-mode" + + ;; More of Shinmeraa's code + "ratify" + "deploy" + + ;; QR Code Generator + "lispqr" + + ;; Download stuff + "trivial-download" + + ;; Error Handling + "clack-errors" + "lack-middleware-clack-errors" + + ;; Better logging + "verbose" + + ;; WebSocket chat server backend + "websocket-driver" + "lack-middleware-csrf" + + "cl-markup" + "hermetic" + + "inferior-shell" + + ;; Background job processing + "psychiq" + + "cl-fad" + "slynk") + :components ((:module "src" + :components + ((:file "main" :depends-on ("config" "view" "db" "draw" "web")) + (:file "config") + (:file "db" :depends-on ("config" "toolkit")) + (:file "view" :depends-on ("config" "i18n")) + (:file "draw" :depends-on ("models")) + (:file "i18n") + (:module "toolkit" + :components + ((:file "packages") + (:file "paths") + (:file "git") + (:file "time") + (:file "money") + (:file "utils"))) + (:module "models" + :components + (;; (:file "toolkit") + (:file "packages") + ;; (:file "model") + (:file "registered-table-mixin") + (:file "attachment") + (:file "category") + (:file "constructed-decks" :depends-on ("ydk" "ygoprodeck-classes" "ygoprodeck-methods" "ygoprodeck-fields")) + (:file "crystal-commerce" :depends-on ("attachment" "ygoprodeck")) + (:file "feedback" :depends-on ("user")) + (:file "label-maker") + (:file "qr-code") + (:file "sqlite-schema") + (:file "user") + (:file "ydk" :depends-on ("ygoprodeck")) + (:file "ygoprodeck") + (:file "ygoprodeck-fields") + (:file "ygoprodeck-classes" :depends-on ("ygoprodeck-fields")) + (:file "ygoprodeck-methods") + (:file "ygoprodeck-cardinfo") + (:file "ygoprodeck-json")) + :depends-on ("db" "toolkit")) + (:module "web" + :components + ((:file "packages") + (:file "setup") + (:file "flash-message") + (:file "params") + (:file "query") + (:file "toolkit") + (:file "index") + (:file "builder" :depends-on ("search")) + (:file "cards") + (:file "constructed-decks") + (:file "category") + (:file "contact") + (:file "decks") + (:file "inventory" :depends-on ("search")) + (:file "qr") + (:file "search") + (:file "sse") + (:file "upload") + (:file "user") + (:file "ydk") + (:file "label-maker" :depends-on ("query")) + (:file "tinymce")) + :depends-on ("db" "i18n" "view" "models" "draw" "toolkit")))) + ;; TODO seeing this all here makes me want to really compress this. + (:module "templates" + :components + ;; Order is important: the ones that extend default.html + ;; must be declared after it, because we compile all of them + ;; at build time. + ((:STATIC-FILE "ydk/index.html") (:STATIC-FILE "user/user-menu-bar.html") + (:STATIC-FILE "user/user-list.html") (:STATIC-FILE "user/register.html") + (:STATIC-FILE "user/login.html") (:STATIC-FILE "user/index.html") + (:STATIC-FILE "user/admin.html") (:STATIC-FILE "upload.html") + (:STATIC-FILE "template.Dockerfile") (:STATIC-FILE "tabs.html") + (:STATIC-FILE "qr.html") (:STATIC-FILE "pages.html") + (:STATIC-FILE "news.html") (:STATIC-FILE "navbar.html") + (:STATIC-FILE "navbar-js.html") (:STATIC-FILE "messages.html") + (:STATIC-FILE "markdown.html") (:STATIC-FILE "layouts/minimal.html") + (:STATIC-FILE "layouts/html-head.html") (:STATIC-FILE "layouts/default.html") + (:STATIC-FILE "latex/default.tpl.tex") + (:STATIC-FILE "latex/ULINE-S-20247.tpl.tex") + (:STATIC-FILE "latex/ULINE-S-20247.tex") + (:STATIC-FILE "latex/Avery-5160.tpl.tex") + (:STATIC-FILE "latex/Avery-5160.tex") (:STATIC-FILE "label-maker.html") + (:STATIC-FILE "kde-team.html") (:STATIC-FILE "inventory/variant-results.html") + (:STATIC-FILE "inventory/search-results.html") + (:STATIC-FILE "inventory/old.html") (:STATIC-FILE "inventory/new.html") + (:STATIC-FILE "inventory/index.html") (:STATIC-FILE "inventory/import.html") + (:STATIC-FILE "inventory/edit.html") (:STATIC-FILE "inventory/base-menu.html") + (:STATIC-FILE "inventory/advanced-search.html") + (:STATIC-FILE "inventory/_view_result.html") + (:STATIC-FILE "inventory/_search_result_th.html") + (:STATIC-FILE "inventory/_new_row.html") + (:STATIC-FILE "inventory/_inventory_variants.html") + (:STATIC-FILE "inventory/_edit_row.html") + (:STATIC-FILE "inventory/_edit_result.html") + (:STATIC-FILE "inventory/_advanced_search_select.html") + (:STATIC-FILE "inventory/_advanced_search_input.html") + (:STATIC-FILE "index.html") (:STATIC-FILE "footer.html") + (:STATIC-FILE "file-js.html") (:STATIC-FILE "docs.md") + (:STATIC-FILE "docs.html") (:STATIC-FILE "decks/search.html") + (:STATIC-FILE "decks/saved-category-list.html") + (:STATIC-FILE "decks/index.html") + (:STATIC-FILE "decks/decks-by-category.html") + (:STATIC-FILE "decks/deck-text-three-column.html") + (:STATIC-FILE "decks/deck-text-textarea.html") + (:STATIC-FILE "decks/deck-text-list.html") + (:STATIC-FILE "decks/deck-text-list-original.html") + (:STATIC-FILE "decks/deck-select.html") + (:STATIC-FILE "decks/deck-menu-bar.html") + (:STATIC-FILE "decks/deck-image-listing.html") + (:STATIC-FILE "decks/deck-html-results.html") + (:STATIC-FILE "decks/deck-html-results-1.html") + (:STATIC-FILE "decks/deck-controls.html") + (:STATIC-FILE "decks/category-select.html") + (:STATIC-FILE "decks/cards-in-decks.html") + (:STATIC-FILE "decks/_field_control_select.html") + (:STATIC-FILE "contact/list.html") (:STATIC-FILE "contact/index.html") + (:STATIC-FILE "contact/admin.html") + (:STATIC-FILE "construct/select-sets.html") + (:STATIC-FILE "construct/pull-menu-bar.html") + (:STATIC-FILE "construct/index.html") (:STATIC-FILE "chat.html") + (:STATIC-FILE "category/index.html") (:STATIC-FILE "category/explain.html") + (:STATIC-FILE "category/child-list.html") + (:STATIC-FILE "category/child-form.html") + (:STATIC-FILE "category/category-menu-bar.html") + (:STATIC-FILE "cards/view.html") (:STATIC-FILE "cards/search-results.html") + (:STATIC-FILE "cards/info.html") (:STATIC-FILE "cards/index.html") + (:STATIC-FILE "cards/cc-item-view.html") + (:STATIC-FILE "cards/cc-item-row.html") + (:STATIC-FILE "cards/_search_result_th.html") + (:STATIC-FILE "cards/_advanced_search_top.html") + (:STATIC-FILE "cards/_advanced_search_select_kind.html") + (:STATIC-FILE "cards/_advanced_search_select.html") + (:STATIC-FILE "cards/_advanced_search_numeric.html") + (:STATIC-FILE "cards/_advanced_search_input_numeric.html") + (:STATIC-FILE "cards/_advanced_search_attrs.html") + (:STATIC-FILE "builder/search-results.html") + (:STATIC-FILE "builder/saved-deck-list.html") + (:STATIC-FILE "builder/saved-category-list.html") + (:STATIC-FILE "builder/index.html") + (:STATIC-FILE "builder/deck-controls.html") + (:STATIC-FILE "builder/current-deck-list.html") + (:STATIC-FILE "builder/base-menu.html") + (:STATIC-FILE "builder/_search_results_th.html") + (:STATIC-FILE "builder/_jquery_droppable.html") + (:STATIC-FILE "builder/_current_deck_list-1.html") + (:STATIC-FILE "builder/_advanced_search_select_kind.html") + (:STATIC-FILE "builder/_advanced_search_select.html") + (:STATIC-FILE "builder/_advanced_search_numeric.html") + (:STATIC-FILE "builder/_advanced_search_input_numeric.html") + (:STATIC-FILE "builder/_advanced_search_attrs.html") + (:STATIC-FILE "_errors/404.html")))) + :description "Deck Builder Project" + + :defsystem-depends-on (:deploy) ;; (ql:quickload "deploy") before + :build-operation "deploy-op" ;; instead of "program-op" + :build-pathname "cl-deck-builder2" + :entry-point "cl-deck-builder2:my/start" + + :in-order-to ((test-op (test-op "cl-deck-builder2-test")))) + +;; Use compression: from 108M, 0.04s startup time to 24M, 0.37s. +#+sb-core-compression +(defmethod asdf:perform ((o asdf:image-op) (c asdf:system)) + (uiop:dump-image (asdf:output-file o c) :executable t :compression t)) diff --git a/cl-deck-builder2.service b/cl-deck-builder2.service new file mode 100644 index 0000000..b2ae006 --- /dev/null +++ b/cl-deck-builder2.service @@ -0,0 +1,15 @@ +[Unit] +Description=Deck Builder Service +After=podman.service +Requires=podman.service + +[Service] +TimeoutStartSec=0 +Restart=always +#ExecStartPre=-/usr/bin/podman stop %n +#ExecStartPre=-/usr/bin/podman rm %n +#ExecStartPre=/usr/bin/podman pull redis +ExecStart=make -C /home/deck/ docker-run + +[Install] +WantedBy=multi-user.target diff --git a/cl-deck-builder2.workers.asd b/cl-deck-builder2.workers.asd new file mode 100644 index 0000000..bb78958 --- /dev/null +++ b/cl-deck-builder2.workers.asd @@ -0,0 +1,68 @@ +(asdf:defsystem "cl-deck-builder2.workers" + :version "0.0.1" + :author "Bubblegumdrop" + :license "Do Not Redistribute" + :depends-on ( + ;; "clack" + ;; "lack" + ;; "caveman2" + ;; "envy" + ;; "cl-ppcre" + ;; "uiop" + + ;; for @route annotation + ;; "cl-syntax-annot" + + ;; HTML Template + ;; "djula" + + ;; for DB + "mito" + "mito-attachment" + "datafly" + "sxql" + + ;; for Models + "assoc-utils" ; AGET is very handy + "cl-csv" ; CSV import + "cl-json" ; JSON import + "cl-pass" ; Password hashing + "local-time" + "split-sequence" + + ;; Documentation! + ;; "documentation-utils" + ;; "documentation-utils-extensions" + ;; "cl-org-mode" + + ;; More of Shinmeraa's code + ;; "ratify" + + ;; QR Code Generator + ;; "lispqr" + + ;; Download stuff + ;; "trivial-download" + + ;; Error Handling + ;; "lack-middleware-clack-errors" + + ;; Better logging + ;; "verbose" + + ;; WebSocket chat server backend + ;; "websocket-driver" + ;; "lack-middleware-csrf" + + ;; "cl-markup" + "hermetic" + + "inferior-shell" + + ;; Background job processing + "psychiq") + :components ((:module "src/workers" + :components + ((:file "main")))) + :description "Deck Builder Project - Background Job Workers" + :in-order-to ((test-op (test-op "cl-deck-builder2.workers-test")))) diff --git a/contrib.sh b/contrib.sh new file mode 100644 index 0000000..836cd02 --- /dev/null +++ b/contrib.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# Start some stuff: Sidekiq Web UI, collectd web UI, MariaDB container, Redis container. +# +# Looks like shell scripts are also going into here. You may additionally backup_database. +# +# Hey it's starting to turn into a real deploy script! + +set -euox + +DOCKER=podman +MARIADB_IMAGE=localhost/alpine-mariadb +REDIS_IMAGE=localhost/redis-7.2-rc-alpine +SCHEMACRAWLER_DIR=$HOME/schemacrawler-16.20.4-bin + +function message(){ + plus="+" + [[ $1 -ne 0 ]] && plus=- + shift + echo "[${plus}] $@" +} + +# This will probably either go away or be moved to mariadb database as we migrate (eventually) +function backup_database() { + shift + running_container= + if [ $# -eq 0 ] + then + running_container=$(${DOCKER} ps --format '{{.Image}} {{.Names}}' | grep cl-deck-builder2 | awk '{print $2}') + else + running_container="$1" + fi + if [ "x$running_container" = "x" ] + then + message 1 "deck builder container not running?" + else + message 0 "found $running_container" + message 0 "copying database $running_container:/home/quicklisp/quicklisp/local-projects/cl-deck-builder2/deck_builder.sqlite3" + $DOCKER cp "$running_container:/home/quicklisp/quicklisp/local-projects/cl-deck-builder2/deck_builder.sqlite3" \ + ~/sqlite3/$(date '+%Y-%m-%d')-deck_builder.sqlite3 + ls -l ~/sqlite3/$(date '+%Y-%m-%d')-deck_builder.sqlite3 + fi +} + +function psychiq_up(){ + ps -C rackup >/dev/null + if [ $? -ne 0 ] + then + message 0 "Starting Psychiq Web UI" + rackup /home/user/code/cl-deck-builder2/ruby/config.ru & + else + message 1 "Psychiq Web UI already running." + fi +} + +function collectd_up() { + ps -C python3 >/dev/null + if [ $? -ne 0 ] + then + message 0 "Starting collectd Web UI" + pushd /home/user/code/alpine-collectd-web/ + python3 runserver.py & + popd + else + message 1 "collectd Web UI already running." + fi +} + +function mariadb_up (){ + mkdir -p ~/mariadb + $DOCKER ps 2>&1| grep 3306 2>&1>/dev/null + if [ $? -ne 0 ] + then + $DOCKER run --restart always -d -P -p 3306:3306 -v $HOME/mariadb:/var/lib/mysql $MARIADB_IMAGE + else + message 1 "mariadb already running." + fi +} + +function redis_up(){ + mkdir -p ~/redis + $DOCKER ps 2>&1| grep 6379 2>&1>/dev/null + if [ $? -ne 0 ] + then + $DOCKER run --restart always -d -P -p 6379:6379 -v $HOME/redis:/data $REDIS_IMAGE + else + message 1 "redis already running." + fi +} + +function schemacrawler(){ + ORIG_DIR="$(pwd)" + cd $SCHEMACRAWLER_DIR + # Gentoo Strikes Again! + # https://github.com/schemacrawler/SchemaCrawler/issues/394 + _JAVA_OPTIONS=-Djava.io.tmpdir=. \ + bash bin/schemacrawler.sh \ + --user= \ + --password= \ + --database "$ORIG_DIR/deck_builder.sqlite3" \ + --server sqlite \ + --weak-associations=true \ + -i standard \ + -c schema \ + -o ~/www/db.png + + chown :www-data ~/www/db.png +} + +function main(){ + # backup_database + psychiq_up + collectd_up + mariadb_up + redis_up +} + +if [ $# -lt 1 ] +then + message 1 "Usage: $0 [main | backup_database | psychiq_up | collectd_up | mariadb_up | redis_up | schemacrawler]" +else + "$1" "$@" +fi diff --git a/db/file.csv b/db/file.csv new file mode 100644 index 0000000..209b12f --- /dev/null +++ b/db/file.csv @@ -0,0 +1,393 @@ +type,name,tbl_name,rootpage,sql +table,deck_item,deck_item,2,"CREATE TABLE ""deck_item"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""deck_id"" INTEGER NOT NULL, + ""kind"" INTEGER NOT NULL, + ""passcode"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,sqlite_sequence,sqlite_sequence,3,"CREATE TABLE sqlite_sequence(name,seq)" +table,ygo_info,ygo_info,8,"CREATE TABLE ""ygo_info"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""desc"" STRING NOT NULL, + ""frame_type"" STRING NOT NULL, + ""type"" STRING NOT NULL, + ""passcode"" INTEGER NOT NULL, + ""name"" STRING NOT NULL, + ""race"" STRING NOT NULL, + ""archetype"" STRING, + ""attribute"" STRING, + ""linkmarkers"" STRING, + ""atk"" INTEGER, + ""def"" INTEGER, + ""level"" INTEGER, + ""linkval"" INTEGER, + ""scale"" INTEGER, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,users-feedback,users-feedback,12,"CREATE TABLE ""users-feedback"" ( + ""rowid"" INTEGER PRIMARY KEY, + ""userid"" INTEGER, + ""text"" STRING, + ""created-at"" STRING, + ""updated-at"" STRING +)" +table,ygoprodeck-card-data,ygoprodeck-card-data,13,"CREATE TABLE ""ygoprodeck-card-data"" ( + ""rowid"" INTEGER PRIMARY KEY, + ""passcode"" INTEGER, + ""name"" STRING, + ""type"" STRING, + ""frame-type"" STRING, + ""desc"" STRING, + ""race"" STRING, + ""archetype"" STRING, + ""created-at"" STRING, + ""updated-at"" STRING +)" +table,ygoprodeck-card-sets,ygoprodeck-card-sets,14,"CREATE TABLE ""ygoprodeck-card-sets"" ( + ""rowid"" INTEGER PRIMARY KEY, + ""passcode"" INTEGER, + ""name"" STRING, + ""code"" STRING, + ""rarity"" STRING, + ""rarity-code"" STRING, + ""price"" STRING, + ""created-at"" STRING, + ""updated-at"" STRING +)" +table,attachment,attachment,21,"CREATE TABLE ""attachment"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""file_key"" VARCHAR(255) NOT NULL, + ""content_type"" VARCHAR(255) NOT NULL, + ""file_size"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""file_key"") +)" +index,sqlite_autoindex_attachment_1,attachment,22, +table,ydk_deck,ydk_deck,23,"CREATE TABLE ""ydk_deck"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""category_id"" INTEGER, + ""created_by"" STRING NOT NULL, + ""name"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""name"") +)" +index,sqlite_autoindex_ydk_deck_1,ydk_deck,24, +table,category,category,27,"CREATE TABLE ""category"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""name"" STRING NOT NULL, + ""left"" INTEGER NOT NULL, + ""right"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,cc_item,cc_item,2939,"CREATE TABLE ""cc_item"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""product_name"" STRING NOT NULL, + ""category"" STRING NOT NULL, + ""wishlists"" INTEGER NOT NULL, + ""buy_price"" REAL NOT NULL, + ""sell_price"" REAL NOT NULL, + ""url"" STRING, + ""barcode"" STRING, + ""manufacturer_sku"" STRING, + ""amazon_asin"" STRING, + ""msrp"" REAL, + ""brand"" STRING, + ""weight"" REAL NOT NULL, + ""description"" STRING, + ""max_qty"" INTEGER, + ""total_qty"" INTEGER NOT NULL, + ""domestic_only"" INTEGER NOT NULL, + ""tax_exempt"" INTEGER NOT NULL, + ""name"" STRING, + ""code"" STRING, + ""rarity"" STRING, + ""edition"" STRING, + ""passcode"" INTEGER, + ""condition"" STRING NOT NULL, + ""language"" STRING NOT NULL, + ""opt_qty"" INTEGER NOT NULL, + ""reserved_qty"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_misc,ygo_misc,31807,"CREATE TABLE ""ygo_misc"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode"" INTEGER NOT NULL, + ""beta_name"" INTEGER NOT NULL, + ""views"" INTEGER NOT NULL, + ""viewsweek"" INTEGER NOT NULL, + ""upvotes"" INTEGER NOT NULL, + ""downvotes"" INTEGER NOT NULL, + ""tcg_date"" STRING NOT NULL, + ""ocg_date"" STRING NOT NULL, + ""konami_id"" INTEGER NOT NULL, + ""has_effect"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,feedback,feedback,7,"CREATE TABLE ""feedback"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""body"" STRING NOT NULL, + ""user_id"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,constructed_deck_item,constructed_deck_item,30965,"CREATE TABLE ""constructed_deck_item"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""name_id"" INTEGER NOT NULL, + ""code_id"" INTEGER NOT NULL, + ""rarity_id"" INTEGER NOT NULL, + ""edition_id"" INTEGER NOT NULL, + ""price"" INTEGER NOT NULL, + ""origin_id_id"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,constructed_decks,constructed_decks,30966,"CREATE TABLE ""constructed_decks"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""name"" STRING NOT NULL, + ""passcode_id"" INTEGER NOT NULL, + ""sell_price"" INTEGER NOT NULL, + ""origin_id_id"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_cards_misc_info,ygo_cards_misc_info,2601,"CREATE TABLE ""ygo_cards_misc_info"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""views"" INTEGER NOT NULL, + ""viewsweek"" INTEGER NOT NULL, + ""upvotes"" INTEGER NOT NULL, + ""downvotes"" INTEGER NOT NULL, + ""beta_id"" INTEGER, + ""beta_name_id"" INTEGER, + ""staple"" INTEGER, + ""konami_id"" INTEGER NOT NULL, + ""treated_as"" INTEGER, + ""has_effect"" INTEGER, + ""question_atk"" INTEGER, + ""question_def"" INTEGER, + ""tcg_date"" TIMESTAMP, + ""ocg_date"" TIMESTAMP, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_set_rarity_code_abbr,ygo_set_rarity_code_abbr,1758,"CREATE TABLE ""ygo_set_rarity_code_abbr"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""code"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""code"") +)" +index,sqlite_autoindex_ygo_set_rarity_code_abbr_1,ygo_set_rarity_code_abbr,1759, +table,get_column_real_type,get_column_real_type,4,"CREATE TABLE get_column_real_type ( + test TIMESTAMP +)" +table,ygo_card_name,ygo_card_name,17,"CREATE TABLE ""ygo_card_name"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""name"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""name"") +)" +index,sqlite_autoindex_ygo_card_name_1,ygo_card_name,1501, +table,ygo_card_type,ygo_card_type,1512,"CREATE TABLE ""ygo_card_type"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""card_type"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""card_type"") +)" +index,sqlite_autoindex_ygo_card_type_1,ygo_card_type,1515, +table,ygo_card_frame_type,ygo_card_frame_type,1517,"CREATE TABLE ""ygo_card_frame_type"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""frame_type"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""frame_type"") +)" +index,sqlite_autoindex_ygo_card_frame_type_1,ygo_card_frame_type,1539, +table,ygo_card_race,ygo_card_race,1541,"CREATE TABLE ""ygo_card_race"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""race"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""race"") +)" +index,sqlite_autoindex_ygo_card_race_1,ygo_card_race,1544, +table,ygo_card_archetype,ygo_card_archetype,1554,"CREATE TABLE ""ygo_card_archetype"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""archetype"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""archetype"") +)" +index,sqlite_autoindex_ygo_card_archetype_1,ygo_card_archetype,1562, +table,ygo_card_attribute,ygo_card_attribute,1566,"CREATE TABLE ""ygo_card_attribute"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""attribute"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""attribute"") +)" +index,sqlite_autoindex_ygo_card_attribute_1,ygo_card_attribute,1567, +table,ygo_card,ygo_card,1571,"CREATE TABLE ""ygo_card"" ( + ""id"" INTEGER NOT NULL PRIMARY KEY, + ""name_id"" INTEGER NOT NULL, + ""desc"" STRING NOT NULL, + ""archetype_id"" INTEGER, + ""attribute_id"" INTEGER, + ""atk"" INTEGER, + ""def"" INTEGER, + ""scale"" INTEGER, + ""level"" INTEGER, + ""frame_type_id"" INTEGER NOT NULL, + ""race_id"" INTEGER NOT NULL, + ""card_type_id"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_card_misc_info,ygo_card_misc_info,1576,"CREATE TABLE ""ygo_card_misc_info"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""views"" INTEGER NOT NULL, + ""viewsweek"" INTEGER NOT NULL, + ""upvotes"" INTEGER NOT NULL, + ""downvotes"" INTEGER NOT NULL, + ""beta_id"" INTEGER, + ""beta_name_id"" INTEGER, + ""staple"" INTEGER, + ""konami_id"" INTEGER NOT NULL, + ""treated_as_id"" INTEGER, + ""has_effect"" INTEGER, + ""question_atk"" INTEGER, + ""question_def"" INTEGER, + ""tcg_date"" TIMESTAMP, + ""ocg_date"" TIMESTAMP, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_set_name,ygo_set_name,1583,"CREATE TABLE ""ygo_set_name"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""set_name"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""set_name"") +)" +index,sqlite_autoindex_ygo_set_name_1,ygo_set_name,1585, +table,ygo_set_code,ygo_set_code,1590,"CREATE TABLE ""ygo_set_code"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""set_code"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""set_code"") +)" +index,sqlite_autoindex_ygo_set_code_1,ygo_set_code,1591, +table,ygo_set_rarity,ygo_set_rarity,1592,"CREATE TABLE ""ygo_set_rarity"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""rarity"" STRING NOT NULL, + ""code"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_set_edition,ygo_set_edition,1594,"CREATE TABLE ""ygo_set_edition"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""set_edition"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""set_edition"") +)" +index,sqlite_autoindex_ygo_set_edition_1,ygo_set_edition,1596, +table,ygo_linkmarker_name,ygo_linkmarker_name,1600,"CREATE TABLE ""ygo_linkmarker_name"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""name"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_linkmarker,ygo_linkmarker,1601,"CREATE TABLE ""ygo_linkmarker"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""bottom_id"" INTEGER, + ""bottom_left_id"" INTEGER, + ""bottom_right_id"" INTEGER, + ""left_id"" INTEGER, + ""right_id"" INTEGER, + ""top_id"" INTEGER, + ""top_left_id"" INTEGER, + ""top_right_id"" INTEGER, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_format_name,ygo_format_name,1603,"CREATE TABLE ""ygo_format_name"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""name"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_format,ygo_format,1613,"CREATE TABLE ""ygo_format"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER, + ""common_charity_id"" INTEGER, + ""duel_links_id"" INTEGER, + ""edison_id"" INTEGER, + ""goat_id"" INTEGER, + ""ocg_id"" INTEGER, + ""ocg_goat_id"" INTEGER, + ""speed_duel_id"" INTEGER, + ""tcg_id"" INTEGER, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_banlist_name,ygo_banlist_name,1620,"CREATE TABLE ""ygo_banlist_name"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""name"" STRING NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_banlist,ygo_banlist,1624,"CREATE TABLE ""ygo_banlist"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""ban_goat_id"" INTEGER, + ""ban_ocg_id"" INTEGER, + ""ban_tcg_id"" INTEGER, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP, + UNIQUE (""passcode_id"") +)" +index,sqlite_autoindex_ygo_banlist_1,ygo_banlist,1625, +table,ygo_alternative_artwork,ygo_alternative_artwork,1629,"CREATE TABLE ""ygo_alternative_artwork"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""alternate_id"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_price,ygo_price,1632,"CREATE TABLE ""ygo_price"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""cardmarket_price"" INTEGER NOT NULL, + ""tcgplayer_price"" INTEGER NOT NULL, + ""ebay_price"" INTEGER NOT NULL, + ""amazon_price"" INTEGER NOT NULL, + ""coolstuffinc_price"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" +table,ygo_set,ygo_set,1597,"CREATE TABLE ""ygo_set"" ( + ""id"" INTEGER PRIMARY KEY AUTOINCREMENT, + ""passcode_id"" INTEGER NOT NULL, + ""name_id"" INTEGER NOT NULL, + ""code_id"" INTEGER NOT NULL, + ""rarity_id"" INTEGER NOT NULL, + ""edition_id"" INTEGER NOT NULL, + ""price"" INTEGER NOT NULL, + ""created_at"" TIMESTAMP, + ""updated_at"" TIMESTAMP +)" diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..dd04ba5 --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,421 @@ +CREATE TABLE feedback ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + body TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE user_role ( + user_id INTEGER NOT NULL, + role_name VARCHAR(64) NOT NULL, + PRIMARY KEY (user_id, role_name) +); +CREATE TABLE role ( + name VARCHAR(64) NOT NULL, + PRIMARY KEY (name) +); +CREATE TABLE user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL, + password_hash CHAR(64) NOT NULL, + password_salt BINARY(20) NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (email) +); +CREATE TABLE ygo_cc_item ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + item_id INTEGER NOT NULL, + variant_id INTEGER NOT NULL, + qty INTEGER NOT NULL, + opt_qty INTEGER NOT NULL, + buy_price INTEGER NOT NULL, + sell_price INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE cc_item ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + category TEXT NOT NULL, + total_qty INTEGER NOT NULL, + wishlists INTEGER NOT NULL, + buy_price INTEGER NOT NULL, + sell_price INTEGER NOT NULL, + url TEXT, + barcode TEXT, + manufacturer_sku TEXT, + amazon_asin TEXT, + msrp INTEGER, + brand TEXT, + weight TEXT NOT NULL, + description TEXT, + max_qty INTEGER, + domestic_only INTEGER NOT NULL, + tax_exempt INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE sold_decks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + category_id INTEGER, + created_by TEXT NOT NULL, + sell_price INTEGER, + ydk_deck_id INTEGER NOT NULL, + deck_sold INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE constructed_deck ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + category_id INTEGER, + created_by TEXT NOT NULL, + sell_price INTEGER, + ydk_deck_id INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE constructed_deck_item ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ygo_card_id INTEGER NOT NULL, + deck_id INTEGER NOT NULL, + sell_price INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_set_item ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + item_id INTEGER NOT NULL, + variant_id INTEGER NOT NULL, + qty INTEGER NOT NULL, + opt_qty INTEGER NOT NULL, + buy_price INTEGER NOT NULL, + sell_price INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_price ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER NOT NULL, + cardmarket_price INTEGER NOT NULL, + tcgplayer_price INTEGER NOT NULL, + ebay_price INTEGER NOT NULL, + amazon_price INTEGER NOT NULL, + coolstuffinc_price INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_alternative_artwork ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER NOT NULL, + alternate_id INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_banlist ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER NOT NULL, + ban_goat_id INTEGER, + ban_ocg_id INTEGER, + ban_tcg_id INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (passcode_id) +); +CREATE TABLE ygo_format ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER, + common_charity_id INTEGER, + duel_links_id INTEGER, + edison_id INTEGER, + goat_id INTEGER, + ocg_id INTEGER, + ocg_goat_id INTEGER, + speed_duel_id INTEGER, + tcg_id INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_linkmarker ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER NOT NULL, + bottom_id INTEGER, + bottom_left_id INTEGER, + bottom_right_id INTEGER, + left_id INTEGER, + right_id INTEGER, + top_id INTEGER, + top_left_id INTEGER, + top_right_id INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_set ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER NOT NULL, + name_id INTEGER NOT NULL, + code_id INTEGER NOT NULL, + rarity_id INTEGER NOT NULL, + rarity_code_id INTEGER NOT NULL, + edition_id INTEGER NOT NULL, + url TEXT NOT NULL, + price INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_set_edition ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_set_rarity ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_set_code ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_set_name ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_card_misc_info ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode_id INTEGER NOT NULL, + views INTEGER NOT NULL, + viewsweek INTEGER NOT NULL, + upvotes INTEGER NOT NULL, + downvotes INTEGER NOT NULL, + beta_id INTEGER, + beta_name_id INTEGER, + staple INTEGER, + konami_id INTEGER NOT NULL, + treated_as_id INTEGER, + has_effect INTEGER, + question_atk INTEGER, + question_def INTEGER, + tcg_date TIMESTAMP, + ocg_date TIMESTAMP, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_card ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + desc TEXT NOT NULL, + archetype_id INTEGER, + attribute_id INTEGER, + atk INTEGER, + def INTEGER, + scale INTEGER, + level INTEGER, + frame_type_id INTEGER NOT NULL, + race_id INTEGER NOT NULL, + card_type_id INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_card_attribute ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_card_archetype ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_card_race ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_card_frame_type ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_card_type ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_card_name ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_name_mixin ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_set_rarity_code ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE ygo_linkmarker_name ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_format_name ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_banlist_name ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE variant_language ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE variant_condition ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE variant ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ydk_deck ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category_id INTEGER, + created_by TEXT NOT NULL, + name TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (name) +); +CREATE TABLE deck_item ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + deck_id INTEGER NOT NULL, + kind INTEGER NOT NULL, + passcode INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_format ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode INTEGER NOT NULL, + format TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_misc ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode INTEGER NOT NULL, + beta_name INTEGER NOT NULL, + views INTEGER NOT NULL, + viewsweek INTEGER NOT NULL, + upvotes INTEGER NOT NULL, + downvotes INTEGER NOT NULL, + tcg_date TEXT NOT NULL, + ocg_date TEXT NOT NULL, + konami_id INTEGER NOT NULL, + has_effect INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_price ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + passcode INTEGER NOT NULL, + cardmarket REAL NOT NULL, + tcgplayer REAL NOT NULL, + ebay REAL NOT NULL, + amazon REAL NOT NULL, + coolstuffinc REAL NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_set ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT NOT NULL, + name TEXT NOT NULL, + passcode INTEGER NOT NULL, + price REAL NOT NULL, + rarity TEXT NOT NULL, + rarity_code TEXT NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE ygo_info ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + desc TEXT NOT NULL, + frame_type TEXT NOT NULL, + type TEXT NOT NULL, + passcode INTEGER NOT NULL, + name TEXT NOT NULL, + race TEXT NOT NULL, + archetype TEXT, + attribute TEXT, + linkmarkers TEXT, + atk INTEGER, + def INTEGER, + level INTEGER, + linkval INTEGER, + scale INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE category ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + left INTEGER NOT NULL, + right INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +CREATE TABLE attachment ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + file_key VARCHAR(255) NOT NULL, + content_type VARCHAR(255) NOT NULL, + file_size INTEGER NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE (file_key) +); \ No newline at end of file diff --git a/doc/about.md b/doc/about.md new file mode 100644 index 0000000..8ff27ed --- /dev/null +++ b/doc/about.md @@ -0,0 +1,315 @@ +# What it is + +***Note:** Since the application is still in development, it is common +for components to be unavailable, unresponsive, or outright +unusable. If you're able to, please [submit a message](/contact) with +the text of the page with Copy/Paste (Ctrl+A, Ctrl+C, Ctrl+V) and any +additional information.* + +**cl-deck-builder2** is really three things: + +- [Inventory Management](#inventory) +- [Deck Builder](#builder) +- [Unifying User Interface](#ui) + +This application merges these things into a web interface where you +can create, modify, and update decks, pricing, and inventory data. + +It is a web app written in [Common Lisp](https://lisp-lang.org/learn/first-steps) +using [Caveman2](https://github.com/fukamachi/caveman). + +## Inventory Management + +The inventory manager is located here: [/cards](/cards). + +**cl-deck-builder2** aims to suppliment Crystal Commerce Product +Manager tools. + +The original intent was to allow CSV upload of Product Names from +Crystal Commerce. This proved to be unnecessary, as YGOProDeck has all +of the information in the `card_sets` arrays for each card. + +The Inventory editor has been superseded by the YGOProDeck editor. + +The CSV components and all of the Crystal Commerce code is in +maintenance mode, and due to be removed, as there is no way to access +or update this information currently. + +### How To Use It + +Currently the following features are supported: + +- Partial List of Inventory Items: [Inventory List](/cards) + +- Clicking on the card image will take you to the Inventory for that card + +- Each Card has a `YGO-SET` associated with it, and each `YGO-SET` has + one of five `VARIANT-CONDITION` associated with that. The two + together create a `YGO-SET-ITEM` which is where the inventory and + pricing information is contained. + +- The information displayed is: `Card Name - Set Code - Set Edition - Set Rarity - Set Price` + +- Clicking the Gear displays the `YGO-SET-ITEM` for this particular card. + +- Editing of individual inventory items: [Edit #1](/cards/1/view) + +- Extraction of additional data on import: exact card name, set + Code, Rarity, and Edition. + +- Import of YGOProDeck Extra data: passcodes and linked images based + on passcodes + +## Deck Builder + +The deck builder is located here: [/builder](/builder). + +The Deck Builder component currently supports searching by full card +name name, e.g. `Magician of Faith`. Any text matching this pattern +entered into the Deck List box will return a list of matching results. + +Currently, there are approximately 12000 entries in the database. This +list came from the YGOProDeck API. + +### How To Use It + +Simply paste a list of matching cards into the list. + +YGOProDeck Decks with extension `.ydk` can be uploaded with an +incomplete public interface which can be found at [/ydk/](/ydk). + +## User Interface + +The user interface is a [Caveman2](https://github.com/fukamachi/caveman) +web app with Common Lisp back-end. All of the things that can be done on +the web front-end have supporting code in the CL back-end. You may load +the source code into your editor and mess around with card information +yourself. + +### How To Use It + +You're using it right now! + +## Source Code + +The source code is available [here](http://[2601:198:100:1261:d250:99ff:fe2e:566a]/git/). + +# Completed Feature List + +## Categories + +We're using the [Nested Set Model](https://en.wikipedia.org/wiki/Nested_set_model#Example). + +You may create Parent Nodes (Left) and Child Nodes (Right). It needs a lot of work and is pretty SQL heavy. + +## Sort By Many Fields + +When searching for results, we try to support "Not Just Alphabetical," +e.g. more than alphabetical sort. You may sort by Qty, Category, Race, +Type, Card Text... + +Some of these aren't actually implemented because it's quite a bit of +work. But the majority of the sort patterns are there. + +## Deck Constraints + +The constraints placed on a deck during construction at the `/builder` +resource are kind-of arbitrary. Here is the breakdown. + +First of all, at the database level, there are no restrictions on a +deck. See [here](https://www.formatlibrary.com/decktypes/inzektor?format=ravine_ruler) +for an example. You will notice in the "Popular Extra Deck Cards" area +there are *20* cards. You may download the `.ydk` file from the +`Download` button at the top. If you import this deck into the app, it +will accept it as is. You will end up with an Extra Deck with 20 cards +in it. This is intended. + +You may also notice, that, by design, when you create a new deck, +there will be zero cards in it. This is allowed as well. + +However, if you continue to construct a deck, you will eventually be +constrained to 60 cards in your main deck, as well as 15 cards in +extra and side decks. There is no way to prevent this limiting from +occurring. + +Ideally, I would like to have a way to switch between "free builder" +and "constrained builder." A problem thus far has been "Where does the +constraint checking happen?" And the current solution lies in the way +the app is structured. + +So if you imagine each deck you construct as a template, then the deck +builder is really a template constructor. There is a queue of Saved +Deck Configurations, which can be categorized, with the +[Categories](#categories) feature above. + +- Main deck max cards 60 +- Side Deck max cards 15 +- Extra Deck max cards 15 + +As it stands, these constraints are hard coded. I'm not sure how I would +implement this, but if I were to, it would be something like, setting +a cookie and doing constraint checking in-line with some kind of CLOS +object. + +### Card Priority + +Cards will be added in this order: + +- Drag and drop: the card goes where you dropped it, provided the above numeric constraints are met. +- Right click: the card is added at the "end" of the deck list, in + this order: main deck, extra deck, side deck, trying to meet the + above numeric constraints. + +Following this, when you save a deck, a priority number is assigned to +the card as follows: + +- "normal" 0 +- "effect" 1 +- "ritual" 2 +- "fusion" 3 +- "link" 4 +- "skill" 5 +- "synchro" 6 +- "token" 7 +- "xyz" 8 +- "spell" 9 +- "trap" 10 +- "effect_pendulum" 11 +- "fusion_pendulum" 12 +- "normal_pendulum" 13 +- "ritual_pendulum" 14 +- "synchro_pendulum" 15 +- "xyz_pendulum" 16 +- Everything Else 17 + +Lower numbers get put "earlier" in the deck listing. The next time you +load the deck it will be sorted in this order. + +## User Profiles + +You may currently register, login, logout, view the user list. This +code has hardly been touched since it was implemented. If I spent more +time working on the user code, I could also implement the "free +builder" toggle system. + +The web framework system we're using gives us access to request and +session information, and we can store cookies. There is also a +database extension for managing all of this information persistently +in a database. Our current database model is very simple. There is a +user, with a password, and some metadata. + +We currently hash passwords, as well. So your passwords are stored +securely. You currently may not change your password. + +When you register an account we ask for an email. We currently do +nothing with this. An email server would take additional time to +configure. A rudimentary setup would most likely be caught in your +spam filters. This also highlights another issue with the app. We don't +currently have a domain name. But that is another topic. + +### Trade Between Profiles + +Theoretically this is no different than assigning a `category_id` to a +`ydk_deck`. Simply add a `user_id` field to indicate the user which +created the deck. However, this is not implemented, because I have not +spent the required time looking at user management, as stated above. + +## Charts / Metrics + +We have rudimentary charts and metrics provided by +[RRDtool](https://oss.oetiker.ch/rrdtool/). These charts are just host +information. We can feed it all kinds of data. It will take time to +research the data format it ingests and how to produce a chart. + +I'm not sure if Crystal Commerce offers pricing or inventory charts or metrics. + +## Picture of Finalized Deck + +We use [ImageMagick](https://www.imagemagick.org/) to generate deck +images and other various image manipulations. We have complete control +over the process. Currently output is very rudimentary. However I have +complete control over the pipeline, and at this stage, more time could +be spent developing this pipeline. It is currently sufficient for our +purposes. + +The output is currently four static images, the main deck, extra, and +side decks, then a concatenation of the three images. Additionally, we +output the intermediate concatenation images as well. + +## Different Card Games + +This is possible, all of the application is currently hard-coded to +use Yu-Gi-Oh!. It would take a quite a bit of time to re-factor all of +the routes to handle this scenario instead of possibly just running +multiple copies of the app, or having dedicated instances of the app +connected to dedicated database back-ends. + +Regardless, currently we only support Yu-Gi-Oh!. If more work were +done on the User component, I could make it configurable as a cookie. + +## Builder Controls + +Click on a card in the search results to see its information (card text, ATK, DEF, ...). + +### Advanced Search + +In the Advanced Search menu you may search by card type, e.g. `frame_type` => `trap` will show you all trap cards. + +## Decks Overview + +There is currently a rudimentary decks overview page. + +## Constructed Deck Workflow + +The workflow for constructing decks is as follows: + +1. Create deck of cards. Currently we select from all cards. Maybe we + should just select cards from inventory. The majority of this + functionality in place and isn't expected to change. A deck will + always just be a list of cards plus metadata. + +2. The deck of cards has metadata about author, name of deck, time of + creation. The main content of the deck is three lists, comprising + the main, extra, and side decks. This information is not expected + to change in the future. Decks created by a particular user for + example. The underlying metadata representation is what's being + worked on. Currently each item in the database has all information + duplicated from the rest of the database. Changing the price for + one card changes the price for only that one card matching that row + in the database. There will be additional tables to store pricing + information in the next step. + +3. On the Deck Overview page, selecting "Pull Deck" will decrease the + inventory of the lowest priced card in inventory by one for every + card in the deck. This is like "add to cart" in an online shopping + platform, with additional inventory keeping. This is analogous to + the deck construction step, except instead of selecting cards to be + put into a deck, that information is provided prior, and we use + that list of information to construct secondary lists. + +4. On the pulled deck page, for every card in the deck, you will be + able to select cards by edition, condition, and rarity of every + card in the inventory matching that card's passcode. I can + conceive a very simple concept where you attempt to check out, and + then are returned with error messages saying which items had + errors. This is the current approach. + +5. There will be an intermediate stage. Once there is enough inventory + and the deck is "pulled" it will enter an area where each + individual card will be selected. e.g. selecting the rarity or set + of a particular card in each deck. + +6. Once all "errors" are resolved (banlists, constraints, etc), the + "pulled" deck will allow you to "construct" it, which will finalize + the state of this pulled deck in the database, moving it to another + table, the list of decks for sale. + +7. The list of decks for sale is just pulled, constructed decks with + pricing information attached and whether or not it was sold and + what price it was sold at and when. + +## Formats + +It appears that I began working on format integration from +YGOProDeck. I have not found a way to integrate this information into +the builder yet. diff --git a/doc/flowcharts/card-doc.dot b/doc/flowcharts/card-doc.dot new file mode 100644 index 0000000..8f0d444 --- /dev/null +++ b/doc/flowcharts/card-doc.dot @@ -0,0 +1,40 @@ +digraph G { + fontname="Helvetica,Arial,sans-serif" + edge [fontname="Helvetica,Arial,sans-serif"] + node [fontname="Helvetica,Arial,sans-serif"]; + + subgraph cluster_0 { + style=filled; + color=lightgrey; + node [style=filled,color=white]; + "YGO-SET-PASSCODE" -> "YGO-SET-ID"; + label = "YGO-SET"; + } + + subgraph cluster_1 { + node [style=filled]; + label = "YGO-CARD"; + "YGO-CARD-PASSCODE"; + color=blue; + } + + start -> "Mystic Tomato" -> 83011278; + + 83011278 -> "YGO-CARD-PASSCODE"; + 83011278 -> "YGO-SET-PASSCODE"; + + "YGO-CARD-PASSCODE" -> "YGO-SET-PASSCODE"; + "YGO-SET-PASSCODE" -> "YGO-CARD-PASSCODE"; + + // "YGO-CARD-PASSCODE" -> end; + // "YGO-SET-PASSCODE" -> end; + + 83011277 -> "YGO-ALTERNATE-ID" -> "YGO-CARD-PASSCODE"; + "VARIANT-CONDITION" -> "YGO-SET-ITEM"; + "YGO-SET-ID" -> "YGO-SET-ITEM"; + + "YGO-SET-ITEM" -> end; + + start [shape=Mdiamond]; + end [shape=Msquare]; +} diff --git a/doc/flowcharts/card-doc.png b/doc/flowcharts/card-doc.png new file mode 100644 index 0000000..8032752 Binary files /dev/null and b/doc/flowcharts/card-doc.png differ diff --git a/doc/flowcharts/cluster.dot b/doc/flowcharts/cluster.dot new file mode 100644 index 0000000..b118b80 --- /dev/null +++ b/doc/flowcharts/cluster.dot @@ -0,0 +1,32 @@ +// https://graphviz.org/Gallery/directed/cluster.html + +digraph G { + fontname="Helvetica,Arial,sans-serif" + node [fontname="Helvetica,Arial,sans-serif"] + edge [fontname="Helvetica,Arial,sans-serif"] + + subgraph cluster_0 { + style=filled; + color=lightgrey; + node [style=filled,color=white]; + a0 -> a1 -> a2 -> a3; + label = "process #1"; + } + + subgraph cluster_1 { + node [style=filled]; + b0 -> b1 -> b2 -> b3; + label = "process #2"; + color=blue + } + start -> a0; + start -> b0; + a1 -> b3; + b2 -> a3; + a3 -> a0; + a3 -> end; + b3 -> end; + + start [shape=Mdiamond]; + end [shape=Msquare]; +} diff --git a/doc/flowcharts/constructed-deck-doc.dot b/doc/flowcharts/constructed-deck-doc.dot new file mode 100644 index 0000000..34ba35a --- /dev/null +++ b/doc/flowcharts/constructed-deck-doc.dot @@ -0,0 +1,71 @@ +digraph G { + fontname="Helvetica,Arial,sans-serif" + node [fontname="Helvetica,Arial,sans-serif"] + edge [fontname="Helvetica,Arial,sans-serif"] + + subgraph cluster_0 { + style=filled; + color=goldenrod3; + node [style=filled,color=white]; + ydk_main [label="MAIN-DECK"]; + ydk_side [label="SIDE-DECK"]; + ydk_extra [label="EXTRA-DECK"]; + label = "YDK (Dragon Turbo)"; + labeljust=r; + } + + subgraph cluster_1 { + style=filled; + color=palegreen; + node [style=filled,color=white]; + ydk_deck_item [label="..."]; + ydk_deck_item_1 [label="YDK-DECK-ITEM"]; + ydk_deck_item_2 [label="..."]; + ydk_deck_item -> ydk_deck_item_1 -> ydk_deck_item_2; + label="YDK-DECK"; + labeljust=l; + } + + subgraph cluster_2 { + style=filled; + color=lightcoral; + node [style=filled,color=white]; + constructed_deck_item [label="..."]; + constructed_deck_item_1 [label="CONSTRUCTED-DECK-ITEM"]; + constructed_deck_item_2 [label="..."]; + constructed_deck_item -> constructed_deck_item_1 -> constructed_deck_item_2; + ydk_deck_item -> constructed_deck_item; + label="CONSTRUCTED-DECK"; + labeljust=r; + } + + ygo_alternate_id [label="YGO-ALTERNATE-ID"]; + ygo_card_passcode [label="YGO-CARD-PASSCODE"]; + variant_condition [label="VARIANT-CONDITION"]; + ygo_set [label="YGO-SET"]; + ygo_set_item [label="YGO-SET-ITEM"]; + + ydk_main -> "Mystic Tomato" -> 83011278; + ydk_extra -> "Mystic Tomato"; + + 83011278 -> ygo_card_passcode; + + ygo_card_passcode -> ygo_set; + ygo_card_passcode -> ydk_deck_item; + ygo_card_passcode -> constructed_deck_item; + ygo_set -> ygo_set_item; + ygo_set_item -> constructed_deck_item [ltail=cluster_2]; + + ydk_side -> 83011277 -> ygo_alternate_id -> ygo_card_passcode; + variant_condition -> ygo_set_item; + + // "Item 1" -> "Item 3" [ltail=cluster_0 lhead=cluster_1]; + start -> "Dragon Turbo"; + "Dragon Turbo" -> ydk_main [lhead=cluster_0]; + // ydk_main -> ydk [ltail=cluster_0]; + + constructed_deck_item_2 -> end; + + start [shape=Mdiamond]; + end [shape=Msquare]; +} diff --git a/doc/flowcharts/constructed-deck-doc.png b/doc/flowcharts/constructed-deck-doc.png new file mode 100644 index 0000000..a13aad0 Binary files /dev/null and b/doc/flowcharts/constructed-deck-doc.png differ diff --git a/doc/label-maker.md b/doc/label-maker.md new file mode 100644 index 0000000..d403091 --- /dev/null +++ b/doc/label-maker.md @@ -0,0 +1,6 @@ +1. Download the [LabelMaker.csv](/public/LabelMaker.csv) template. +2. **Import** Character Set: UTF-8; **Separator Options** Separated By: Comma. +3. Edit the **Description**, **Price** and **Bar Code** columns. + The **Label No.** column does nothing and is ignored. +4. Be sure to save as a **CSV**! +5. Upload your CSV. It will be available as labels.pdf. diff --git a/doc/release-checklist.org b/doc/release-checklist.org new file mode 100644 index 0000000..9888010 --- /dev/null +++ b/doc/release-checklist.org @@ -0,0 +1,8 @@ +* Release Checklist + +- [ ] make docker-build +- [ ] make routes.org +- [ ] make declt +- [ ] make diagrams +- [ ] make statusreport +- [ ] make invoice diff --git a/doc/routes.org b/doc/routes.org new file mode 100644 index 0000000..8501b99 --- /dev/null +++ b/doc/routes.org @@ -0,0 +1,190 @@ +* Resources + +https://github.com/fukamachi/caveman/issues/112 + +* Static Pages + +* Index +#+BEGIN_SRC +(defroute index "/" () +#+END_SRC + +* About +#+BEGIN_SRC +(defroute "/about" () +#+END_SRC + +* News +#+BEGIN_SRC +(defroute "/news" () +#+END_SRC + +* TODO List +#+BEGIN_SRC +(defroute ("/todo" :method :GET) () +#+END_SRC + +* Markdown Viewer +#+BEGIN_SRC +(defroute ("/markdown" :method :GET) (&key _parsed) +#+END_SRC + +* Documentation +#+BEGIN_SRC +(defroute doc "/doc" (&key |org|) +#+END_SRC + +* Simple Forms + +* YDKs +#+BEGIN_SRC +(defroute ("/ydk" :method :GET) () +(defroute ("/ydk" :method :POST) (&key _parsed) +#+END_SRC + +* Label Maker +#+BEGIN_SRC +GET /label-maker +/label-maker/ +(defroute ("/label-maker" :method :POST) (&key _parsed) +(defroute ("/label-maker" :method :GET) () +(defroute ("/label-maker/instructions" :method :GET) () +#+END_SRC + +* QR Code +#+BEGIN_SRC +(defroute ("/qr" :method :GET) () +(defroute ("/qr" :method :POST) (&key _parsed) +#+END_SRC + +* Register + +* Contact +#+BEGIN_SRC +(defroute ("/contact" :method :GET) () +(defroute ("/contact/admin" :method :GET) () +(defroute ("/contact" :method :DELETE) () +(defroute ("/contact/:id/delete" :method :DELETE) (&key id) +(defroute ("/contact" :method :POST) (&key _parsed) +#+END_SRC + +* Attachments +#+BEGIN_SRC +(defroute ("/upload" :method :GET) () +(defroute ("/upload" :method :POST) (&key _parsed) +(defroute ("/upload/:id" :method :DELETE) (&key id) +#+END_SRC + + +* Cards +#+BEGIN_SRC +(defroute ("/cards" :method :GET) (&key _parsed) +(defroute ("/cards/search" :method :POST) (&key _parsed) +(defroute ("/cards/:id/view" :method :GET) (&key id) +(defroute ("/cards/ygo-set-items" :method :GET) (&key |set-id| |variant-id|) +(defroute ("/cards/:id/image" :method :GET) (&key id) +#+END_SRC + + +* Categories +#+BEGIN_SRC +(defroute ("/category/new-child" :method :POST) (&key _parsed) +(defroute ("/category/explain" :method :GET) () +(defroute ("/category" :method :GET) (&key _parsed) +(defroute ("/category/search" :method :POST) (&key _parsed) +(defroute ("/category/:id/view" :method :GET) (&key id) +(defroute ("/category/list" :method :GET) (&key _parsed) +(defroute ("/category/:id/delete" :method :DELETE) (&key id) +(defroute ("/category/:id/rename" :method :POST) (&key id _parsed) +(defroute ("/category/fake-data" :method :GET) () +(defroute ("/category/new-parent" :method :POST) (&key _parsed) +#+END_SRC + +* Constructed Decks +#+BEGIN_SRC +(defroute ("/construct" :method :GET) () +(defroute ("/construct/:id/view" :method :GET) (&key id) +(defroute ("/construct/:id/view" :method :POST) (&key id _parsed) +(defroute ("/construct/:id/select-sets" :method :GET) (&key id) +#+END_SRC + +* Deck Templates +* Decks +#+BEGIN_SRC +(defroute ("/decks/:id/view" :method :GET) (&key id) +(defroute ("/decks/:id/generate-cover-images" :method :GET) (&key id) +(defroute ("/decks/:id/delete-generated-images" :method :DELETE) (&key id) +(defroute ("/decks/:id/to-category" :method :POST) (&key id _parsed) +(defroute ("/decks/:id/rename" :method :POST) (&key id _parsed) +(defroute ("/decks/:id/pull" :method :GET) (&key id) +(defroute ("/decks/cards-in-decks" :method :GET) (&key _parsed) +(defroute ("/decks/:id/name" :method :GET) (&key id) +(defroute ("/decks/by-category/:id" :method :GET) (&key id _parsed) +(defroute ("/decks/:id/html-listing" :method :GET) (&key id _parsed) +(defroute ("/decks/:id/html-text-listing" :method :GET) (&key id _parsed) +(defroute ("/decks/:id/text-listing" :method :GET) (&key id _parsed) +(defroute ("/decks/:id/image-listing" :method :GET) (&key id _parsed) +(defroute ("/decks/:id/delete" :method :DELETE) (&key id) +(defroute ("/decks/search" :method :GET) (&key _parsed) +(defroute ("/decks/search" :method :POST) (&key _parsed) +(defroute ("/decks/deck-select" :method :GET) (&key |id|) +(defroute ("/decks/category-select" :method :GET) (&key |deck-id| |category-id|) +(defroute ("/decks/kde/:id" :method :GET) (&key id) +(defroute ("/decks" :method :GET) (&key _parsed) +#+END_SRC + +* Users +* Login +#+BEGIN_SRC +(defroute ("/user/login" :method :GET) () +(defroute ("/user/login" :method :POST) (&key _parsed) +(defroute "/user/logout" () +(defroute ("/user/register" :method :GET) () +(defroute ("/user/register" :method :POST) (&key _parsed) +(defroute ("/user" :method :DELETE) (&key _parsed) +(defroute ("/user/:id/roles" :method :GET) (&key id) +(defroute ("/user/admin" :method :GET) () +(defroute ("/user" :method :GET) () +#+END_SRC + +* Deck Builder +#+BEGIN_SRC +GET /builder +POST /builder name= race= archetype= attribute= passcode= +GET /builder/current-deck +POST /builder/current-deck action=load id=N +POST /builder/current-deck action=save +POST /builder/current-deck action=clear +POST /builder/current-deck action=rename name="New Name" + +POST /builder/current-deck action=add deck=main,side,extra passcode=N +POST /builder/current-deck action=remove deck=main,side,extra index=N + +(defroute ("/builder/delete" :method :DELETE) (&key _parsed) + +GET /builder/saved-categories id=N +GET /builder/saved-decks +#+END_SRC + +* Inventory +** TODO Multi Inventory +#+BEGIN_SRC +(defroute ("/inventory/new" :method :POST) (&key _parsed) +(defroute ("/inventory/import" :method :GET) () +(defroute ("/inventory/:id/edit" :method :GET) (&key id) +(defroute ("/inventory/:id/edit" :method :POST) (&key id _parsed) +(defroute ("/inventory/patch" :method :PATCH) (&key _parsed) +(defroute ("/inventory/:id/delete" :method :DELETE) (&key id) +(defroute ("/inventory/variants/:id" :method :GET) (&key id) +(defroute ("/inventory" :method :GET) (&key _parsed) +(defroute ("/inventory" :method :POST) (&key _parsed) +(defroute ("/inventory/search" :method :POST) (&key _parsed) +(defroute ("/inventory/new" :method :GET) () +#+END_SRC + +* Workbench +#+BEGIN_SRC +(defroute ("/chat" :method :GET) () +(defroute ("/html" :method :GET) (&key |path|) +(defroute ("/test" :method :GET) (&key _parsed) +#+END_SRC diff --git a/doc/store/LabelMaker.csv b/doc/store/LabelMaker.csv new file mode 100644 index 0000000..54477fb --- /dev/null +++ b/doc/store/LabelMaker.csv @@ -0,0 +1,33 @@ +Label No.,Description,Price,Bar Code +1,Nice – Great – Good,$69.99,99988877766 +2,Nice – Great – Good,$79.99,99988877767 +3,Nice – Great – Good,$89.99,99988877768 +4,Nice – Great – Good,$99.99,99988877769 +5,Nice – Great – Good,$109.99,99988877716 +6,Nice – Great – Good,$119.99,99988877726 +7,Nice – Great – Good,$129.99,99988877736 +8,Nice – Great – Good,$139.99,99988877746 +9,Nice – Great – Good,$149.99,99988877756 +10,Nice – Great – Good,$159.99,99988876766 +11,Nice – Great – Good,$169.99,99988876766 +12,Nice – Great – Good,$179.99,99988873766 +13,Nice – Great – Good,$189.99,99988872766 +14,Nice – Great – Good,$199.99,99988871766 +15,Nice – Great – Good,$209.99,99988873766 +16,Nice – Great – Good,$219.99,99988872766 +17,Nice – Great – Good,$229.99,99988871766 +18,Nice – Great – Good,$239.99,97988877766 +19,Nice – Great – Good,$249.99,99888877766 +20,Nice – Great – Good,$259.99,99938877766 +21,Nice – Great – Good,$269.99,99982877766 +22,Nice – Great – Good,$279.99,99988277766 +23,Nice – Great – Good,$289.99,99988827766 +24,Nice – Great – Good,$299.99,99988872766 +25,Nice – Great – Good,$309.99,99988877266 +26,Nice – Great – Good,$319.99,99988877726 +27,Nice – Great – Good,$329.99,99988877766 +28,Nice – Great – Good,$339.99,19988877766 +29,Nice – Great – Good,$349.99,91988877766 +30,Nice – Great – Good,$359.99,99188877766 +31,Nice – Great – Good,$369.99,99918877766 +32,Nice – Great – Good,$379.99,99981877766 diff --git a/doc/store/S-20247.pdf b/doc/store/S-20247.pdf new file mode 100644 index 0000000..a11528c --- /dev/null +++ b/doc/store/S-20247.pdf @@ -0,0 +1,227 @@ +%PDF-1.6 %âãÏÓ +12 0 obj <>/OCGs[19 0 R 20 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 18 0 obj <>stream + + + + + + + + + + + + Microsoft Word - S-20247.doc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 2 0 obj <>/Resources<>/ExtGState<>/Properties<>>>/Thumb 26 0 R/TrimBox[0.0 0.0 612.0 792.0]/Type/Page>> endobj 22 0 obj <>stream +H‰lÒ1Â0 …á=§x7ˆÝ6Á] ˆ©0ä ˆî/QÓ ¿Œù¥÷I–’ yk‚ó¥!åÖ­CÞî)ßöçë“ÔPk.«âýL=i¨ÍR©-Exk('æbs.6çhkXŒ¹Øœ‹Í9Ú敹؜‹Í9Úîœ 8p2àâÖ0)s±9›s´5èÄ\lÎÅæm÷c·N\úŸ®›ÿÇãwêñ;é+Àfm‰ endstream endobj 26 0 obj <>stream +8;Z]^Tb.8I&-G5pDif,Z2!PD,RG#Z;FZ65K2I:6)VgeTK=Pm4!@^5c3H=X9991(72 +And(?at+",=FD_`Z<3Nk+VlT7*]@PoXT8EBAhY/~> endstream endobj 27 0 obj [/Indexed/DeviceRGB 255 28 0 R] endobj 28 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 19 0 obj <> endobj 20 0 obj <> endobj 31 0 obj [/View/Design] endobj 32 0 obj <>>> endobj 29 0 obj [/View/Design] endobj 30 0 obj <>>> endobj 25 0 obj <> endobj 24 0 obj [/Separation/All/DeviceRGB<>] endobj 23 0 obj <> endobj 33 0 obj <> endobj 34 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 25.4.4 %%For: (Madeline Flaten) () %%Title: (S-20247.pdf) %%CreationDate: 3/25/22 4:55 PM %%Canvassize: 16383 %%BoundingBox: 17 34 596 757 %%HiResBoundingBox: 17.5 34.5 595.5 756.5 %%DocumentProcessColors: %AI5_FileFormat 14.0 %AI12_BuildNumber: 521 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 0 612 792 %AI3_TemplateBox: 305.5 396.5 305.5 396.5 %AI3_TileBox: 18 40 594 774 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 0 %AI24_LargeCanvasScale: 1 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 2 %AI17_Begin_Content_if_version_gt:24 4 %AI10_OpenToVie: -681 992 1.00000201502252 0 0 0 2121 1295 18 0 0 61 67 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: -681 992 1.00000201502252 2121 1295 18 0 0 61 67 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 73 %AI17_Begin_Content_if_version_gt:24 4 %AI17_Alternate_Content %AI17_End_Versioned_Content %%PageOrigin:0 0 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 35 0 obj <>stream +%AI24_ZStandard_Data(µ/ýXʪȒŽ)DDš€Í#íÖ!ô“jÉ¢Ö:†@à‹–„}’Jnô£ì 2—x¹øma°ø­_×s±ÌÅ-€<Ë/Ì.Ï3ÙÅÍ/ì‚Û@ñ7g,n,»¼ˆþ¯%ïƘcóN– ö)ƒ“ ŽìŽ†é`Ô/¿á̲Ápˆœ3³å!¶~ùMÉû¢»ÚÙŒÁÞP[—eƒëÞpÅmM¿*,~ø·Ô(G"ö) ‹Qïj-õËoÊK,àýòÅìž~½<_–aËÊ}?Ë9þ}M·!N®k(‡A]·$ýÂí‡eWI·³Zžiñ]FÆÕ5Yæ’c™‡[œ¯[X¦_.–ߺ…k³—澟ۙslšsßã¸bÿÛ™ŽÛ_s ÊMÒ4-ÿaŸëÜ—aÙÕrŽF/»Y¿ðÛjëüˆó¾§ýÿpœcP€ƒ’ r!àE×/¿™Éu-· h0ÌXí¾0E wFƒ½>æ]¿&‹¼ë…¹€Íß–ý6–c×Ë\2àAÿ6@€Ý!3¹¦k®¾žÐÖ­êáyÖÒÚ×µÖûëºýwá7æ‚еy¸Õ¾¿ ÀÛºuÛÚÏæµ£·bïʱ»r Ïp ŸwåΆcÑÖt– +¸¾vyØ‹hÌÇÁ²\ ¡Ÿã}s/T ‰4r _loz³åÞ¡à FK±9/®ÞZ(\³U2^¯Ö·p0Ü¢Ñ`È~ËÐÂ3ýªWǘ‘Û~Öv>¿¿yWv½1iä@ι¿•[õG¿•·%8Çò[¿´üù¸•·…äìCuüʸõ¡‘ë·´ÿ£~Ëoízܸò¶¶nU‡¯«²—½¦ßMå³¹>ÛSëÃmno¾®=Ü~Üxåhˆ§j¸Ö¾¼J@P¾f L=¾ ÜË«#·AÆÍ5…LÜÞ\n§8·9'€ã\·¹ ··&no­‰Û_ÜÜÎ0K¾âæ ÓÑ0{¹X8Ê]6ÊrarF/ÜÁXˆ›3ŽÒQ2%ï GÉ÷æᶇçÙ+·çgmgä@ ¦ó¼h¦’€ÎÅrŒ~Ûƒ‹ȱfÜh¨EõG +…)â@?·1çÆ5yžÉ©¯ßšˆûÞßW¬n?¦ÚÇ-ü©ã·Ýü6naàÝÐnìá +£[ï&æª[¢/ׯ»rÞÚågú{×›Û{a¸MëÖÕí'öÄX–G°,¸]ûîûºn]¾¦ˆ<ìzëß~fô Ë­ÙÑ,Ã>@¼Î¾üòêÇœÿ-lç·?„ů«ý#îãÀÚå ZÎõx¿\»|éÛäWŒ™kògãVƒ?«FgƒÅo¿ÿËÕ\«½&ÃòÿxÞF×±ŸÍäÔWÏÛDqoNºê<öuÝ–^ò†É ‡£;vwp¾bƒë ÷ý슱ñ {¿ÇÃÚG2ø·dº~åÆ«_·ñ÷5ŒrÆ‚ã‹çLGçï¼s»›½o“ï¾÷Þ[ä\w88ÞX(¸‡Kò(ö³gµ9WöG [X½ÞUû:–ÓUüÂ|eÅà܃þ~@»™Õ:ºË´ÕöÛoaKýÑ• {Wæ8¸‡‡[—ÈÃ~»Y•ƒÖoLú®…ö.KQýQËî!À¿|É`p¥ÃävÉ58üšâÂAr¦hnÒ×-<Ïü¨;\ç ZöÛÙ,0Ëï ó!‚os +ò¼ms¿uíEÖÎ0+õ6îáa-MZ‘ÔÛx7¹Ç÷­Y­ãÂo­²êƒr®ng_¶xøµ¬L êmúË°œÒ´wÉÿSÇ~Vsɪçm¦¸0œ)š“3Å’äL‘ö¡÷âyÃdp ‡É•$ÇàÜ#¹‹_7®ÉÏÛȹàÜÃà¥é6@ܦž·q>œi>J®÷Ñì#É=|ݪ±ü‚/weß]!ú¸‡‡ëwäyøq`ìë¶p>~aﶜ{|Ùây[)–¢Ù¹G,¿ô÷¥Uƒz޶Ųs`èûrÝþš®Á0†ã_®[ÿSÏÛ¸-’ +ß’]T!út^ÞF·XºÅŠ©·Qj®©ÒG1Uˆ©·•$‡>G@ñ{¼þÊd2™Â¿ú¯ímŮؓ†b»bÀ_ŠÑP&“€‰ Zž*üøkÏ'o›Oì|bKiWB­ÜƒZ¹·¥"íðÚä|bç{†r‰¿J¢‡2Ú‡2:”Ñ¡ŒöÛÛä{’H$Eð4”Ñ ŒFø¿ÅnpwZ§w§ƒ»Ó/ap÷$[g¸yÛFÎ>ì)2‰L"“Ⱥìt[µ~§wV­ßšYcz¤Izhfƒ=4iB‚›g`i:ÐcR£,hŽYËðÓ€¿vfÖiŽY µd-Ã=&>!3³Cm•hø$3Ê’ŒÑ©ÎÒ©•×ê7Ñ~c +ÀÆ«S„õ›¤˜ný&éÚ±gˆ¡åŠØ¢S@L_û ;}ÖôIŸ†Ái£á“ð*ÐL•Á¤îb¦J¿ÐgýubÏõ¶X4û°qÁ! y2“ú Y™ÓY—uœ²u†lM 9tº!묎¤Úo +-WBºÅž![Zò°ŽÓÛ,-ë8=ƒfýh¹pR‚'8)Á 5Ó õ6K‹à*jå Ù’„rl¬„Ú}9Z>'Nª©½lÈ2ѯ½ª r‰=ÃÕ ~|¨¢VnаœQiBžP«â”Xäª +©>6>µrrUÇ@¥ËßãEC±›b)¨ ¸,o¤Uë7‚° D[wðÖSS©¥:fO=Á‘ú¡¶Â8d-É;zž™ÙÞ&S Ÿ„¨­.Û3°.ûCb'«8¡JË•ÐÛ<ÕÇÆ +-WÂ>KP÷6™“ðÖó Ad{p·JÀ:΂ÂÅq~sô¶ÄƒðŽ‘ÁÝ)B‘†¿f9CÐìc¡(Á+‰ÒÛb¦Ç´ªh„E<«©dêÌHÁKÃáGŠ¥#»“Do;l,Zì"1Æç„E‰•`KÚ2 S'UŸ… +„Eg;,»KXµK˜%˜hç¥A WTé4肨tO¥·¹xe@œJx& ¬GB…Ȫýu€"¸·-É;8‘׆ ƒÈÿ5“d܇¸Á>(gó $áô8ýÉ¡>ÌšgA ¤&º4¤&Ä‚X¢ÄÝÉ‘Qt©ì »BÝ¡ÌïŽÿ xX}³¡ÄPŒ‚½-Ý!—à ˆÒ1ÐHÄÌÄùik$á"æ˜g8GÀù4pB‰r¶„ ê¦G™º{G·d'§Ðý•™††n%φäAóp™<;¼,Nz‰Þ*Jåy¢®‚™ÊOåùd©ÀPcµ(| +4T-…•h(ƒTšO™ž¨¿GÖ˜Èá(‰v¬æ %gÃàŽãb¹›]bW£ÐF.\(›‹ndÝßð9CX–œ¡ã"_GïfŸpýßÛºŒÁ݈ˆj*@‘¥ÆvÕiïâºo6¤ eFƒKC¡á“ÐÛ£$¬Y î>1>èeœJÆ@F ,Î 7³íô60…|Œ@§º +ò‘…Kˆ5Q}^°0-eÇR5¯f¦ém•½I}¹4¸ä0yúظ9k¨/@CÖŠà¬Ñ9ÐÙƨ_Ø=Ze¸!Ò±QçeÙªS)\ ,(šŠe—*Ë€ãqABXþOoJ1t^* ÞÀ3‰V:Þ&ŽªßêÆ‚¾¸T`©Ä&f*Qœõ¶OTŽÛó¤þ ùŸÞ¨'ƒ»aÖ<Ã"@² zk )6`4êÇ!‰L;3æw{[áäøú(¶4èÔ¯*¡v™gHûo˜éaý’(´Ž`ÕÓ™âÂbmñZ*sÆ…á Žô“Ù£k 8tªC Hém‹M§ËB‘†é‡uÙ‡Ãç û);´\ Gî7Ÿ†Ž±$ØɧØYdgØy›¨ –è(+JÑ!:°óˆ®°ãXz¢«éþ?½©R4 Ä~ÙO >–¤Ì¤ï@ðEg¶¤<¼g±`Ï((> RÔa4ª· l8áZ«|¯‹3l°@’.½Yr†cCŠ'w•èôåëD–žèÜU¢óDWØ] +b‰Ž6š]l’„ÝÛÆ×ͬ!½Mzhf è1 ;4!ëÏc’ÉÌ2ŽémH™™ ²×*ãmµúMaê4:Õ1˜…¹2U.°•©Ò/nú¼#7}ºIí¬igͲ,ëm伪~âDò ÊÍ!‡ÏzÛEðÚløÌ ŸYÆ“­j¿“xžàqk +ƒ‚“|ðWÆ2HtÒ‚€êý›m[¥Qæ]R–½a–}:•3B€‚ˆ™$†( Æ¥Až>Ën³c*Ï¢bÙ*Ë€ö6Y¨Ðúbæ,{ü4¾."“%'Cf·¹Ò(ºôã®Ý!º‘HUJt!5ÑqˆA`t²6` ÉZEb9‡½„$š~¯Ív*2ÊeC茴o\xí4ºøx0€+˸ObúS1MO!„"ݵ•I!@C ˜Zƒ–§ƒÕ å逿¶€ d]ætTQ«¨Hi‡9ŸJ¦íðÚÚáµûAŠBFûMÊ%²ÄÊ%’IDÎ>öÔx¤ ÙšYãmi¢™¤ y=&¤f6BÍ®G23Ë”4G¡–á™YFÃ'aF|H Ÿd23±öìbè³î—~Öý£ÏI}†£ÄòÑv±¡ÅBËKÈZNΉӜ8«jU¨¨•Ï‰Ó¹§sU­?q:Ã8q:»¬ÊêEðJ¨½>?UÓCy’Œ} X»ï”¬Díº…´®%M§A{[è´(°zÅR;¥GXx›ØZ)°„ &3½íº›°‹ÀÄæàm¤KÎ(oK!FƒØVíƒ'‚ôq^ŸS‰@‡&ÆÀºìÁyè®eú°áD]„,Y2…U”ñU 3VR.SG’•Dõ×M¥)¢’e@e¼^Œ‹©‚HV/3¦ƒ+ +娈U©zj‰Ž>T°?,h3e ‡%ÝbÉQL_›n±µ©ÒÓt 3Uú‰n±´ÊT銓©Ò¦Å÷ø«¿ú+þ=¼ÍÛH’† +‰Ê±d(•L‡×>>‡×¦^óiNQ+âD­ÁS’©Ëh¿¹Œö[FË¥Šà)h¢~ðF#ÓÈ‚ 벉ñ¬ËN99û°³{ŠL!Ó#MHÍFj摬exf¶Ñµ Ÿdf_ù(™Yæ{ÈÌ ‘©%µúMjÕ°~LSÀ:ÎF§úW¿I¹2UúÊTò~_`†I}†“úû8GâmX‚ì¼$9Œ%EIrèÙvvà ˪$‡^7dgèm¬Ë&>’Cç Zn¬-¯õm3h¹Ü[«*\U«:'N«Š€B­|U±Žs]Usâ$‹ÝÛ8’C'>j§>gÿZ’w” Ÿ3)pͲA•.òÿ£‹$Ð +BÆ…Kƒ%è¬&P§áݘßF#ÙÒg¨Ú‚F ,B3H$ùÙÄL%>èïP°šu*ÿ÷¶–óz1Ç<·ÕI\™*l=hbºÑ@ +$Ýb›Å³zžçmÖÎäÍù/×´ÿ0ÎÃÚÙ×-ç´4lû~Û?Ú}õý€Î}?ÀÁà Î7}AvÞ9Ir n:Ìm–Ü,›ì‹¾à÷Î;ç|ïÍ;ï\ƒï½¤ƒÜ›äÜt˜Û,¹Y.6Ù}Ñ(¥ƒt\ƒ3L†Ù(%×à¦ÃÜfÉÍr±I˜[,‹Ãd˜\ƒo³4¦é0·Y.6ÉÍ’›åb“}±alr›õ0qàÇ96ÐFo[ n,LƆ\ðmÆ™‹×à4Ê•móèÞ˜¸½¡ßVÞ©?ºÚ+îÁ©Ò†cëåöo8ƒ}«Ý|[kÃmn›ÜæL³sÎ9ƒÛÜÞÜx]q¾ð6oKyÂÛL(‚  •ÁÓw8¼ípð6oó¶Âh&º‹ì »Ï£vÞ–9ŒŠî耂ú x:,–§ú¡Œ$ªè ²sÐ*¾Î$ÀItÞf*ˆ%ºš%…%/:€YÈï„9(ý¶²žDÖ>[>–W<‰Î8{R–§è0ÓR‚w^ì5ÇôY)ÅÑ9ÄvÆAãâkÊ1)çLqáˆR­£ "ÔùR߈PÏ-^«Á8#H ™KˆÂA½bzh6m6à‘¥vH,âý·ÈnL‚l nä ³±@€ÌÐäÂ;ær‹âR€aÇ=X Z¦,ˆè,h–kÌ3œhZæÞ›sÔ˜g(rRBJNq’ÜOÓ)MÈÌ +)á#ñŠµébúÚ©Žrè9¤^¼˜gú¸Ô+ó ů÷W~üþ'óœEˆ“dY º6x¯ü@ìš)ó Û¤AKÓ$BÑÞK…/³ «Ç-fP+QÁG‚:²™XÇy((׈…tˆaC¤UÅÀÅBš­&÷„4Pé­V6no“¤…–+PÔÊ Á9;h¹H’)¬Ë6a­qc"µT¿•žI=QJ¨¨•'¬ËC­qÏDƒ»ÛM‹EŽ^“ T!*Ý£#tÖ0b©·± š>$¤Å&¡SJpp„\ñX±ôª¼è9AxŠà!  &¤é3nÚ¥S·èTߡϸi“äÐÅÍgÜ5æ“0QÓ£n½­ž9 +dh¢¾=솷JaaU!…¬·í EUK‰–s39ôåD<ÎÇ÷¹ÛÛR_y^LQL_‰!.æ"d´ÿ.æábžŠ•©ÒO’CGÍ +­ªA©›.^Äôµ 'Ñ]DWØ (ŠnÀ‘Qt±ÄÅ<ÃÑx@!Tt¡ÔC‡Q +<¥ÀxªŒ,ŒOeK¤^ö÷‰Ÿ‡ˆeÉwÑê÷yÛÇ ÇímËÛ¼ípH`ýpâµ.ó0ÈN!’ +bê¢+ŸÎðIG?<“öû@2aòÔ !JD«‘±du¤th  Ë®«±å å¨P¡õœ*åÎ4ðžq{[Jþ˜§%D­¼UcÏ5[tª‡‚Ö‚2Ó¸7LÅòÏ{ÆͨÌÀ~ÑR6 '=;IºL0|Z5Å©-h„²5JûE@ù?õ.osW‘$ìæ¦Ó%ˆ³Nî7Ÿo[-Þ&°x#¡Å4Ù + {Óï°8Co[Þ¦* P"4Y<6.keÆLzi4ŸbÄÒÚøGt…ÇÄIt"Aá;ÉêRab׌*&…fR:£·}Ňñ¯Ü%ÊLë>‡j¿G·y[ò¶‚cCŠ“-ºÂŠÝ ¿`§R4]¢‚Iw•èVU)Ñ5^,°ó(Š2…]Gt…]ª –èß@ØeÜU¢³”pØQ¾°ëFE×Cj¢ã¼FŠgî^´´9£Rh”N>a +»>” +qÆ^¦B¨†¢~(Eêm¾ämˆ•«{a@ó,Xk7ªWŒDS¢ø’·Å*½½m‹¢+ì4²3ì6›™–è"„bõ(½hÉõ¢¤·y\)…%Û’¢!Y¡qÃc¼ŽBh,c;Zb±Pr»6´’y†ófV }“ÿC’Œ·UÔB‰t^Ìíó˜üïm Cx›ˆ×L¶×ºå—µ[;¨óÝ´¢\”HX„óÇãKðÓ +9öHð45e1$Ë@(P´õA)Æ/P¡±7\iÂøaø©ÒûÊ2no£‘ê•IXVE© n(b…·f¢ó¶4%{±;°Š‡¶]7¡ ëj"TöB¢ŠŠ²JšÎÚÛb3=R6yAQ¦Ìžñf¢z¨þšp5V‡·‰ÔL*ýyA5“z¤lDuö55R+"‘¡ÃªT"‹ JY,.(6b‰6hÈS ÓcŠªxCc—˜?”ÊÚ1ÞFÔb(®QÈÑó¥q¶6œ‰·y[g¨áÀ±!EÐc2C/¥x¨û«V8¬Zèw†QÑ¥Qøû¹¬ ï“U^¿>(¡ÍƒàmáÊ2Ë°g¸"V¤Sí>Jz[+–¼ÒÏ +‰ê*†‚P+Ww ¼ešª%Eùx*±Jo…‰"xÌQôR„ZùW.V»¶Ô”|fÃKM<.µ$ÿ«±çnõêè z©…R¡›̃.‰•åô/2…f:”(ß`ySJ=šÞ–½Ø(Z&ÊૉÐÌ‚£~z +Joc´˜"[YíJO’q‡’߆’Ì4î«Kt‰ÿÁîbâ$:—DÝI¢ +ÃvÞ:ŒŠ£*%ºG'v,=ÑUDWØQDWØ9:`·¨{b6Cá(„Ò„fÈ®Ç$UhLg +‡˜øk[d¥ ïLJ +IèmõêñÕ!ë²Öew’·¡0H ¬ËÞvÜ[ŽdÔ‘R¸ðŽy†f cyÇ,PFB., (#qX c9£PŒ¢0ƒ*hÈ‚üÝØkíõ©B[®qár…èš).I’ÓU]öCÛkqÒd¢R‘$ !Çxãb˜%™Çˆ5S ‹I¤ž:9Í\öó“h°3£³žBöL8üráÌ]b±²QN¡XÜ”lsU1à„Thb%]s‘ÃrÌFÈ,k™ˆüJ‡ töôLIÿ¼t˜ðüq¼†©ß>èŒþŽ[ùúýMPL.¿]ø¸üOþå«…g$„O%ÓåÈŽÎ3ÚŒ^œL§“7ån“vÉðžä¾ëã•ó¥E«Ž¡ €BOwM‹– `¶Bæ^ãÈFä‡P¡ÓÓgJêüs§ÅŒgÀñ2L5h³\ìòÐ|òÐŒ¹è‹àùdT%¢Š8© ®óŽàÇ«/|¿ò½èÇÙ€gqâpÁÉ,äµÛÌ7ñºìòb@ȉ„BûÈ~°%¶*¼…—ãÉxãÂgãÄSÅSwïÜ’céø¢® ®=fŒîÍ6)ZpxïZì@4Ò¼î§6»ó³{RK»ðŸ0™Ùõ@æweXvý,Σ>ÛÍð&©ûÖiK‰Kª U £ÝŽruQ{Ð.!íaN»0ÚšÒ‰@ä"ã–6ue(™4nkV@ 5¦q«sÔ !P³2îÕãæÞ0ƒþ"ðkÅßá9-~1="ÙÜ¢áLdbíŠ)Käàbºc²MFñ‚íF2c²}ÀóH1I‘ðÙÓÏ lWV®² xMÜ­|© l§¶ê‡&Ü&½Åˆ CL•ó&‰rÂöSnŠ³þ•“ÃÒ9ÖÂÄ„bx§ ¦²Ž3‘ëE¶u‡ Î(w?+\}yßoWIŒ‡‘ [we×I»CŽ0ÐÅ™“`Ûh¥È6Ææùë‡í©Êv;‘N>úETÙ&t>J©ª`;…M,0—@ªMHàÇiØž^¶E^Fäƒ(ÓÊô­åŽlËûËû÷ýð-²-7¢²ÉdRÄN7PŽW;"•¸Ã'lñgŒ»¶u‰hj£ÁRI¨¬ß‡Ë=  Ï18@²)¨ü²QÄÞXÜ£Ê($€:§ +’(R±äu_ëÛ¬&’l{˹ù Ûêd;äâåLE§‡zædûÕÊ(¼ÊÐtdÛ¡¨Ú¨Ø ªK÷_¨°¥²]ÅŠt Ûã@¶u”HÔ:?²]1l¾õò"Û¸ €YçQ½kËo¦'8ìûâ߸Ý ¼v{fpÉàÈò7§¿ñ:ÁñêC/ô›£'ª™“ Wñ¸iÙˆÁÙt';]v¢cØãÁ€‚XXúæ³Âþ?§@Î9AÜø¶/ZY"v?F:‚Ü5àä"Ù@w´º°<‚È.´"Š‘a«7¯>hÈâ ï²Éç7îbLíõs+Ø¿;v: ½¼ÝA•äž‡gÜRm»ƒE='‰Ìv}0<æ;|‚¸>ßR xœô'?¼Þÿž,ŠÀù`ÙX¾S™¥{†EÚÎt œF‚d/èé‹6ú×CÇš®/Ú.—1¶ºö¼vàB ÚËåÅPµ×”GÈIc€× +RH¤“Æ-‚ŸK÷á ‡Ó"DN¿{‰â62ýÊ)ËœZl“…Rþ7ÓFçÆb-*%Ux(—Xdu+^Œ°Áº…NM~ëRÏq…i,é”Ok–Ô!TW]¯1ÿUqTËÔΔù–+‰¶ˆgoHÁš4Î5#ÙO%‹r»J@|Ô¼ ¹ækãÎWv›ÔW>vX¨^ÕjæôÛ˜Ö„¼|BCÓf·þIíoF³“!-=|æ´~PRçÖ ïň_ýMË¡›õ°¯OšOúw“œg›øÕ5gîþ¯„y(‹~ˆka…‹x§|!=¿8Äl–"8'žqf¶³%9óÌ¢ÃJveœÑÅõ‡°~=xü‚\c s +êàÕ]Dœ ý—UŒ„ ÌÌa}˜Òƒ#žüÞ 2¹Ê÷¨Âi»ô­Κ_¦Ðz"رï9KÀí’«áõcŽj•[pŽ.Gˆk.Ôß<¬ÿMŠÔ/³æÕÒ•ü»› + 7/KSŸ¤xªUù'“)j¼8¤„ŒÊ³7´ox&¡(ð?ïaÌjb^ÿá¥zNm¨¸K\RùÀÝûCÄa6¸êg–J„ŠÜ RÓ™¤ÔÄ!¹5:~º‰öšÄÔN§5¼ {ûåypšÉ1ÝðgUÁGpÍ)h*¯‰M\2Ë<¤R±©ìJ÷gºu_Á±Ü›`¨A³bÚ™˜H§Ò&IýÁ¦"dVêVí°P¦\CNÅ‚²²ËÉ|ˆ3\·éaˆ/y81Ï™¤Ê…‡‘L:À¹IHÂØ%1±pªó›$zj 6¸Z¨…´º4•_v§9v §RˆÃÄ4*Ú±iÒ¼ì$ýoq?²#â¹f”0ÒÑ2hlšð$+;‚"™›­rÀS»T%(çG*\ÀÌé t›lwÉ.þ,AÀÅ%‘€ÝŒ€kÕDØ®Ÿ™s"%ÂÆÏê³WÙa¡uOÚ`'\x JhΪުŠ,%—‘³jZ*çľvK/ZКõ5á3AP:‡ý :tŸwÑ¥ VÑíFǧdÿÑ%á¤Z´¡y¾i+¡.êl™àUAÊHŒ!Õûqé} ‹ŽµÖ.^UâM‰ÿüÛ©–äÑO¶rãB{–¥û¡&µ#EYO‘ÀlØa,|´á¾¯:¯]Q QYÈòrQcTqHV'¥\è“™ikIqTTþˆAq@·™_eÄ76e¡qëˆ&W9ü¨ ucYk·ZûÌÔŒçtë[´ÕCHk’¿N®óä"/7ܳ0Žï˜Zڳ骉åÊÒyOŽbJ£5“CŒ~%¤0[œÞߺ¬¥‡$óÌé°œ„¥™Æ‘âï¿ÏZõöÔÒȘ崣¹DuþÞsM×¼nLX²aCVˆ˜—ïX~ïÀCFÎTŸxþŸÑ+-o{ DžZ/ú–*†æ¨X切GtK?3¶’âhŒ¹óÛdqž"ú:]S‚ ÊÅ“´#¾š ’•†ëÿ¶8Ö8ƒÁŽR,¤e¬x_· å¥Ü¶Ð§(ÃE\Æ4˜ Ü‘v´$Žýï/[lÛ–·ÿO> +MŽ’ôZàºýWœ:'×CG"!E1C¸[Í™¡kÞ'®©ÜbZ’3ŽfÎN؃ËÙŠ>õ¡oÂm{Ô#fŽø‹¥1;G z#6òßðaúVßÐodq•dÈÓVõž«ˆ«qE½¿~‘— ©Ì%+¥¹+Ùsõ}…0Pî뱋W¥ÂÛ +\Þ.þO{úu÷­/pü +Õj¢|XÓd0…àcF¿JâჼFo~_\ºªß›ÁýGË{å@Pr¨Ò@#ìZíÇÃOAúYN½ç@ºŒ;Å\ø ƒÀvÚð@•Y`õlÎËu‡îv]V#å+d¿ö +ÕŸzI¬œ¨8 Ò%µî2öÚÐ}Q³1C^Cí"š]¿‡ò:º…õ¥ê™I“on-·+:3ἹÕÿœÍ¿6tŸ¼º¸Ýf¶Ær8ñfПgÎ[¾/¬€ÍÜõ4×F1;;,Ø¿ù* ^_#ÌìÚ@t°$\—¾3R9}HÐÛò|‹þ)ÆÁ:™çÊûå¡'Ê +°¥, +“‘Í»ø'Ÿ9OT} ƒZ©Nç­]ûøL­ÅÑOu$s™¾Îßð, „\p¿­…Œ_8Ý€ˆn>‹¿ÈõÊž”OÁ~&<ÈAlä¿ÉÙî ][ˆ„/Žzç~k»ÞgIn?Ø=6é©dý²Û"Ø÷^âP¥k¶ø I¶pø3[Ïí¿Âó ØŽ~VöÇÿL•}©môoCìiw+µHs¡8;7Z°X÷F寇2ÉÝ^FrÝï™ÈÖ 7­|ÊK¢üwè‹ý?þôP©Å1úóLbå/½_=˜úL•c› +¥¤¸ž°‰6"ƒŒZäzŸn%€(¾H÷˜MŸ‘q`õ>v$`ûêJ“«­—\+‚:§éönu§nÈ¢Ú¯ +jc šÛö—»b9ïñƒ`TÀ ‡¹³)˜VÎ]ñ¬>ì3í·‹z´p7iJ°2êˆæ5ÄEidÎÍJ¹ +à¨jçÅ«Frü“â%ã‚ÜÉ®G\÷ôÅçë5ÜjæÓN>ì'ÇÐÏ´Õ›§ÝdmÆzÍV~›Çq ú“o!æ§Ø†­â•w¾_|]ÍA±‚Š€÷Wo®²IáÅÒ(¥f§.Â7§Q`L0nâ3y›) èj˯÷üA&/Ççù_íǶÁ­õ;zo5-8ÿ—'þõmÓ¿&ø9ß³^V£ú 3†ò}C8†Ì®'Ã]øUí¡H1ÇmæºÕYÝk7½ò\®U„Mó¼?Y„VÕ•V|2û‡à%¦^îìÙQ¼$€/âe[îó.Ū-ÐAlAoÆû,ãø#° ÛM‚¼ŽðÊÌÔþ1ñ»¨"š°i,çÄT2Šm€)ÿ…y1åèãˆVõE ÚÞôì›~[<–0lÉ&˜ïI÷®VHqá®I7½+èuZ +ÍÔO¾B¢Ÿê>ºØPåsy!_gÅSÕ.ÒÄÎì]Dñ´t‹GS‘_¥-/±# “Z‚5¶ÙxÖ„‰xªQVFÎ{?ÅÓ部)ÝÒÉÖ«OŽàs‚w—Õ‰xª>NüÅ’å”ä¬c¿0ÂD<].Å“£&µÄ‡ÀáÐC\ËXi¯ŠMƒç ‚P}Y;N*­¹V«ýí’Þ Nlˆ<‘b{ýxŠ‘šªñdF±ó5(}§ñùÖ7ñeÒ|þTRÿxºÌŠW(òfk¹LŒÛr—µìUÙ–Ñá¡??‡ùÆS‰@Šueìlã©WRx«OØ×+sã‰L⦪ǓKú”Î×A6žv–úš‡X㉢~ËsO?:”K{¬•øí3àÂÝ{ͼ~NRnrq¿³1Ðd5×l/Ø6`¹Û«ù>a +•êM!e/¬w±ùJô™ÁýžY˜c †ŒTKx¼Cá .=ã“#±Wwß g[a¼çoOîõÀ˜÷@ûÃ÷ð+Î4U SË›Wwcxh=fÁÌ1¿¹+Dð@µNöÊ­Ûã~ºL—]º!bŒJÿ&ë­y‡¿`Œ%¯°. ®bê’ÞkzM†¶Iä«U—./Ì[Â7@8oð%Þ.‚Ùæ±Lc!g*T»±ø|a x£·^ÛŽ†•'n™Õ5ï {˜s ™WÁ­ ¥ ®¡Æä ÊeDƬcÐE;-Ì*¯ÔUè¿>¾ÞOî¥é ¹ä†#hô$ pÎØ:¼n4ˆé]£É¿ó9®ŸÁw!Q!ðœˆ1ËÚ=†f Ž7m,œÙŠWž—`¼HNŽÀÃ3n^Œõt}tÈsT4²p±g(¥;)0¡JrÞH{ÉS,éÛÒE˜°ˆœõV|x7çzJ@ÂÀíߘB@«‹ÀJÀôKlQ€ÊÿÊΉ!«S Ê—¬BìÀ…U­0/¤|òÅ4ukËäfzÉàÎÓ¶N±­µÔ·µé..#¡昧öi'ëÛ¥iyµ‡¥JNRf’7(mÑó'£ ±ÂØFãªãÖ 'y½—¬´(µ>®í/H àíÙ¿ÂR‘{gÄ÷¤ðÕ¼4nÊG:Éi˜\+]Úp-Øø+×ùƒäöíò×pkg@,ÌGEp=¬@F™ì8ÂáÉIfK-Q-ZéX|+š¥œšå‡F„LçÌÃ86Ë[,ˇhÚ«&yeƒuêc¦Â!jíàk…Ûÿö×ÄICG~K)žHm â¤ \ÃïŒXf+š_ÿ¼±´R;B D‡§ÊwÿðoQTNJ| Í Q¸†ªöÿè_j_°,yBþ??|››*¨‘Õƒ-¯T}`bnº?Ë[Iªéu”]3KÚæ¦_ˆ )ë~‘±|’ç@úçiñPoLÈõ¯0×<5 ìCbôïÓ ™›Žø™Å!æ˜è_œç‹p¿ٙ›¨ŒÊqcyïã_åX’5-ƒ‘b>Ò¤?DÿVÅôSék¡Íùdß«ÙÄ‹Ö#þýD©!ÝχÌëß*í¯Ir³ãNlF+âß÷ìƒ+rêß §¬/àßÂõ[:ƶö¨Ë ’ø׬õoT¹‚]˜Ö´P–)tn¿z™þB¶«¸¨a1\©YN—`)Ä•Ùõ}¡EÕTìuv÷¡ˆaµôÄPFÕˆu¡`ü&TÚegÛ²—®›© h¯ôl™ÜÚ€'€ É ¯èò» qŽ­PC,‡Y6ÜHÏÄ`SZ +¥¥° 0”ŒËÚq÷0錯;ÓHs›8˜³Ñšüä‡YXø.Q3¨Ü]̯°)•ù)ƒ$áÚ?Çî¬ÃÒx –,c`³•ñQMIÙšt$í ñVJÑÑ$ÑÈò^ñËÐvìU¡èWUûÇz'|f?ûZmZØÀ&INù>‹(CÝoYmEP6þ,#´.Ÿ|]\Ú3US¯]\©š +¶z{i‚#>Ur"à™a¶¨ÐNN“¥Eð­¾¼q…†oµˆ/šnú¤¢ïïHkªcÀA'BˆEIŸŸt*8ˆ%²Â_”F™Jh™ÿ×O Áà}O +辩JmFÇ¥9žûµ“ÓE@EèÓÓ„µÃä@<å! Åñì‰J½Íòp¾[BõCAqÀXczסÞƆd´¶ÿi¡^¦/ž€è¹ ²%œÏ<¬9C½²Ê¾·ûg(¨×ˆ• ™ ê¾ùêMëÔZ\盽¢ÔÛžl^7Ôë¯kI…ñÝEJd–ùP\ªè"ö w‹$ÆÎ#b1F†Òëeæà·°E¢æŠò½n*ízm#µ'd‘ÁP­Â;ÑbhsɈš²e°v^x7,×ò‰Û€ÎZT°h ÌÂí>S)ÌÕÒY¢ G–Ù­™(µE“É*GUØ“íÈôàÂ(±Y]+¿ì‡5ßÞ@[ âî ¹sH1 *íÚ¦-œ"?B¶zˆôÃ5 -¼`:‚j¸À$n™‚<&rÔJß÷«Í3¹ëó cÂA¬Þ:+&çˆI:ÉÞBß2€¿ñ)…EqÐdq@)^üàýZx×a+xÝü+ï(Ñ7't¸à%m Pš QÒÒ*bRÏØ@bZ²Xºê[±d‰lƹŽÚÛŒTv¬_#‚ArÏmó›JH§?_ æªáKž­Ó†ß¸fˆ&T&£ƒaô˜‚Lj›Þð¯ØªNúêåîkU¯ƒ¨\½B6uhýýº½4 W¯Ûð At| ª^‹q£ÂÂp÷×)¬z¹ O¡&cJÕë +-¬¹¤í«7 * ê8¡ñ H%®-¬…j˱›ˆÚÛêZ’ç¢Whþê dIU¯Gñ E¿0¨¶ÆÈ£ù‰¼zs~Ç=f»ê= ±ùÃÜЪר}‘g=Åm>9Dь̀ KTHúÍ_¹ +4f•MO€ÐÚ¾ò¶ä¶*Þèð@óå³XÔ)€µ^Æ‘¯oï3ø‚xö¹ßŽ|vÌF§µ·N‚+úv¼o]{ûhíÿvƒRÇ—uTècÝ!$Cz@à†€OÍ-\ÌîªÜ%›ôšŒ&dÒñÇÄm¦·Dïc.FÉ@Æ>6”þž^]Æ®"mw¿hõm(R†CÁêeÛ¦2R9ýîMwêeX˜ŒÄ~JEðýÙ †žŠÖéŸ2œ?_z¹áËW½&\½»Ž•kË‹#^¡Ç7‹ø¬w—9ÿËE +¹»8#ëÐ5%ä¡Šjñ¥¥ô±i¾»ñîøëÇ|3ïvØ‘Áýî&‹)%oöäÝõ "Î+¢²à Á7KÄŸ$ø¥U×ï®Á÷À1ÁöîöÐcâáIõUbà(Y³AUfÂ])ÞóäMºLËÎDHð‘Ä`|wA"ªßN•;‚Pº«Žh@ð³€û¹…CuÓ YƵé0ßщ%è2Œ‘~%®?ݨi™æÆË›%¡µ½W‚ªï…;É1Ö +9²î˜›iM¬IV´.¡yǶ²ÂX|,OˆÂ-J˜üx¦ ÌÇÄ8³ùY3Ë(g͵Èã{êò|hÔÊÝ/ƒ:¯¤«@ÏF¹;Ÿ¢CËç®›~<íÏAåÃ"¤Íóïpk”99šˆ;baŒŠ­§ý2#ÀÜãež1¾Ø¦þ1\"B,bøéuÝk஫(¦×5}?R;[4ž ÛÉŒ5js_µ"°Å?–;—¯P-;s oµ3”oþ´3GÑõ·+;q¿Sl}¤zMò×Öòª|!Gƒq¸7YÖ·,8O}O“‚´Ûc,´0û¦fÞ¿cç/¢°™ž)í#AMfçÙÏa\\Æïß7~G´Ã_\ùaÁLœ…FôÛõ»ùšIÕºD/ž‚š¹ô¼>2.Ï”=k3—%¿–ù<…*p9ÐÔ dñ!°‹P9 õúƒˆ=Þðæ‡R({ìvK«#[b®lìÀW>E(¶=Î +ÂõÜA-ˈ‹x.È™lý'2’DIcòœ×„­ËCûóËƆs V”ú?t%æÈìÅnìNã/û +~FzvNš${å-Ð|1Å…¹êôSô$)¿Ñ")Ëb+­¡kÖ±ˆe/¹Ù•+v8ýí»ã诡#“/ì3•l+^tó9´Õôk©;‹n%ì{tw‰)3 ±px’}´ քÙ±E7¬$+8º{.0ó† “f.®Õƒ^IhRt…£ÛˆBá\‚œÙÀ®Fãï¥YàkíÑ ¯Ë+ºiUf¼þëP¼xž53@@P†LSp +¹‹Î¡Êkù+#-î‰@ñÕ³ûèMV3bŠáá`Ò7µ(Æ.ò/2xö\¨>RÜÿD.Ÿ(¾8Ú.~ùe3v¸ ,ãüì|¹®HÅJšÜˆsÆ!]±ô +’Bìà ¡Š%ª›YÏR1.L ¿E”×6ÇB—øß9"ë\®.Z6s¢Ù 1ð™ü¡ÐïìÇQOÒ:Lu߸#?¹@ÊSSñÖû‹Ër@o…J‘ö¿®‰üÅ›åIïã2õ]ñx)ëƒO·ÝÔÏjžâq;BbÈÕíPJ1¼¼Š{„à)qg^eˆyh` ÎqÉ{¯áq„÷‘­ªG‡õWßðäÚ3ÙºIúe†™FnŠ¦²„ú‘4ãîš &õ~YV?Úø̼HÐ-3 ¶] +/)3Ü%A4ëkhn~ëþ hО·%¸iKtþb@–Eã 4°?¤aÏ<•D(òž ½*ýÁæ¨ìîK¦kÈÓëyÀÌFL÷¹ ‡BX¦›‡²[æÑd¯d®¶Êtͷ›`º[ðvLßᘠì]1£ž˜.rÏícä5W¢}ÌtƒÛK•¯þ8‘•î ƒé6ÊÈ@tè¹k%|“ÿçn¶óø'˜a°ï‡›YÖbéþ央ÇyÏòþQlÞøÉ+cKÊbêixZTK¾‹Š) +Si@¯ŒFT¥Á/(VF^* ‚’Dͧ[wäŽÎ‚UFèˆrsÌEÝë|µZÀ°ôÌ;6R‰½2ø½òÌÎ]WÑ¥ƒêc[Úù+ã\†Ù#²<”÷ŠºCÇð%[xRŠó®TWnSƒÁÔÓ{_ƒÚÀûßÕeêdZHñˆ¿ö4¡ØV4uã”úk2k¢þX8ÚiÃj(¦vSôÆr˜0Ä­;¢™[¤ËÁ «ø^}I ^FeÞbŒ£ñÚ–èeº¦* \”ÀØM¤UuÙ“Ðá¤b5æI’È@£WºìTW¡œê¸Ë®xéM4ªDHê"¨‘‡÷³óhx4¦!µ¦¾²rñE÷Ëéˆ9Ÿo‰pêIøÒ¨]«²Õ˜j¦ \p,)ŸêþëûέÁ’7µh½CPMåĽŠ ]û³øÛz‚e¯‰[‡· pZùWqÌ/ ÆWdà|²àöú™~²ÆwÔ8¨â4¢HswÝ*·dc÷ι0üåëÎ +\³Jmû´ZTã"ÑË[ÈE†×¢_PV[ˆ]6qCò1uL{ +ˆ‹Ö’ k3gÍøQöµðð_ þè}WÐÌd±âËìrÉ"¢{r%“72…Îç¬y4Os¸_ÆÖ™­á Y·°™ãkt ÓT†!ûÙNe|,·ä†9ÐÚ—Çm´7¶*¾q¥ +½†qäköçùÙí§,ʼn³Ðg´7ìô~¢ëøæ•,3£Þ gD†M aÆòo½ó*Ö=¨uHe£'#/7Բƚe‹Ç Å쮇û}çt7JëçK¦‘uâx(W ¦,)•ÖìÞWXS¤fuÑ›:ï:´Îxæ| ‹ÎÚ:¿hreHHË-ÁsË#dÔút3ŒîÇÒcœ5­™ ÆU› ò²L•1Ò  ¶Mx»ŸC7*ƒàû,…æö쉈X7dˆríÈ„¸Ò üþ;æ xWß*7Sü«õ$‹ÎÖtÀ§fk:üÆæ¬V>j‚¨2ÆuÛpKN†WñïH°‹ºÀŒîú +¯{õPkv0)ãl®”ƒc×»|¶}üFH0á´×¥éð"{š–Â8g?]>_-鬘JŠ­[4`—΃¡3ê˜î§ïÆu…IO5ªÏ¡¦´8›=n옪@Ü[¬@Êôe\Äp¤1D£‹YÇäï¥$„Ho 휷Íè)÷¯€Úmƒ]is tâQµ½µµÙ$8·rŠãH‡ 1ÆŸ `•81Tª%§óלeµšRÌéhË*n‹¦zˆQ†ªp~^˜,‹Ñ +eäóã/×wzvï¯hç(²ž5¦?yY3@6µSxñ,€lƒMþÝi«¾÷߉Â/W„w°‰8ª…FL× ’¤,*y‡ ©±Ô&•# õˆgé-Î^«S=<ß>FÌtIezþ}ØoÛ;5ý©ÏuPÛ&Y†h"ñaº1b¹°ÝøÚ’¬³‹Òjrï:Gü^Y^×Ì–ãKÖT™?!¸4¾*B.L=3dgÛN´eJ}C>¿™Æ§€þp–ë0Èâ<Ï-•’€âqÏÓ'[i†÷³G^3­^U2÷Ò t¹ñÚfJ 䞈õž%Àâþe„m¦Â&y¹–Ò§ +^zŠzìhšÕ€“2l( |•½S¦l¹£•Å j©ä:rVh¶âWŸ!rg1뢻‚ÜJµX•+*röÒ¨_åa*Êdiw¶'ždÏÞrÎ[‹GôVÞLYþkk¡J7†U*?3«ò–"ö/s5·D ¬š^axìÓÙÌUs*Ö…®fÑvÓwlÝ( I¬ër¿D5RÀ‰MONØ ×¸/ lÞ÷;löÏo9ž˜ç|¿ ¹ׯãu_B·91hÅÛ\F&Œë”p7èû?ÿ±/;‘ŒgQfÛåøआ[gOÅ–©ðv0º2†É=‹¼ê-»×÷˜4Cn’Šý(¨xèeõ¶â†àˆÍ´37y\Åz¼Y˜y©]·Sóá²)LK{ ¾f®Ü«~Væ­™>ëYqŒ)Ov©†<³ÐÈ^5`;ÞíktçnJV ̯rhyk隣ÒLP®p/KK6L¬Á3g5ñÏhNá>à†íòèXœÍŒóNìtÊ}åz·¸Óœ¶A†Ò ‚š©Ü#3 N}œù}”©‚¡Ç¤¥æºVtÝÏë°€tÿ;x½Ë°÷ƒÞ„æɃônE›c0kÞP;Sæ ß9UØßÙ–E›'ÌòWÑËÒÖŒ˜KBÇéqê*¾yo‚ä½.óWÃÜöÊ·ÉˇÁÛ¼@(Õ«obpš3—wN0 +<Û~ýOSÊpÐ÷8pÌ1µWjläô½ÔxdìX8‹²cŒ: @yͱWΰäÚeZAÁcÐD’6β ^w=X ÉA²•Å1Ñ1B¢¨Ô=ò&†²ªÉüÐ&uf`ˆg|¡Á×ÕáŠ]~Ø3—5ý·~wÕ+Þ¿„£P ¼c„Ó‘E,ÊHt„šÍ{UÚ6ÔÀ·'h=o«æÕÜQC¨&sù¨cÈ%˵â¡8SHßG+¬÷æ2 F²ªF6s8Gìez¤¯ì…œƒÍv¸Ø¶¯D =ˆð8¿ñ:.7½™(ú­¦ì ®°ßNКºÛ,d护•<*—¡‹Rí£“€þ9q´Â`>Û -ŽÂý¾ÔÇzÖ0笎ÚË!ê§Eë8Û,ÌÞÅ%×éG,rÓ˜,‡ï±[­8g Íªæ|Åqœ•eÔƒaIÜ6ÔZ”B¬ÉVÈ{Ú1­•Áâ`ôLG†äež3Ä{t‘ߤª|—kbZu­ìL’h 1ªV¸Ýg> ?#Déë`œ„îýñЈ5ü˜&Êh¹Ï¸€·gþ>{éJcnkïqíY˜èo{9^®äåU„"R0\RÉ‹ÓvaYÀÃ’ñ ÊÀfx8 o«yÇŸ½å·‰¿CI16 QŒ?ß7^h $¢Â«ELf«fˆRÚŒ“êÍ—ªèÚU!îE9:¦/°Š\`똼¹¸HBÊǸ²L]!²^ÝÒ£6$ª?bÛÉ¡æá ¦ £Æž̧tš•W†¤|Ô߈~ŒÇ›r­Ð<},ž0cñ»[`Í“lTôQs¡qKÖ»óÞ89Ÿ·§?h£þF@ep;%·…wvR_—iÂàæÿ¡d9õKöö‡°{æLD.1¦¾áRû’à.TéƒsÐå_ +]Ä;ào×*ÐCú‰ÀÏTÌCÈNTí—^Y56|§Û’fö/;j»õ3Û^ýa´bPôÅ,W—=À^~®|Pþ¸Ówq.ÌÒçb·Õô™7(x£À³RáŒz}©ÔfA²¥!Ê4憘V3Óµ} +Àñ)ý﾿¿ LHö  :k(>²üfì¶2 @^áKš]šìI[p8d +W†pÆEVضô¶o5¡36ÿ>m:´á“_œcÞÆ Á"¥íKW—<ÿ×ýÝ¡Áz©¶8†ö®¢ÏpòÁÅÛ ‰ÉøÝ7u ÏÂtqªòÞ/ôçÐ1lÝëÍɯ.Ò®Ül&¶è±6¢sDLˆ'‘L:×ü!|@_[¤Éû I~‡;¨}¸ø0xfòœ;V1xdŠƒŒ“Í®L'Óû­¯ðRˆ·Õ_íÛédݾ ÌGšPàÀÍ Ý[»ùÏõõ$ÊŽ Ã§¸.”'í'6Ý+ŽìúX«²7©AFe+fÀlû7ÄàÏ9Xº½žWs>Ãf¦†ªí^Rˆ1¿6²gÍ×Ï0§.’[.cÿS]í?CúðñP¬ÜuŠµ…Ýÿ!rmÂoÒÛø +qcl¥\âüY…z>¥‰- 1áôAá$ÿ}É”g”ÔßÏ¢’#Óž9É¢Ì5>üœ…šâjÆKŽZ“ú/¯4Qw>Ø_ÜÏH², — ÎyÏXhøð©´€îòÌAñ»²Õ¼¯‹ª8q(Bópž¬qÈ#4§7Àr]l‚-‡¦W/BL3H*ý‰C*=käØôsW:›ÌUfõn UNÔf*A-ÝrŽa0¨u÷¥¬·ÇK‡¨7™z=ïI³Ûv(æ.r"o‡C$1íìf˜%¿«·ÒR[Ó&ͼn[™²ÞŒµ~ƒM¤{òMèEõ?zgñÍHÎEO/ „)UëÆù“<øêúT÷ûw8Ê»ÛäÖž`ÀÂ0`vQd[PTöœàÂÎ<í¯µ£­ëhïsMM™à*öv6´ –@×ÿø¾â 7y7Dµk@|Ç]R]Ô tìà›À “xÇ›%ÇÊh”|:s?!Z B¹+&˜n€Ie~c¶ž¹×®X-kW»Ð"W—àCY“€Úa›ÝÖ’yúSÕ…šû#ýQ™´Ç”K­‚«±»Òî¬ån—è6†ÝâüÍú—ªþý¡‰Ò:+5a8r6‘ÙŠ—¨qçÿ«•þ|k.2+«fð¹ª<€»—­íó³ô\@1W׶ÏÐÁÍš QðïUÍCéÈPg+ý!¶—Q-S$dß gK¹ ã˜©œr%]"%}†"-Iέ۞@Èz(´@ %“u»7fÒ¦d[n©NþÐ’’ X¡”ÚkØOÆ#;ôW%Aþ•–èCȸ fw Ï"¡QÀÆ• ßÍ·Í÷³^[3î)Ÿ—Þb³ ©cÌÉ¢O76¯4µ„X1<ñ*57;O2f"}nÙ¸…’TOùž°Üi(i܉ý\êç Û—³ñÛͺô¿@³ÒiéˆÞélÄw.ËW”÷ã¼yÁ«®œ½Ì~lýËï5ÅÔæ&-‹oÀ.‡p/O8=ý¦„ûÍ>êC¶"ù¼APë7ÿI­V˜Ñ9¯ˆaXAçt±éš4Á|xØU6d¬M„”PöSSƒ½"¦ z–LÀT­O˜¯Þ)ÉCÁù•ÏòñýÅß!U.BµÊüCoQ57å1ãL¢%¢?^Uèo*d€²rÊ…ß¡cè|–À¥(—‰ÜƒÞuJé„#çÙ‡y'Nõæµóÿª +¬HÞô!WC›í:2L~‡©RT¾* ô ÒnG_¡¥©–™¤ŠœÒy`½¾³àŒÓ„,V¬;Iúº¦“ÇtÊ)6gökªšf=¿¼@ïF +GšÈ·p ‘O÷œ'd•Ò.ü*ïÿn¨*õ3ßÀ7ª™a¡1žCyá» ¦.írúT2&ŽkÍ*Í¿7Ò9ZŠ¬?˜S’¶ÛMë•v b39tÛÕkIô&†`ò€&ûÞ‹ÈŠÔôMè>NiCôç—J7ªÚí}õ×eqLÑ32í;xÝ1ºõÿGò¦Û«7^úÙko3J¼³<ˆV¬/¯hšwáñÜ€ à%V„vìʶÓóq{N"4\©öZà ճÕ˜@ãZúmþ‘ø ;û» ê¨ïIäÕ(Õ’,* …dóf{*o\_Ì’"q®ET ªÿCäÐF!lÂ[L>÷Š9¬ýÁ÷ÖÀn'³KýjGº-;ì|tAõ‘œRkD'Å?k%”ÿ‡J*t‚(½#KNí]ê”$‡Öåi¬ãÃÛcf¥Š±&&ôô›=±Õ',‘I6…1BîøàB"ð¹{˜q’Ú m§'ů 'Km ¼^w“[¿BéË‘ôª(¾î_v +ïÿlV@ÍY@iÆôõ©B¡ø‘—¬öxõ\“,·ü¹‰†%‹ÒŸVŸÂ¢^dL«Zÿ"‹œ¦D’¯Å<R-ÉTc…±vÊ"ˆ} Ì##’Ÿ‚:3¦ðøü§€t=MÁ€»)‡i(t“XF9ßõØ¢²ÔÉ5ì½áîîøŸhMø°È ÏG‡Ó/K.aí(Íúf¬ÊØq á°èÚÈõ2l²Ì¤ 9Ë$Pš[õ˜î*×”O5ZlPbµù Ê1ÅÏ2ù‰…ˆ5´Ñ?­S£y]`® í^#d,Ú#‘ÔClÞjæŽ\%eIÐß<—ï`''¸Ö¿ÑzvVóÜ9Àø€ßJÂë#¶6çIàôRIÂk=Ò=¼MÈhñ’09\ ”‹r…ð4"jÃO=Yºt`½Z9X£bÜãDŸb@1qŸ˜^U}¼ž¬Œ>mTõr\žØ7 C» ÜóáÝ…µYÖ,Ú¨W;_†“ÚGŸåŽks•S†4z‹†¹Çûy>ê25íÉ^mƒWr]ËBoæ‰Ú|`A±_ œ`ïŠXa?ƒN¿ˆ +piþÎ ñ…ÈÀDŸ\.AĈH5¢“Ì9x ûÚêÆ:|øl°¿›œ5Ø6|@û£çªëÔò–`TdÝü¸Ó¢ŽçCCÀ®•À¬F 9Åm`•áHï-ó!Æ"Âï‹ß¸ÓÖVÒï`ÜäçmÃLµ¦™7=Ö;¢‘iRBúïèŸ3€ ÿnR:J5‹t XìL)ðF–:„l\|!Ì´Ç™?©Ðrù/jú aÒ›”Wå(èE1 +wd³>õÆÅå(”¸ ×z_æá ×HšëÃ×Ù¼gN7 èô‡…XœÓÞ4›_)~d˜_ÁZ‚œHÉï‘òK‘ï·ñ–3Z"Í?Lh'z÷Ý‹÷KK#´ÌþUmçz |ÝjùõÈȨYãÒv}ª,ü ™˜RÆA‰Ç}F*è|³…öv‰9Pð©ù¿xï·£y?`†‡¢9ë­°ÛÖR};ÒHb#5«Ô K>ŽQó¸i|ïXgy†4EâÄCùŽ÷"Æ8õß@<ÉÝØBÅ5-©ÞØ'ëÜì¸ß&‡Óü-¦H_Œð…ôºŸé¯|‘X!1ÙÑMbB„38M)íUîê“q®{µbÆS1GOeM#V> endobj xref +0 37 +0000000008 65535 f +0000000000 00000 f +0000016720 00000 n +0000016647 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000016 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000163 00000 n +0000018153 00000 n +0000018231 00000 n +0000043290 00000 n +0000017105 00000 n +0000018791 00000 n +0000018647 00000 n +0000018534 00000 n +0000017344 00000 n +0000017592 00000 n +0000017640 00000 n +0000018418 00000 n +0000018449 00000 n +0000018302 00000 n +0000018333 00000 n +0000018865 00000 n +0000019009 00000 n +0000020361 00000 n +0000043322 00000 n +trailer <<98702769C1B24085A272824F5FB09B4C>]>> startxref 43526 %%EOF \ No newline at end of file diff --git a/doc/store/S-20247.tpl.tex b/doc/store/S-20247.tpl.tex new file mode 100644 index 0000000..a6944c4 --- /dev/null +++ b/doc/store/S-20247.tpl.tex @@ -0,0 +1,43 @@ +\documentclass{article} +\usepackage{microtype} +\usepackage[paper=letterpaper,layoutwidth=11in,layoutheight=8.5in,portrait]{geometry} +\usepackage[newdimens]{labels}% let the package do the work... +\usepackage{graphicx} +\usepackage[scaled]{helvet} +\usepackage[T1]{fontenc} +\graphicspath{ {.} {/tmp/labels/} } +\renewcommand\familydefault{\sfdefault} +% +\LabelGridtrue +\LabelInfotrue +% +%\LabelGridfalse +%\LabelInfofalse +% +\LabelCols=4 +\LabelRows=8 +% +\LeftPageMargin=0.25in +\RightPageMargin=0.25in +\TopPageMargin=0.5in +\BottomPageMargin=0.5in +% +\InterLabelColumn=0mm% adjust as required +\InterLabelRow=0mm +% +\RightLabelBorder=1mm% adjust to taste +\LeftLabelBorder=1mm +\TopLabelBorder=6mm +\BottomLabelBorder=1mm +% +\setlength{\baselineskip}{0pt} +\setlength{\emergencystretch}{3em} +\setlength{\lineskip}{0pt} +\setlength{\parindent}{0pt} +\setlength{\parskip}{0pt} +\setlength{\tabcolsep}{0pt} +% +\numberoflabels=1% set to 1 when you fill in the individual labels - this will just repeat 1 instance 20 times to show the layout. +% +\begin{document} +% diff --git a/doc/store/avery-5160.tex b/doc/store/avery-5160.tex new file mode 100644 index 0000000..39a5285 --- /dev/null +++ b/doc/store/avery-5160.tex @@ -0,0 +1,59 @@ +\documentclass{article} +%\usepackage[default]{comicneue} +%\usepackage[sfdefault]{noto} +\usepackage[scaled]{helvet} +\usepackage[T1]{fontenc} +\usepackage[T1]{fontenc} +\usepackage[paper=letterpaper,layoutwidth=11in,layoutheight=8.5in,portrait]{geometry} +\usepackage[newdimens]{labels}% let the package do the work... +\usepackage{graphicx} +\usepackage{ragged2e} +\usepackage{microtype} +\graphicspath{ {.} {/tmp/labels/} } +\renewcommand\familydefault{\sfdefault} +% +\LabelGridtrue +\LabelInfotrue +% +%\LabelGridfalse +%\LabelInfofalse +% +\LabelCols=3 +\LabelRows=10 +% +\LeftPageMargin=0.21975in +\RightPageMargin=0.21975in +\TopPageMargin=0.5in +\BottomPageMargin=0.5in +% +\InterLabelColumn=0.14in % adjust as required +\InterLabelRow=0mm +% +\RightLabelBorder=1mm% adjust to taste +\LeftLabelBorder=1mm +\TopLabelBorder=0.3in +\BottomLabelBorder=1mm +% +\numberoflabels=30% set to 1 when you fill in the individual labels - this will just repeat 1 instance 20 times to show the layout. +% +\tolerance=9999 +\emergencystretch=10pt +\hyphenpenalty=100000 +\exhyphenpenalty=100 +\setlength{\parskip}{0pt} +\setlength{\lineskip}{0pt} +\setlength{\baselineskip}{0pt} +\fontdimen2\font=0pt +% +\begin{document} + +\genericlabel{% + \begin{minipage}{2in}% adjust as desired + %{\scriptsize \textls[-60]{REALLY VERY LONG, REALLY, REALLY, REALLY, LONG, REALLY VERY LONG, REALLY, REALLY, REALLY, LONG, LONG LONG, PRODUCT NAME}} \vspace*{1pt} \\ + {\scriptsize \textls[-60]{{Hello World}}} \vspace*{1pt} \\ + \begin{tabular}{p{0.59\linewidth} p{0.41\linewidth}} + {\Huge PRICE} & {\includegraphics[trim={0 0.5in 0 0.5in},clip,width=1in]{1}} \\ + \end{tabular} + \end{minipage}} + +\end{document} diff --git a/doc/todo.md b/doc/todo.md new file mode 100644 index 0000000..292a30e --- /dev/null +++ b/doc/todo.md @@ -0,0 +1,69 @@ +# Backend Changes +- [Done] Abritrary drag and drop (e.g. from main deck -> extra deck) +- [Done] optimum qty - cards in decks +- [Done] The "Optimum Qty" is "The number of times the card appears in + each deck (e.g. 3) times the number of decks it appears in times + 5". I think that's just SELECT COUNT(PASSCODE) ? And for + constructed decks it would be the same. + +- [In Progress] Constrcuted-Deck-Overview +- [In Progress] Storing / Saving / Updating optimum Qty +- [In Progress] Number in inventory of this card - what is this? The total SUM count from all YGO-SET-ITEMs? +- [In Progress] Created / Sold status - I think this is done already... +- [In Progress] Inverse Priority system (more cards = sell more, less cards = hold more) + +- [ ] Arbitrary inventories (Secondary Inventory, ...) + +# Categories + +- [In Progress] Better UI +- [ ] Show Decks from "subcategories" of current Category + +# Database +- [Done] Yu-Gi-Oh + - [Not Implemented] Images from CrystalCommerce + We just got API access too. + + - [Not Implemented] Attach card image to specific card + Nobody seemed to want to upload card images individiually. + + - [Done] Qty of card: number of times card appears in decks x multiplier (default x1) + This is implemented but it doesn't display on the web frontend. + +- [ ] Pokemon +- [ ] Magic + +# Constructed Deck Overview + +- [ ] TODO + +# Inventory + +We deleted the Crystal Commerce stuff. Maybe it will make a comeback. + +# UI Improvements +- [In progress] Many, many, many, subtle UI bugs... + +- [ ] Deck-Overview shows: + - [ ] constructed Decks based on this deck + - [ ] sold decks + - [ ] Use this information x a multiplier to construct "optimal qty." of cards to keep in stock. +- [ ] Deck Builder Sub children category + +# Other + +- [ ] Constructed Deck Overview Panel +- [ ] when its sold and how often it sells based on deck sells +- [ ] Remove Category removes decks from category +- [ ] Peronsalized decks via inventory qty +- [ ] CC API for DB Connect +- [ ] Output to text / CSV +- [ ] Environment variable for translation WITH-LOCALE + +# Not Implemented Yet +- [ ] Import new stuff from Crystal Commerce on the Web UI +- [ ] Export to CSV for TCGPlayer Card Sorting Machines +- [ ] Sync with our App - TCG Machines? I looked into their stuff. It + looks very proprietary. Perhaps I should take a look at it. + +Check your notepad for notes diff --git a/doc/web/nginx/deck-builder b/doc/web/nginx/deck-builder new file mode 100644 index 0000000..2763371 --- /dev/null +++ b/doc/web/nginx/deck-builder @@ -0,0 +1,27 @@ +server { + listen [::]:80; + listen 80; + + server_name phntsm.ddns.net; + + root /var/www/html; + + location / { + return 301 https://$host$request_uri; + } + + location /.well-known/acme-challenge/ { + alias /var/www/challenges/; + try_files $uri =404; + } + + location ~ /\.ht { + deny all; + } + + location /nginx_status { + stub_status; + allow 127.0.0.1; #only allow requests from localhost + deny all; #deny all other hosts + } +} diff --git a/doc/web/nginx/deck-builder-tls b/doc/web/nginx/deck-builder-tls new file mode 100644 index 0000000..9e16810 --- /dev/null +++ b/doc/web/nginx/deck-builder-tls @@ -0,0 +1,122 @@ +server { + listen [::]:443 default_server ssl; + listen 443 default_server ssl; + + server_name phntsm.ddns.net; + + ssl_certificate "/etc/ssl/nginx/phntsm.ddns.net.crt"; + ssl_certificate_key "/etc/ssl/nginx/phntsm.ddns.net.key"; + ssl_dhparam "/etc/ssl/nginx/phntsm.ddns.net.dhparam"; + + ssl_session_timeout 5m; + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_session_cache shared:SSL:50m; + ssl_prefer_server_ciphers on; + + root /var/www/html; + index index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + # try_files $uri $uri/ =404; + + set_real_ip_from unix:; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + client_max_body_size 8m; + #auth_basic "Restricted Content"; + #auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_send_timeout 300; + fastcgi_read_timeout 300; + + proxy_pass http://127.0.0.1:5005; + include /etc/nginx/fastcgi_params; + include /etc/nginx/proxy_params; + } + + # /~user web directories + location ~ ^/~(.+?)(/.*)?$ { + alias /home/$1/www$2; + index index.html index.htm; + autoindex on; + } + + # location /_/static/assets/ { + # alias /home/git/public/; + # } + + location /git/ { + auth_basic "Restricted Content"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_send_timeout 300; + fastcgi_read_timeout 300; + + client_max_body_size 512M; + # make nginx use unescaped URI, keep "%2F" as is + rewrite ^ $request_uri; + rewrite ^/git(/.*) $1 break; + proxy_pass http://127.0.0.1:3000$uri; + include /etc/nginx/fastcgi_params; + include /etc/nginx/proxy_params; + } + + location ~ /collectd/ { + gzip off; + root /home/user/code/alpine-collectd-web; + + auth_basic "Restricted Content"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_send_timeout 300; + fastcgi_read_timeout 300; + + #proxy_http_version 1.1; + rewrite ^/collectd/(.*) /$1 break; + proxy_pass http://127.0.0.1:8888; + } + + location ~ /psy/ { + auth_basic "Restricted Content"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_send_timeout 300; + fastcgi_read_timeout 300; + + #proxy_http_version 1.1; + # rewrite ^/psy/(.*) /$1 break; + include /etc/nginx/proxy_params; + proxy_pass http://127.0.0.1:9292; + } + + # location ~ \.php$ { + # include /etc/nginx/fastcgi_params; + # include /etc/nginx/proxy_params; + # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + # # fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; + # fastcgi_pass unix:/var/run/php/php-fpm.sock; + # } + + # location ~ /phpmyadmin/ { + # auth_basic "Restricted Content"; + # auth_basic_user_file /etc/nginx/.htpasswd; + # index index.php; + # } + + location ~ /\.ht { + deny all; + } + +} diff --git a/doc/web/nginx/default b/doc/web/nginx/default new file mode 100644 index 0000000..c5af914 --- /dev/null +++ b/doc/web/nginx/default @@ -0,0 +1,91 @@ +## +# You should look at the following URL's in order to grasp a solid understanding +# of Nginx configuration files in order to fully unleash the power of Nginx. +# https://www.nginx.com/resources/wiki/start/ +# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/ +# https://wiki.debian.org/Nginx/DirectoryStructure +# +# In most cases, administrators will remove this file from sites-enabled/ and +# leave it as reference inside of sites-available where it will continue to be +# updated by the nginx packaging team. +# +# This file will automatically load configuration files provided by other +# applications, such as Drupal or Wordpress. These applications will be made +# available underneath a path with that package name, such as /drupal8. +# +# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. +## + +# Default server configuration +# +server { + listen 80 default_server; + listen [::]:80 default_server; + + # SSL configuration + # + # listen 443 ssl default_server; + # listen [::]:443 ssl default_server; + # + # Note: You should disable gzip for SSL traffic. + # See: https://bugs.debian.org/773332 + # + # Read up on ssl_ciphers to ensure a secure configuration. + # See: https://bugs.debian.org/765782 + # + # Self signed certs generated by the ssl-cert package + # Don't use them in a production server! + # + # include snippets/snakeoil.conf; + + root /var/www/html; + + # Add index.php to the list if you are using PHP + index index.html index.htm index.nginx-debian.html; + + server_name _; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + try_files $uri $uri/ =404; + } + + # pass PHP scripts to FastCGI server + # + #location ~ \.php$ { + # include snippets/fastcgi-php.conf; + # + # # With php-fpm (or other unix sockets): + # fastcgi_pass unix:/run/php/php7.4-fpm.sock; + # # With php-cgi (or other tcp sockets): + # fastcgi_pass 127.0.0.1:9000; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} + + +# Virtual Host configuration for example.com +# +# You can move that to a different file under sites-available/ and symlink that +# to sites-enabled/ to enable it. +# +#server { +# listen 80; +# listen [::]:80; +# +# server_name example.com; +# +# root /var/www/example.com; +# index index.html; +# +# location / { +# try_files $uri $uri/ =404; +# } +#} diff --git a/doc/wiki.org b/doc/wiki.org new file mode 100644 index 0000000..33fcdef --- /dev/null +++ b/doc/wiki.org @@ -0,0 +1,55 @@ +#+TITLE: Wiki KB + +* Abbreviations + +- CG :: Card Game +- TCG :: Trading Card Game +- OCG :: Official Card Game + +* Links + +** Reference +- [[http://cheat.markdunkley.com/][Liquid Cheat Sheet]] +- [[https://challonge.com/pricing][Challonge]] +- [[https://decks.tcgplayer.com/magic][TCGPlayer Deck Builder - MTG]] +- [[https://jqlang.github.io/jq/tutorial/][JQ Tutorial]] +- [[https://keyrune.andrewgioia.com][Keyrune Magic: the Gathering set symbol font]] +- [[https://lisptips.com/post/11728375873/initform-and-default-initargs][:initform and :default-initargs]] +- [[https://npm.io/package/ydke][ydke]] +- [[https://scryfall.com/docs/api][Scryfall is a powerful Magic: The Gathering card search]] +- [[https://www.db.yugioh-card.com/yugiohdb/][Konami Yu-Gi-Oh! DB]] +- [[https://www.masterduelmeta.com/][Master Duel Meta]] +- [[https://www.noip.com/integrate/request][No-IP Dynamic DNS Support]] +- [[https://www.sqlite.org/lang_analyze.html]] +- [[https://www.toptal.com/designers/htmlarrows/][HTML Arrows]] +- [[https://yugioh.fandom.com/wiki/Template:Rarities][Template:Rarities]] +- [[https://yugipedia.com/wiki/][Yugipedia]] +- [[https://www.db.yugioh-card.com/yugiohdb/forbidden_limited.action][Forbidden / Limited List]] +- [[https://www.youtube.com/watch?v=sy7dtW8CvQ4][How Do MTG Card Sorting Machines Compare?]] + +*** Might Be Useful +- [[https://dev.pokemontcg.io/][PokemonTCG API Dev Portal]] +- [[https://github.com/FelixRilling/yugioh-deck-tool][Yu-Gi-Oh! Deck Tool]] +- https://github.com/AccelerationNet/function-cache +- https://github.com/OpenBookStore/openbookstore +- https://github.com/lispnik/iup +- https://github.com/mmontone/sxql-composer + +** Fun +- [[https://ygopro.org/yugioh-card-maker/][Custom Yu-Gi-Oh Card Maker]] +- [[https://pokecardmaker.net/creator][Custom Pokémon Card Creator]] +- [[https://www.duelingbook.com/][Dueling Book]] +- [[https://gimp-tools.sourceforge.net/tools.shtml][Ofnuts' Gimp Tools]] +- [[https://www.deviantart.com/teufik2305/art/Yu-Gi-Oh-Playmat-60x35cm-with-Labeled-Zones-853628853][Yu-Gi-Oh! Playmat with Labeled Zones]] +- [[https://www.metamats.com/1-player-templates][1 Player Templates - Metamats]] + - [[https://images.squarespace-cdn.com/content/v1/58f293311b631b9852e71d4b/1591657147889-DOEJSUD8UN7KZ61YPYNQ/1Player+Zones-01.png?format=2500w]] + - [[https://images.squarespace-cdn.com/content/v1/58f293311b631b9852e71d4b/1591657047085-J5LQ9ABPX7HJQZD9DDEZ/1player+zones+Transparent-01-01.png?format=2500w]] + +** Tools + +- [[https://dnschecker.org/all-dns-records-of-domain.php][DNS Checker]] +- [[https://gitlab.com/fisxoj/sly-docker-poc/-/tree/master]] +- [[https://www.freecodecamp.org/news/rest-api-design-best-practices-build-a-rest-api/]] +- [[https://restfulapi.net/rest-api-design-tutorial-with-example/]] +- [[https://stackoverflow.com/a/34001943][multiple javascript queryselectors conditions]] +- [[https://dba.stackexchange.com/questions/89051/stored-procedure-to-update-an-adjacency-model-to-nested-sets-model]] diff --git a/doc/workflow.md b/doc/workflow.md new file mode 100644 index 0000000..6077b4f --- /dev/null +++ b/doc/workflow.md @@ -0,0 +1,50 @@ +# Workflow Design Doc + +## Overview + +The workflow for constructing decks is as follows: + +1. Create deck of cards. Currently we select from all cards. Maybe we + should just select cards from inventory. The majority of this + functionality in place and isn't expected to change. A deck will + always just be a list of cards plus metadata. + +2. The deck of cards has metadata about author, name of deck, time of + creation. The main content of the deck is three lists, comprising + the main, extra, and side decks. This information is not expected + to change in the future. Decks created by a particular user for + example. The underlying metadata representation is what's being + worked on. Currently each item in the database has all information + duplicated from the rest of the database. Changing the price for + one card changes the price for only that one card matching that row + in the database. There will be additional tables to store pricing + information in the next step. + +3. On the Deck Overview page, selecting "Pull Deck" will decrease the + inventory of the lowest priced card in inventory by one for every + card in the deck. This is like "add to cart" in an online shopping + platform, with additional inventory keeping. This is analagous to + the deck construction step, except instead of selecting cards to be + put into a deck, that information is provided a priori, and we use + that list of information to construct secondary lists. + +4. On the pulled deck page, for every card in the deck, you will be + able to select cards by edition, condition, and rarity of every + card in the inventory matching that card's passcode. I can + conceive a very simple concept where you attempt to check out, and + then are returned with error messages saying which items had + errors. This is the current approach. + +5. There will be an intermediate stage. Once there is enough inventory + and the deck is "pulled" it will enter an area where each + individual card will be selected. e.g. selecting the rarity or set + of a particular card in each deck. + +6. Once all "errors" are resolved (banlists, constraings, etc), the + "pulled" deck will allow you to "construct" it, which will finalize + the state of this pulled deck in the database, moving it to another + table, the list of decks for sale. + +7. The list of decks for sale is just pulled, constructed decks with + pricing information attached and whether or not it was sold and + what price it was sold at and when. diff --git a/doc/ygoprodeck-import.md b/doc/ygoprodeck-import.md new file mode 100644 index 0000000..1dd133e --- /dev/null +++ b/doc/ygoprodeck-import.md @@ -0,0 +1,149 @@ +# Introduction + +The [Yu-Gi-Oh! API by YGOPRODeck](https://ygoprodeck.com/api-guide/) provides the following information. + +# Data + +The base data for cards. This is always returned, without asking for `?misc=Yes`. + +JSON Node Name: `data` + +## All Cards + +Every card has these attributes. + +- `id`: Passcode on the bottom left of the card. +- `desc`: Card description / effect text. +- `frameType`: The backdrop type that this card uses (normal, effect, synchro, spell, trap, etc.). +- `name`: Name of the card. +- `type`: The type of card you are viewing (Normal Monster, Effect Monster, Synchro Monster, Spell Card, Trap Card, etc.). + +## Monster Cards + +Cards that attack or defende have ATK and DEF. E.g. Spell and Trap do +not have these attributes, except Race. The Race of a Spell card is +"Spell" and the Race of a trap card is "Trap". + +- `atk`: The ATK value of the card. +- `attribute`: The attribute of the card. +- `def`: The DEF value of the card. +- `level`: The Level/RANK of the card. +- `race`: The card race which is officially called type (Spellcaster, Warrior, Insect, etc). + +## Spell/Trap Cards + +- `race`: The card race which is officially called type for Spell/Trap Cards (Field, Equip, Counter, etc). + +## Card Archetype + +- `archetype`: The Archetype that the card belongs to. Alan takes + feedback on Archetypes + [here](https://github.com/AlanOC91/YGOPRODeck/issues/10). + +## Additional Response for Pendulum Monsters + +- `scale`: The Pendulum Scale Value. + +## Additional Response for Link Monsters + +- `linkval`: The Link Value of the card if it's of type "Link Monster". +- `linkmarkers`: The Link Markers of the card if it's of type "Link Monster". This information is returned as an array. + +# Set Info + +JSON Node Name: `data.card_sets` + +Fields: + +- `set_code` +- `set_name` +- `set_price` +- `set_rarity` +- `set_rarity_code` + +# Images + +JSON Node Name: `data.card_images` + +Fields: + +- `image_url` +- `image_url_cropped` +- `image_url_small` + +# Pricing + +JSON Node Name: `data.card_prices` + +Fields: + +- `amazon_price` +- `cardmarket_price` +- `coolstuffinc_price` +- `ebay_price` +- `tcgplayer_price` + +# `misc_info` + +JSON Node Name: `data.misc_info` + +Fields: + +- `beta_id` +- `beta_name` +- `downvotes` +- `has_effect` +- `konami_id` +- `misc_info` +- `ocg_date` +- `question_atk` +- `question_def` +- `staple` +- `tcg_date` +- `treated_as` +- `upvotes` +- `views` +- `viewsweek` + +[Using &misc=yes will now show question_atk and/or question_def to be 1 for true if a card is found to have a ? in the data.] +(https://github.com/AlanOC91/YGOPRODeck/issues/152#issuecomment-1032552854) + +## Banlist Info + +JSON Node Name: `data.misc_info.banlist_info` + +Fields: + +- `ban_goat` +- `ban_ocg` +- `ban_tcg` + +## Formats + +JSON Node Name: `data.misc_info.formats` + +Values: + +- `Common Charity` +- `Duel Links` +- `Edison` +- `GOAT` +- `OCG` +- `OCG GOAT` +- `Speed Duel` +- `TCG` + +## Link Markers + +JSON Node Name: `data.misc_info.linkmarkers` + +Values: + +- `Top` +- `Top-Right` +- `Right` +- `Bottom-Right` +- `Bottom` +- `Bottom-Left` +- `Left` +- `Top-Left` diff --git a/locale/en_US/LC_MESSAGES/cl-deck-builder2.mo b/locale/en_US/LC_MESSAGES/cl-deck-builder2.mo new file mode 100644 index 0000000..5bdc195 Binary files /dev/null and b/locale/en_US/LC_MESSAGES/cl-deck-builder2.mo differ diff --git a/locale/en_US/LC_MESSAGES/cl-deck-builder2.po b/locale/en_US/LC_MESSAGES/cl-deck-builder2.po new file mode 100644 index 0000000..429471a --- /dev/null +++ b/locale/en_US/LC_MESSAGES/cl-deck-builder2.po @@ -0,0 +1,242 @@ +#: templates/builder/index.html +#, lisp-format +msgid "Deck Builder" +msgstr "" + +#: templates/construct/select-sets.html +#, lisp-format +msgid "Select Card Sets" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Nothing to see here!" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Oops" +msgstr "Oops" + +#: templates/contact/admin.html +#, lisp-format +msgid "Feedback Admin Panel" +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "Try typing it up in Notepad, then Copy/Paste." +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "" +"You can type up a message here and submit it for the administrator to see." +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "Contact Us" +msgstr "" + +#: templates/decks/_field_control_select.html +#, lisp-format +msgid "None" +msgstr "" + +#: templates/decks/index.html +#, lisp-format +msgid "Deck List" +msgstr "" + +#: templates/index.html +#, fuzzy, lisp-format +msgid "Click the links at the top to get started." +msgstr "Click the links in the sidebar to get started." + +#: templates/index.html +#, lisp-format +msgid "Welcome to cl-deck-builder2!" +msgstr "Welcome to cl-deck-builder2!" + +#: templates/index.html +#, lisp-format +msgid "Index" +msgstr "Index" + +#: templates/kde-team.html +#, lisp-format +msgid "KDE Team Deck Listing" +msgstr "" + +#: templates/label-maker.html +#, lisp-format +msgid "Label Maker" +msgstr "" + +#: templates/qr.html +#, lisp-format +msgid "QR Code Generator" +msgstr "" + +#: templates/tabs.html +#, lisp-format +msgid "Tabs" +msgstr "" + +#: templates/upload.html +#, lisp-format +msgid "File Uploader" +msgstr "" + +#: templates/user/admin.html +#, lisp-format +msgid "User Admin Panel" +msgstr "" + +#: templates/user/login.html +#, lisp-format +msgid "Login" +msgstr "" + +#: templates/user/register.html +#, lisp-format +msgid "Register" +msgstr "" + +# English translations for cl-deck-builder package. +# Copyright (C) 2023 THE cl-deck-builder'S COPYRIGHT HOLDER +# This file is distributed under the same license as the cl-deck-builder package. +# Automatically generated, 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: cl-deck-builder 2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-10 19:17+0000\n" +"PO-Revision-Date: 2023-09-20 23:11+0000\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/i18n.lisp:60 +msgid "Please login to continue" +msgstr "Please login to continue" + +#: src/web/upload.lisp:67 +#, lisp-format +msgid "Upload success! ~a" +msgstr "Upload success! ~a" + +#: src/web/upload.lisp:70 +msgid "Error uploading file. Try again?" +msgstr "Error uploading file. Try again?" + +#: src/web/upload.lisp:85 +#, lisp-format +msgid "No such attachment #~d" +msgstr "No such attachment #~d" + +#: src/web/contact.lisp:45 +msgid "Feedback cleared." +msgstr "Feedback cleared." + +#: src/web/contact.lisp:47 src/web/contact.lisp:72 src/web/qr.lisp:38 +#: src/web/ydk.lisp:14 src/web/ydk.lisp:25 src/web/decks.lisp:121 +#: src/web/decks.lisp:138 src/web/label-maker.lisp:125 src/web/user.lisp:79 +#: src/web/user.lisp:83 +msgid "Something went wrong. Try again?" +msgstr "Something went wrong. Try again?" + +#: src/web/contact.lisp:71 +msgid "Thanks! We got your message." +msgstr "Thanks! We got your message." + +#: src/web/category.lisp:65 src/web/decks.lisp:137 +msgid "Success!" +msgstr "Success!" + +#: src/web/builder.lisp:220 +#, lisp-format +msgid "Deck ~a already exists in database; loading..." +msgstr "" + +#: src/web/builder.lisp:224 +#, lisp-format +msgid "Creating deck ~a into database." +msgstr "" + +#: src/web/builder.lisp:279 +#, lisp-format +msgid "Found entry ~A (~d); loading...~%" +msgstr "" + +#: src/web/builder.lisp:298 +#, lisp-format +msgid "Found entry ~A (~d); renaming to ~a.~%" +msgstr "" + +#: src/web/decks.lisp:64 +msgid "Nothing to see here..." +msgstr "" + +#: src/web/decks.lisp:242 +msgid "No Name" +msgstr "No Name" + +#: src/web/decks.lisp:290 +#, lisp-format +msgid "Deck ~a deleted success!" +msgstr "Deck ~a deleted success!" + +#: src/web/toolkit.lisp:29 +msgid "Please log in." +msgstr "Please log in." + +#: src/web/label-maker.lisp:108 +msgid "No files." +msgstr "" + +#: src/web/label-maker.lisp:122 +#, lisp-format +msgid "Success! Download link: labels.pdf" +msgstr "" + +#: src/web/user.lisp:23 +#, fuzzy, lisp-format +msgid "You are already logged in as: ~a" +msgstr "You aren't logged in." + +#: src/web/user.lisp:29 +msgid "A user with that email is already registered." +msgstr "" + +#: src/web/user.lisp:43 +#, lisp-format +msgid "Hello, ~a!" +msgstr "Hello, ~a!" + +#: src/web/user.lisp:47 +msgid "Incorrect password." +msgstr "Incorrect password." + +#: src/web/user.lisp:50 +msgid "No user corresponding to this email address." +msgstr "No user corresponding to this email address." + +#: src/web/user.lisp:76 +#, fuzzy +msgid "Success! Please log in." +msgstr "Please log in." + +#: src/web/user.lisp:123 +msgid "Logged out." +msgstr "Logged out." + +#: src/web/user.lisp:124 +msgid "You aren't logged in." +msgstr "You aren't logged in." diff --git a/locale/templates/LC_MESSAGES/.gitignore b/locale/templates/LC_MESSAGES/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/locale/templates/LC_MESSAGES/cl-deck-builder2.pot b/locale/templates/LC_MESSAGES/cl-deck-builder2.pot new file mode 100644 index 0000000..caf484b --- /dev/null +++ b/locale/templates/LC_MESSAGES/cl-deck-builder2.pot @@ -0,0 +1,241 @@ +#: templates/builder/index.html +#, lisp-format +msgid "Deck Builder" +msgstr "" + +#: templates/construct/select-sets.html +#, lisp-format +msgid "Select Card Sets" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Nothing to see here!" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Oops" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Feedback Admin Panel" +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "Try typing it up in Notepad, then Copy/Paste." +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "" +"You can type up a message here and submit it for the administrator to see." +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "Contact Us" +msgstr "" + +#: templates/decks/_field_control_select.html +#, lisp-format +msgid "None" +msgstr "" + +#: templates/decks/index.html +#, lisp-format +msgid "Deck List" +msgstr "" + +#: templates/index.html +#, lisp-format +msgid "Click the links at the top to get started." +msgstr "" + +#: templates/index.html +#, lisp-format +msgid "Welcome to cl-deck-builder2!" +msgstr "" + +#: templates/index.html +#, lisp-format +msgid "Index" +msgstr "" + +#: templates/kde-team.html +#, lisp-format +msgid "KDE Team Deck Listing" +msgstr "" + +#: templates/label-maker.html +#, lisp-format +msgid "Label Maker" +msgstr "" + +#: templates/qr.html +#, lisp-format +msgid "QR Code Generator" +msgstr "" + +#: templates/tabs.html +#, lisp-format +msgid "Tabs" +msgstr "" + +#: templates/upload.html +#, lisp-format +msgid "File Uploader" +msgstr "" + +#: templates/user/admin.html +#, lisp-format +msgid "User Admin Panel" +msgstr "" + +#: templates/user/login.html +#, lisp-format +msgid "Login" +msgstr "" + +#: templates/user/register.html +#, lisp-format +msgid "Register" +msgstr "" + +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-10 19:17+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/i18n.lisp:60 +msgid "Please login to continue" +msgstr "" + +#: src/web/upload.lisp:67 +#, lisp-format +msgid "Upload success! ~a" +msgstr "" + +#: src/web/upload.lisp:70 +msgid "Error uploading file. Try again?" +msgstr "" + +#: src/web/upload.lisp:85 +#, lisp-format +msgid "No such attachment #~d" +msgstr "" + +#: src/web/contact.lisp:45 +msgid "Feedback cleared." +msgstr "" + +#: src/web/contact.lisp:47 src/web/contact.lisp:72 src/web/qr.lisp:38 +#: src/web/ydk.lisp:14 src/web/ydk.lisp:25 src/web/decks.lisp:121 +#: src/web/decks.lisp:138 src/web/label-maker.lisp:125 src/web/user.lisp:79 +#: src/web/user.lisp:83 +msgid "Something went wrong. Try again?" +msgstr "" + +#: src/web/contact.lisp:71 +msgid "Thanks! We got your message." +msgstr "" + +#: src/web/category.lisp:65 src/web/decks.lisp:137 +msgid "Success!" +msgstr "" + +#: src/web/builder.lisp:220 +#, lisp-format +msgid "Deck ~a already exists in database; loading..." +msgstr "" + +#: src/web/builder.lisp:224 +#, lisp-format +msgid "Creating deck ~a into database." +msgstr "" + +#: src/web/builder.lisp:279 +#, lisp-format +msgid "Found entry ~A (~d); loading...~%" +msgstr "" + +#: src/web/builder.lisp:298 +#, lisp-format +msgid "Found entry ~A (~d); renaming to ~a.~%" +msgstr "" + +#: src/web/decks.lisp:64 +msgid "Nothing to see here..." +msgstr "" + +#: src/web/decks.lisp:242 +msgid "No Name" +msgstr "" + +#: src/web/decks.lisp:290 +#, lisp-format +msgid "Deck ~a deleted success!" +msgstr "" + +#: src/web/toolkit.lisp:29 +msgid "Please log in." +msgstr "" + +#: src/web/label-maker.lisp:108 +msgid "No files." +msgstr "" + +#: src/web/label-maker.lisp:122 +#, lisp-format +msgid "Success! Download link: labels.pdf" +msgstr "" + +#: src/web/user.lisp:23 +#, lisp-format +msgid "You are already logged in as: ~a" +msgstr "" + +#: src/web/user.lisp:29 +msgid "A user with that email is already registered." +msgstr "" + +#: src/web/user.lisp:43 +#, lisp-format +msgid "Hello, ~a!" +msgstr "" + +#: src/web/user.lisp:47 +msgid "Incorrect password." +msgstr "" + +#: src/web/user.lisp:50 +msgid "No user corresponding to this email address." +msgstr "" + +#: src/web/user.lisp:76 +msgid "Success! Please log in." +msgstr "" + +#: src/web/user.lisp:123 +msgid "Logged out." +msgstr "" + +#: src/web/user.lisp:124 +msgid "You aren't logged in." +msgstr "" diff --git a/locale/templates/LC_MESSAGES/djula.pot b/locale/templates/LC_MESSAGES/djula.pot new file mode 100644 index 0000000..f667e61 --- /dev/null +++ b/locale/templates/LC_MESSAGES/djula.pot @@ -0,0 +1,105 @@ + +#: templates/builder/index.html +#, lisp-format +msgid "Deck Builder" +msgstr "" + +#: templates/construct/select-sets.html +#, lisp-format +msgid "Select Card Sets" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Nothing to see here!" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Oops" +msgstr "" + +#: templates/contact/admin.html +#, lisp-format +msgid "Feedback Admin Panel" +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "Try typing it up in Notepad, then Copy/Paste." +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "You can type up a message here and submit it for the administrator to see." +msgstr "" + +#: templates/contact/index.html +#, lisp-format +msgid "Contact Us" +msgstr "" + +#: templates/decks/_field_control_select.html +#, lisp-format +msgid "None" +msgstr "" + +#: templates/decks/index.html +#, lisp-format +msgid "Deck List" +msgstr "" + +#: templates/index.html +#, lisp-format +msgid "Click the links at the top to get started." +msgstr "" + +#: templates/index.html +#, lisp-format +msgid "Welcome to cl-deck-builder2!" +msgstr "" + +#: templates/index.html +#, lisp-format +msgid "Index" +msgstr "" + +#: templates/kde-team.html +#, lisp-format +msgid "KDE Team Deck Listing" +msgstr "" + +#: templates/label-maker.html +#, lisp-format +msgid "Label Maker" +msgstr "" + +#: templates/qr.html +#, lisp-format +msgid "QR Code Generator" +msgstr "" + +#: templates/tabs.html +#, lisp-format +msgid "Tabs" +msgstr "" + +#: templates/upload.html +#, lisp-format +msgid "File Uploader" +msgstr "" + +#: templates/user/admin.html +#, lisp-format +msgid "User Admin Panel" +msgstr "" + +#: templates/user/login.html +#, lisp-format +msgid "Login" +msgstr "" + +#: templates/user/register.html +#, lisp-format +msgid "Register" +msgstr "" diff --git a/locale/templates/LC_MESSAGES/lisp.pot b/locale/templates/LC_MESSAGES/lisp.pot new file mode 100644 index 0000000..50c4602 --- /dev/null +++ b/locale/templates/LC_MESSAGES/lisp.pot @@ -0,0 +1,135 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-10 19:17+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/i18n.lisp:60 +msgid "Please login to continue" +msgstr "" + +#: src/web/upload.lisp:67 +#, lisp-format +msgid "Upload success! ~a" +msgstr "" + +#: src/web/upload.lisp:70 +msgid "Error uploading file. Try again?" +msgstr "" + +#: src/web/upload.lisp:85 +#, lisp-format +msgid "No such attachment #~d" +msgstr "" + +#: src/web/contact.lisp:45 +msgid "Feedback cleared." +msgstr "" + +#: src/web/contact.lisp:47 src/web/contact.lisp:72 src/web/qr.lisp:38 +#: src/web/ydk.lisp:14 src/web/ydk.lisp:25 src/web/decks.lisp:121 +#: src/web/decks.lisp:138 src/web/label-maker.lisp:125 src/web/user.lisp:79 +#: src/web/user.lisp:83 +msgid "Something went wrong. Try again?" +msgstr "" + +#: src/web/contact.lisp:71 +msgid "Thanks! We got your message." +msgstr "" + +#: src/web/category.lisp:65 src/web/decks.lisp:137 +msgid "Success!" +msgstr "" + +#: src/web/builder.lisp:220 +#, lisp-format +msgid "Deck ~a already exists in database; loading..." +msgstr "" + +#: src/web/builder.lisp:224 +#, lisp-format +msgid "Creating deck ~a into database." +msgstr "" + +#: src/web/builder.lisp:279 +#, lisp-format +msgid "Found entry ~A (~d); loading...~%" +msgstr "" + +#: src/web/builder.lisp:298 +#, lisp-format +msgid "Found entry ~A (~d); renaming to ~a.~%" +msgstr "" + +#: src/web/decks.lisp:64 +msgid "Nothing to see here..." +msgstr "" + +#: src/web/decks.lisp:242 +msgid "No Name" +msgstr "" + +#: src/web/decks.lisp:290 +#, lisp-format +msgid "Deck ~a deleted success!" +msgstr "" + +#: src/web/toolkit.lisp:29 +msgid "Please log in." +msgstr "" + +#: src/web/label-maker.lisp:108 +msgid "No files." +msgstr "" + +#: src/web/label-maker.lisp:122 +#, lisp-format +msgid "Success! Download link: labels.pdf" +msgstr "" + +#: src/web/user.lisp:23 +#, lisp-format +msgid "You are already logged in as: ~a" +msgstr "" + +#: src/web/user.lisp:29 +msgid "A user with that email is already registered." +msgstr "" + +#: src/web/user.lisp:43 +#, lisp-format +msgid "Hello, ~a!" +msgstr "" + +#: src/web/user.lisp:47 +msgid "Incorrect password." +msgstr "" + +#: src/web/user.lisp:50 +msgid "No user corresponding to this email address." +msgstr "" + +#: src/web/user.lisp:76 +msgid "Success! Please log in." +msgstr "" + +#: src/web/user.lisp:123 +msgid "Logged out." +msgstr "" + +#: src/web/user.lisp:124 +msgid "You aren't logged in." +msgstr "" diff --git a/old/api/api-v0.lisp b/old/api/api-v0.lisp new file mode 100644 index 0000000..fb4e0d4 --- /dev/null +++ b/old/api/api-v0.lisp @@ -0,0 +1,3 @@ +(in-package #:cl-deck-builder2.api) + +(defclass api-v0 ()) diff --git a/old/api/api-v1.lisp b/old/api/api-v1.lisp new file mode 100644 index 0000000..dfd8b47 --- /dev/null +++ b/old/api/api-v1.lisp @@ -0,0 +1,14 @@ +(in-package #:cl-deck-builder2.api) + +(defclass api-v1 (api-v0)) + +(defclass db-metaclass (standard-object) + ((create) + (find) + (select) + (update) + (delete))) + +(defclass deck () + () + (:metaclass db-metaclass)) diff --git a/old/api/package.lisp b/old/api/package.lisp new file mode 100644 index 0000000..3a760ef --- /dev/null +++ b/old/api/package.lisp @@ -0,0 +1,6 @@ +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.api + (:use :cl)) + +(in-package #:cl-deck-builder2.api) diff --git a/old/hermetic.lisp b/old/hermetic.lisp new file mode 100644 index 0000000..fe3535b --- /dev/null +++ b/old/hermetic.lisp @@ -0,0 +1,43 @@ +;;;; src/web/hermetic.lisp +;;;; +;;;; demo quick reference +;;;; + +(in-package #:cl-deck-builder2.web) + +(defroute ("/hermetic" :method :GET) () + (if (hermetic:logged-in-p) + (progn + (flash :messages (format nil "Welcome, ~A!" (hermetic:user-name))) + (render-with-env #P"/user/index.html")) + (render-with-env #P"/user/login.html"))) + +(defroute ("/hermetic/login" :method :POST) (&key _parsed) + (let ((username (query-param "username" _parsed)) + (password (query-param "password" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:string username) + (:string password)) + (let ((params (list :|username| username :|password| password))) + (login params + (cl-markup:html5 (:h1 "You are logged in")) + (cl-markup:html5 (:h1 "Wrong password :c")) + (cl-markup:html5 (:h1 "No such username " params))))) + (ratify:combined-error (e) + (flash :errors e) + (redirect "/hermetic"))))) + +(defroute ("/hermetic/logout" :method :GET) () + (logout + (cl-markup:html5 (:h1 "You are logged out")) + (cl-markup:html5 (:h1 "You are not logged in.")))) + +(defroute ("/hermetic/users-only" :method :GET) () + (auth (:user) + (cl-markup:html5 (:h1 "If you are seeing this, you are a user.")))) + +(defroute ("/hermetic/admins-only" :method :GET) () + (hermetic:auth (:admin) + (cl-markup:html5 (:h1 "If you are seeing this, you are an admin.")) + (cl-markup:html5 (:h1 "Custom auth denied page. You are not authorized!")))) diff --git a/old/lack-middleware-secure.lisp b/old/lack-middleware-secure.lisp new file mode 100644 index 0000000..ac9c495 --- /dev/null +++ b/old/lack-middleware-secure.lisp @@ -0,0 +1,168 @@ +;;; +;;; See http://diary.wshito.com/comp/lisp/clack/lack-middleware-session/ +;;; for explanation. +;;; + +(ql:quickload 'clack) +(ql:quickload 'lack) + +(defun starts-with (str prefix) + (when (>= (length str) (length prefix)) + (string= (subseq str 0 (length prefix)) prefix))) + +;;; Middleware to proctect the secure area +;;; :uidãŒè¨­å®šã•ã‚Œã¦ã„ãªã„å ´åˆï¼Œprotected-pathã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã¨ +;;; redirect関数を呼ã³å‡ºã—ã¦ãƒ­ã‚°ã‚¤ãƒ³ãƒšãƒ¼ã‚¸ã¸ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã™ã‚‹ï¼Ž +(defun secure-mw (redirect protected-path) + (lambda (app) + (lambda (env) + ;; preprocessing + (let* ((url (getf env :path-info)) + (session (getf env :lack.session)) + (uid (gethash :uid session))) + (if (and (null uid) + (dolist (prefix protected-path) + (when (starts-with url prefix) (return t)))) + (progn + ;;当åˆã®ã‚¢ã‚¯ã‚»ã‚¹å…ˆã‚’セッション変数ã«ä¿å­˜ + (setf (gethash :prev-url session) url) + (funcall redirect)) + (funcall app env)))))) + +;;; ログインページã¸ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã™ã‚‹ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã‚’è¿”ã™ï¼Ž +(defun redirect-to-login-page () + '(303 (:location "/login") (""))) + +(defun get-uid (env) + (gethash :uid (getf env :lack.session))) + +(defun get-session-id (env) + (getf (getf env :lack.session.options) :id)) + +(defun get-change-id (env) + (format nil "~A" (getf (getf env :lack.session.options) :change-id))) + +(defun page-header (env) + `("

Lack Session Middleware Test

+

--- Login Logout Example ---

+
    +
  • Access any directories. Any directories under '/private' needs to be logged in to access.
  • +
  • Session ID: " ,(get-session-id env) "
  • +
  • :change-id = " ,(get-change-id env) "
  • +
+
")) + +(defun status (uid) + (if uid + `("

You are logged in as " ,uid ". (logout)

") + `("

Login

"))) + +(defun page-footer () + '("")) + +(defun login-form () + ;; /auth ã«uidã¨passwdã‚’POST + '("

Use 'wshito' for username, 'mypass' for password.

+
+

Username: +

+

Password: +

+

+
")) + +;;; ログインページ +(defparameter *login* + (lambda (env) + (let ((uid (get-uid env))) + `(200 (:content-type "text/html") + ,(append (page-header env) + (if uid + (list "

You are already logged in as " uid ".

") + (login-form)) + (page-footer)))))) + +;;; ログアウトページ +(defparameter *logout* + (lambda (env) + (setf (getf (getf env :lack.session.options) :expire) t) + `(200 (:content-type "text/html") + ,(append (page-header env) + (list "

You have logged out.

") + (page-footer))))) + +;;; èªè¨¼é–¢æ•° +(defun authenticate (name password) + (and (string= name "wshito") + (string= password "mypass"))) + +;;; :body-parameters内ã«ã¯POSTã§é€ã‚‰ã‚ŒãŸãƒ‘ラメータãŒï¼Œãƒ‰ãƒƒãƒˆå¯¾ +;;; ã®ãƒªã‚¹ãƒˆã¨ã—ã¦ä¿æŒã•ã‚Œã¦ã„る.ã“ã®å ´åˆã ã¨ï¼Œ +;;; (("uname" . "wshito") ("passwd" . "mypass")) +(defparameter *auth* + (lambda (env) + (let* ((params (getf env :body-parameters)) + (name (cdr (assoc "uname" params :test #'string=))) + (pass (cdr (assoc "passwd" params :test #'string=)))) + (if (and (= (length params) 2) + (authenticate name pass)) + (let* ((session (getf env :lack.session)) + (url (gethash :prev-url session "/"))) + (setf (gethash :uid session "/") name) + (setf (getf (getf env :lack.session.options) :change-id) t) + `(303 (:location ,url) (""))) + (redirect-to-login-page))))) + +;;; ログインãŒå¿…è¦ãªprivateエリア +(defparameter *private* + (lambda (env) + (let* ((session (getf env :lack.session)) + (uid (gethash :uid session nil)) + ;; /privateã«mountã—ã¦ã„ã‚‹ã®ã§pathã«ã¯/privateãŒå«ã¾ã‚Œãªã„ + (path (concatenate 'string "/private" (getf env :path-info)))) + `(200 (:content-type "text/html") + ,(append (page-header env) + (status uid) + (list "

Private Area: path = " path "

") + (page-footer)))))) + +;;; +;;; Main App +;;; +(defparameter *sample-app* + (lambda (env) + (let* ((session (getf env :lack.session)) + (uid (gethash :uid session)) + (path (getf env :path-info))) + (when (null uid) (setf (gethash :prev-url session) path)) + `(200 (:content-type "text/html") + ,(append (page-header env) + (status uid) + (list "

path = " path "

") + (page-footer)))))) + +;;; +;;; Creates Lack Application +;;; builderãƒã‚§ãƒ¼ãƒ³ã®æœ€å¾Œã ã‘ãŒ1é‡lambdaã§ï¼Œãれ以外ã¯2é‡lambda. +;;; builderã•ã‚Œ*app*ã«æ¸¡ã•ã‚Œã‚‹å†…容ã¯å¤–å´ã®lambdaå¼ãŒfuncallã§å‘¼ã³ã ã•ã‚ŒãŸ +;;; 後ã®çµæžœï¼Žå¤–å´ã®lambdaã¯builder時ã«å®Ÿè¡Œã•ã‚Œã‚‹ï¼Ž +(defparameter *app* + (lack:builder + :session + (secure-mw #'redirect-to-login-page '("/private")) + (:mount "/login" *login*) + (:mount "/auth" *auth*) + (:mount "/logout" *logout*) + (:mount "/private" *private*) + *sample-app*)) + +;;; +;;; Starts the Web server +;;; +(defparameter *handler* + (clack:clackup *app*)) + +;;; +;;; Stops the Web server +;;; +;; (clack:stop *handler*) diff --git a/old/readme.md b/old/readme.md new file mode 100644 index 0000000..99252dc --- /dev/null +++ b/old/readme.md @@ -0,0 +1 @@ +old code diff --git a/old/web-route-test.lisp b/old/web-route-test.lisp new file mode 100644 index 0000000..487e355 --- /dev/null +++ b/old/web-route-test.lisp @@ -0,0 +1,34 @@ +;;;; +;;;; src/web/web-test.lisp +;;;; +;;;; This is a test for Lack.Middleware.Mount +;;;; +;;;; Eventually I want all the web routes to be their own package, +;;;; maybe integrated better with the models. +;;;; + +(in-package #:cl-user) + +;; TODO I could probably start moving all this stuff into packages.lisp + +(defpackage #:cl-deck-builder2.web.web-test + (:use :cl + :caveman2) + (:local-nicknames + (#:v #:org.shirakumo.verbose)) + (:import-from :cl-deck-builder2.view + :render) + (:export :*web-test*)) + +(in-package #:cl-deck-builder2.web.web-test) + +;; +;; Application +;; + +(defclass () ()) +(defvar *web-test* (make-instance ')) +(clear-routing-rules *web-test*) + +(defroute "/" () + (format nil "Test!~%")) diff --git a/old/ygoprodeck-json.lisp b/old/ygoprodeck-json.lisp new file mode 100644 index 0000000..adc0bea --- /dev/null +++ b/old/ygoprodeck-json.lisp @@ -0,0 +1,137 @@ +#| + +Legacy Code + +Scheduled For Deletion + +YGOProDeck v1 JSON Importer + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.models.ygoprodeck.json + (:use :cl + :cl-deck-builder2.db + :cl-deck-builder2.models.ygoprodeck) + (:local-nicknames + (#:v #:org.shirakumo.verbose)) + (:export :json-import-cardinfo)) + +(in-package #:cl-deck-builder2.models.ygoprodeck.json) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar *json* nil + "The currently loaded \"cardinfo.php\" data pulled from YGOProDeck +API. You may have to munge it into \"cardinfo.json\" with jq: + + $ jq . < cardinfo.php > cardinfo.json +") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; GET-NEW-CARDINFO got renamed to YGOPRODECK2:FETCH-CARDINFO. +;; Actually, basically everything got moved into YGOPRODECK2. This is +;; legacy code. + + +(defun json-import-cardinfo (path &optional (force nil)) + (load-cardinfo path force) + + (let ((lst ())) + ;; CARD is an ALIST + (dolist (card *json*) + (let (;; Banlist_Info: Unused + ;; (banlist-info (assoc-utils:aget card :banlist--info)) + + ;; Card Images: Variant Artwork + ;; Skip the first one with REST + (card-images (rest (assoc-utils:aget card :card--images))) + + ;; make a copy: we munge it + (card-prices (copy-alist (assoc-utils:aget card :card--prices))) + ;; make a copy: we munge it + (card-sets (copy-alist (assoc-utils:aget card :card--sets))) + ;; Link Markers: Unused + ;; (linkmarkers (assoc-utils:aget card :linkmarkers)) + + ;; make a copy: we munge it + (card-misc-info (copy-alist (assoc-utils:aget card :misc--info))) + + ;; '((:banlist--info) + ;; (:card--images) + ;; (:card--prices) + ;; (:card--sets) + ;; (:linkmarkers) + ;; (:misc--info)) + + (passcode (assoc-utils:aget card :id)) + (base-fields (assoc-utils:alist-plist + (json-cardinfo-base-fields card)))) + + ;; First import all the base data + (push (apply #'make-instance 'ygo-info base-fields) lst) + + ;; Card images, variant artwork, I'm not sure how to handle + ;; it. Here's a dirty hack! If there are any variant images + ;; with passcodes, just create a duplicate entry with the same + ;; data from the parent. I'm sure a separate table is a better + ;; way to handle this information, but I don't know how to do + ;; that right now! + (dolist (card-image card-images) + (let ((id (assoc-utils:aget card-image :id))) + (setf (getf base-fields :passcode) id) + (push (apply #'make-instance 'ygo-info base-fields) lst))) + + ;; TODO Ban List Info + ;; Append :passcode to all banlist-info for this card + ;; (dolist (banlist banlist-info) + ;; (setf banlist (acons :passcode passcode banlist)) + ;; (apply #'mito:create-dao 'banlist-info + ;; (assoc-utils:alist-plist banlist))) + + ;; Append :passcode to all card-prices for this card + (dolist (card-price card-prices) + (setf card-price (acons :passcode passcode card-price) + ;; Data Transformation + (car (assoc :cardmarket--price card-price)) :cardmarket + (car (assoc :tcgplayer--price card-price)) :tcgplayer + (car (assoc :ebay--price card-price)) :ebay + (car (assoc :amazon--price card-price)) :amazon + (car (assoc :coolstuffinc--price card-price)) :coolstuffinc) + (push (apply #'make-instance 'ygo-price (assoc-utils:alist-plist card-price)) + lst)) + + ;; Append :passcode to all card-sets for this card + (dolist (card-set card-sets) + (setf card-set (acons :passcode passcode card-set) + ;; Data Transformation + (car (assoc :set--name card-set)) :name + (car (assoc :set--code card-set)) :code + (car (assoc :set--rarity card-set)) :rarity + (car (assoc :set--rarity--code card-set)) :rarity-code + (car (assoc :set--price card-set)) :price) + (push (apply #'make-instance 'ygo-set (assoc-utils:alist-plist card-set)) + lst)) + + ;; misc_info + misc_info.formats + ;; (dolist (misc-info card-misc-info) + ;; (setf card-set (acons :passcode passcode card-set) + ;; ;; Data Transformation + ;; (car (assoc :set--name card-set)) :name + ;; (car (assoc :set--code card-set)) :code + ;; (car (assoc :set--rarity card-set)) :rarity + ;; (car (assoc :set--rarity--code card-set)) :rarity-code + ;; (car (assoc :set--price card-set)) :price) + ;; (push (apply #'make-instance 'ygo-set (assoc-utils:alist-plist card-set)) + ;; lst)) + + )) + (do-grouped-insert lst))) + +;; TODO linkmarkers +;; Append :passcode to all linkmarkers for this card +;; (dolist (linkmarker linkmarkers) +;; (setf linkmarker (acons :passcode passcode linkmarker)) +;; Data Transformation +;; (apply #'mito:create-dao 'linkmarker +;; (assoc-utils:alist-plist linkmarker))) diff --git a/preamble.lisp b/preamble.lisp new file mode 100644 index 0000000..db7877f --- /dev/null +++ b/preamble.lisp @@ -0,0 +1,17 @@ +#| + +preamble.lisp + +For whatever reason, I don't keep this project in my +~/quicklisp/local-projects directory. So I have this preamble bit +of code that will push your current directory to Quicklisp. + +|# + +;; Assume the project is in the current working directory. It's +;; harmless, probably. +(eval-when (:compile-toplevel :load-toplevel :execute) + (pushnew (uiop:getcwd) ql:*local-project-directories*) + + ;; Load em' up. We use SLY so load SLYNK instead of SWANK here. + (ql:quickload '(:cl-deck-builder2 :slynk))) diff --git a/print-deps.lisp b/print-deps.lisp new file mode 100644 index 0000000..f30b721 --- /dev/null +++ b/print-deps.lisp @@ -0,0 +1,24 @@ +#| + +print-deps.lisp + +Output dependency information extracted from the ASDF:DEFSYSTEM. + +TODO Integrate this with the Dockerfile. + +|# + +(defparameter +extra-depends-on+ + '(:hunchentoot :woo :slynk/trace-dialog :slynk/profiler :slynk/mrepl :slynk/indentation + :slynk/fancy-inspector :slynk/arglists :slynk)) + +(defun format-deps (system &optional (stream *standard-output*) extra-depends-on) + (format stream "(ql:quickload (list ~{~s~^ ~}))~%" (system-deps system extra-depends-on))) + +(defun system-deps (system &optional extra-depends-on) + (mapcar (alexandria:compose #'alexandria:make-keyword #'string-upcase) + (sort (remove-duplicates + (append extra-depends-on + (asdf:system-depends-on + (asdf:find-system system)))) + #'string<))) diff --git a/ruby/config.ru b/ruby/config.ru new file mode 100644 index 0000000..4313c76 --- /dev/null +++ b/ruby/config.ru @@ -0,0 +1,21 @@ +# Easiest way to run Sidekiq::Web. +# Run with "bundle exec rackup simple.ru" + +require 'sidekiq' + +# A Web process always runs as client, no need to configure server +Sidekiq.configure_client do |config| + config.redis = { :size => 1, url: 'redis://localhost:6379', namespace: 'psychiq' } +end + +# In a multi-process deployment, all Web UI instances should share +# this secret key so they can all decode the encrypted browser cookies +# and provide a working session. +# Rails does this in /config/initializers/secret_token.rb +secret_key = SecureRandom.hex(32) +use Rack::Session::Cookie, secret: secret_key, same_site: true, max_age: 86400 + +require "sidekiq/web" +map '/psy' do + run Sidekiq::Web +end diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..be303db --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.fasl diff --git a/src/config.lisp b/src/config.lisp new file mode 100644 index 0000000..3cd1b22 --- /dev/null +++ b/src/config.lisp @@ -0,0 +1,121 @@ +#| + +src/config.lisp + +Configuration skeleton generated by Caveman. + +Additional changes: + +*public-directory* :: This is where YGOProDeck images and things generated *by the "public" are stored. + +Database Configuration: + +We use :MEMORY for SQLite in-memory,currently :MAINDB is the default +database which is an SQLite3 file in this directory, and :MYSQL is our +MariaDB testbed server we're planning on migrating to. + +|# + +(in-package :cl-user) + +(defpackage #:cl-deck-builder2.config + (:use #:cl) + (:import-from #:envy + #:config-env-var + #:defconfig) + (:export #:config + #:*application-root* + #:*public-directory* + #:*static-directory* + #:*template-directory* + + #:*app-log-file* + + #:appenv + #:developmentp + #:productionp) + (:documentation "Configuration package. Using Envy configuration switcher. Mostly default from Caveman2 skeleton generator. I added a bunch of documentation.")) + +(in-package :cl-deck-builder2.config) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setf (config-env-var) "APP_ENV") + +(defparameter *application-root* + (asdf:system-source-directory :cl-deck-builder2) + "The source directory of this web application.") +(defparameter *public-directory* + (probe-file #P"~/public/") + "The absolute path to the directory where we serve public files from.") +(defparameter *static-directory* + (merge-pathnames #P"static/" *application-root*) + "The absolute path to the directory where we serve static files from.") +(defparameter *template-directory* + (merge-pathnames #P"templates/" *application-root*) + "The absolute path to the directory where we find and compile templates from.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *app-log-file* + (merge-pathnames + (make-pathname + :name (local-time:format-timestring + nil (local-time:now) + :format local-time:+rfc3339-format/date-only+) + :type "log") + (merge-pathnames + *public-directory* + (user-homedir-pathname))) + "The log file for this instance of the app to use. We don't really care about conflicts at this point, as we :APPEND. + +Possibly in the future use the git commit id.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configurations +(defconfig :common + `(:databases + ((:memory :sqlite3 :database-name ":memory:") + (:maindb :sqlite3 :database-name + ,(asdf:system-relative-pathname + :cl-deck-builder2 "deck_builder" :type "sqlite3")) + (:mysql :mysql + :host "127.0.0.1" + :port 3306 + :database-name "deck_builder" + :username "deck_builder" + :password "deck_builder")) + :server (:address "0.0.0.0" :port 5005 :server :woo) + :slynk (:interface "0.0.0.0" + :port 4005 + :style :spawn + :dont-close t) + :websocket (:address "0.0.0.0" :port 5001))) + +(defconfig |development| + '()) + +(defconfig |production| + '()) + +(defconfig |test| + '()) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun config (&optional key) + "Query the configuration environment information. + +ARGUMENTS +KEY The key to query from the configuration." + (envy:config #.(package-name *package*) key)) + +(defun appenv () + "Get the current environment variable value. Default is ~APP_ENV~." + (uiop:getenv (config-env-var #.(package-name *package*)))) + +(defun developmentp () + "Return T if the application is in \"development\" mode." + (string= (appenv) "development")) + +(defun productionp () + "Return T if the application is in \"production\" mode." + (string= (appenv) "production")) diff --git a/src/db.lisp b/src/db.lisp new file mode 100644 index 0000000..81ee6b0 --- /dev/null +++ b/src/db.lisp @@ -0,0 +1,295 @@ +#| + +src/db.lisp + +Database / connection skeleton generated by Caveman. + +Additional changes: + +WITH-DATAFLY-CONNECTION :: Same as WITH-CONNECTION, but we use +DATAFLY:*CONNECTION* instead of MITO.CORE:*CONNECTION*. + +WITH-DATAFLY-TRANSACTION is the same. + +TODO: I would like to be able to specify :MITO or :DATAFLY. + +TODO Test this stuff + +DONE Caveman has a db module and dbi manager middleware - Looks +useless. We already have CONNECT-CACHED in db.lisp. + +|# + +(in-package :cl-user) + +(defpackage #:cl-deck-builder2.db + (:use #:cl) + (:import-from #:cl-deck-builder2.config + #:config) + (:import-from #:cl-deck-builder2.toolkit + #:grouped + #:relative-pathname) + (:import-from #:cl-dbi + #:connect-cached) + (:export #:connection-settings + #:db + #:with-connection + #:with-transaction + #:with-datafly-connection + #:with-datafly-transaction + #:with-includes + + #:do-grouped-insert + + #:count-dao + #:create-dao + #:delete-by-values + #:delete-from + #:delete-dao + + #:create-table + #:drop-table + #:recreate-table + + #:find-dao + #:find-or-make-instance + #:find-or-create-instance + #:insert-dao + #:max-dao + #:retrieve-by-sql + #:retrieve-dao + #:save-dao + #:select-dao + #:update-dao) + (:documentation "The database package. + +This is more than just the default generated by Caveman2 skeleton. + +It provides convenience functions for accessing database information and objects.")) + +(in-package #:cl-deck-builder2.db) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun connection-settings (&optional (db :maindb)) + "Query the Caveman configuration for these database settings. DB is the name of the configuration query, e.g. :MAINDB." + (cdr (assoc db (config :databases)))) + +(defun db (&optional (db :maindb)) + "Get a handle to an open database, possibly cached with CONNECT-CACHED." + (apply #'connect-cached (connection-settings db))) + +(defmacro with-connection (conn &body body) + "Wrap BODY using LEXICAL-LET to bind MITO.CORE:*CONNECTION* to CONN." + ;; TODO + ;; `(if (and (listp ,conn) + ;; (eq (type-of ,(car conn)) 'cl-dbi::dbi-connection)) + ;; (let ((,(car conn) ,(cdr conn))) + ;; ,@body)) + `(let ((mito.core:*connection* ,conn)) + ,@body)) + +(defmacro with-transaction (&body body) + "Wrap BODY with CL-DBI:WITH-TRANSACTION." + `(cl-dbi:with-transaction mito.core:*connection* + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; What's a better way to do this? DEFGENERIC? +(defmacro with-datafly-connection (conn &body body) + "Wrap BODY using LEXICAL-LET to bind DATAFLY:*CONNECTION* to CONN." + `(let ((datafly:*connection* ,conn)) + ;; (datafly:*trace-sql* t)) + ,@body)) + +(defmacro with-datafly-transaction (&body body) + "Wrap BODY with CL-DBI:WITH-TRANSACTION. Datafly variant." + `(cl-dbi:with-transaction datafly:*connection* + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; I didn't realize that this implicitly only allows SELECT-DAO. I +;; guess in the MITO documentation he only allows SELECT-DAO to have +;; MITO:INCLUDES machinery. Where is that captured? For now, I assume +;; you know this, and that WITH-INCLUDES is only compatible with +;; SELECT-DAO anyway. +(defmacro with-includes (class includes &body body) + `(mito:select-dao ,class + ,includes + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; What happened here? It used to work now it's returning NIL. A LOOP +;; is nicer than a DOLIST+SETF but... +(defun do-grouped-insert (obj &key (n 1000) (fn #'mito:insert-dao)) + "Batch INSERT-DAO to the database. Display a progress bar." + (let ((pos 0) + (width 50) + (grouped (grouped obj n))) + (with-connection (db) + (loop for group in grouped + collect + (prog1 + (with-transaction + (mapcan fn group)) + (format t ".") + (if (>= pos (1- width)) + (progn + (setf pos 0) + (format t "~%")) + (incf pos)) + (finish-output)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO FIND-DAO-OR-MAKE-INSTANCE? +;; TODO Maybe merge FIND-OR-MAKE-INSTANCE and FIND-OR-CREATE-INSTANCE +(defun find-or-make-instance (class &rest args) + "Wrap MITO:FIND-DAO and if no result is returned, issue MAKE-INSTANCE for CLASS." + (with-connection (db) + (with-transaction + (or (apply #'mito:find-dao class args) + (apply #'make-instance class args))))) + +(defun find-or-create-instance (class &rest args) + "Wrap MITO:FIND-DAO and if no result is returned, issue CREATE-DAO for CLASS." + (with-connection (db) + (with-transaction + (or (apply #'mito:find-dao class args) + (apply #'mito:create-dao class args))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; DAOs +(defmacro select-dao (class &body body) + "Select an object of CLASS from the database by wrapping MITO:SELECT-DAO in WITH-CONNECTION etc. + +Note that SELECT-DAO is a DEFMACRO form. + +You may additionally specify BODY parameters that are SXQL statements." + `(with-connection (db) + (with-transaction + (mito:select-dao ,class ,@body)))) + +(defun delete-from (class &rest clauses) + "Delete entries for CLASS by wrapping SXQL:DELETE-FROM. + +You may additionally specify CLAUSES parameters that are SXQL statements." + (with-connection (db) + (with-transaction + (mito:execute-sql + (apply #'sxql:make-statement :delete-from + (sxql:make-sql-symbol + (mito.class:table-name (find-class class))) + clauses))))) + +(defun count-dao (class &rest fields-and-values) + "Count the number of entries matching this CLASS using FIELDS-AND-VALUES. + +Simply wrap MITO:COUNT-DAO in WITH-CONNECTION etc." + (with-connection (db) + (with-transaction + (apply #'mito:count-dao class fields-and-values)))) + +(defun create-dao (class &rest initargs) + "Create an entry for this CLASS by wrapping MITO:CREATE-DAO in WITH-CONNECTION etc." + (with-connection (db) + (with-transaction + (apply #'mito:create-dao class initargs)))) + +(defun max-dao (class &rest fields-and-values &key (field :id)) + "Select the aggregate MAX from the SQL database and return the result, if any. + +You may additionally specify the FIELD to find the MAX by." + (setf class (mito.util::ensure-class class)) + (let ((sql (sxql:select ((:as (:ifnull (:max field) 0) :max)) + (sxql:from (sxql:make-sql-symbol + (mito.class:table-name class)))))) + (when fields-and-values + (sxql:add-child sql (mito.dao::where-and fields-and-values class))) + (getf (first + (retrieve-by-sql sql)) + :max))) + +(defun delete-by-values (class &rest fields-and-values) + "Delete an entry for this CLASS by wrapping MITO:DELETE-BY-VALUES. + +You may specify additional FIELDS-AND-VALUES to narrow your search." + (with-connection (db) + (with-transaction + (apply #'mito:delete-by-values class fields-and-values)))) + +(defun delete-dao (obj) + (with-connection (db) + (with-transaction + (mito:delete-dao obj)))) + +(defun find-dao (class &rest fields-and-values) + "Find an entry for this CLASS, if it exists, by wrapping MITO:FIND-DAO. + +You may specify additional FIELDS-AND-VALUES to narrow your search." + (with-connection (db) + (with-transaction + (apply #'mito:find-dao class fields-and-values)))) + +(defun insert-dao (obj) + "Insert an existing object OBJ into the database by wrapping MITO:INSERT-DAO in WITH-CONNECTION etc." + (with-connection (db) + (with-transaction + (mito:insert-dao obj)))) + +(defun save-dao (obj) + "Save an existing object OBJ into the database by wrapping MITO:SAVE-DAO in WITH-CONNECTION etc." + (with-connection (db) + (with-transaction + (mito:save-dao obj)))) + +(defun update-dao (obj) + "Update an existing object OBJ into the database by wrapping MITO:UPDATE-DAO in WITH-CONNECTION etc." + (with-connection (db) + (with-transaction + (mito:update-dao obj)))) + +(defun retrieve-by-sql (sql &key binds) + "Retrieve a query from the database by wrapping MITO:RETRIEVE-BY-SQL in WITH-CONNECTION etc. + +I don't know what BINDS does. I think it has something to do with passing parameters to queries." + (with-connection (db) + (with-transaction + (mito:retrieve-by-sql sql :binds binds)))) + +(defun retrieve-dao (class &rest fields-and-values) + "Retrieve a row for this CLASS, if it exists, by wrapping MITO:RETRIEVE-DAO. + +You may specify additional FIELDS-AND-VALUES to narrow your search." + (with-connection (db) + (with-transaction + (apply #'mito:retrieve-dao class fields-and-values)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tables +(defun create-table (&rest classes) + "Create tables for CLASSES by wrapping MITO:ENSURE-TABLE-EXISTS. + +CLASSES may be a list of classes to create corresponding tables for." + (with-connection (db) + (with-transaction + (mapcar (alexandria:compose #'mito:ensure-table-exists #'find-class) + classes)))) + +(defun drop-table (&rest classes) + "Drop tables for CLASSES by wrapping SXQL:DROP-TABLE. + +CLASSES may be a list of classes to drop corresponding tables for." + (with-connection (db) + (with-transaction + (mapcar (lambda (class) + (mito:execute-sql + (sxql:drop-table + (sxql:make-sql-symbol + (mito.class:table-name (find-class class))) + :if-exists t))) + classes)))) + +(defun recreate-table (&rest classes) + "DROP-TABLE then CREATE-TABLE for CLASSES." + (mapcar #'drop-table classes) + (mapcar #'create-table classes)) diff --git a/src/documentation.lisp b/src/documentation.lisp new file mode 100644 index 0000000..d6bef33 --- /dev/null +++ b/src/documentation.lisp @@ -0,0 +1,40 @@ +#| + +src/documentation.lisp + +DOCUMENTATION-UTILS helpers. + +Currently not in use. + +The intention of this file was to provide supplimental documentation. + +|# + +(in-package #:cl-deck-builder2) + +(setf docs.ext:*documentation* + (docs.ext:make-documentation-collection)) + +(defclass my-formatter (documentation-utils-extensions:rich-aggregating-formatter) + ()) + +(docs:define-docs + :formatter my-formatter) + +(defun find-undocumented-packages () + (mapcar + (lambda (pkg) + (documentation-utils:check :package (find-package pkg))) + '(cl-deck-builder2 + cl-deck-builder2-test + cl-deck-builder2.app + cl-deck-builder2.db + cl-deck-builder2.draw + cl-deck-builder2.toolkit + cl-deck-builder2.models + cl-deck-builder2.models.ygoprodeck + cl-deck-builder2.models.user + cl-deck-builder2.models.ydk + cl-deck-builder2.models.crystal-commerce + cl-deck-builder2.models.feedback + cl-deck-builder2.web))) diff --git a/src/draw.lisp b/src/draw.lisp new file mode 100644 index 0000000..8baa184 --- /dev/null +++ b/src/draw.lisp @@ -0,0 +1,277 @@ +#| + +draw.lisp + +The main drawing stuff from ImageMagick is handled here. We use +UIOP:RUN-PROGRAM. It's pretty clumsy, but it works for the types +of files we're working with. + +It could probably use some polishing up. Particularly the command +processing. Using INFERIOR-SHELL just calls UIOP:RUN-PROGRAM under the +hood anyway. Is it worth it for the extra param processing? It might +be slightly safer, certainly LISP-ier... + +|# +(in-package :cl-user) + +(defpackage #:cl-deck-builder2.draw + (:use #:cl) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:import-from #:cl-deck-builder2.toolkit + #:make-static-filename-kind + #:relative-file-list + #:ygo-probe-file-list + #:static-probe-file-list) + (:import-from #:cl-deck-builder2.models + #:ydk-main-deck-of + #:ydk-extra-deck-of + #:ydk-side-deck-of) + (:export :draw-deck-image) + (:documentation "The Drawing package. + +We currently wrap ImageMagick on the command line using UIOP:RUN-PROGRAM. + +Cairo2 does not support JPEG. We generate a command pipeline with ~montage~ and ~convert~ binaries.")) + +(in-package #:cl-deck-builder2.draw) + +;; Rough class abstraction: +(defclass draw-pipeline () + ()) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *command* nil + "The current command line used in the drawing process. Useful for inspection/debug purposes.") + +(defparameter +convert-binary+ "convert" + "The path to ImageMagick ~convert~ binary.") + +(defparameter +montage-binary+ "montage" + "The path to ImageMagick ~montage~ binary.") + +;; This is for full deck list. I can have a few params for different +;; decks e.g. main, side, extra. +;; +;; TODO Having all these params is ugly. Maybe a hash table? +(defparameter +command-line-params-main-deck+ + ;;"-resize '421x614>' -geometry +5+5 -tile 10x") + ; "-geometry +5+5 -tile 10x -texture '/home/user/public/noise.png'" ;;'#ff8b53'" + "-geometry +5+5 -tile 10x -background '#FF8B53'" + "Parameters for making the MAIN deck. It's 10x cards wide, There's 5 px between cards. The color is from . + +This is for full main deck list. I can have a few params for different decks e.g. main, side, extra. This one is for the main deck. Parameters for making the MAIN deck: It's 10x cards wide,There's 5 px between cards.") + +(defparameter +command-line-params-extra-deck+ + ;; "-resize '421x614>' -geometry +5+5 -tile 15x") + "-geometry +5+5 -tile 15x -background '#BC5A84'" + "Parameters for making the EXTRA deck. It's 15x cards wide,There's 5 px between cards.") + +(defparameter +command-line-params-side-deck+ + ;; "-resize '421x614>' -geometry +5+5 -tile 15x") + "-geometry +5+5 -tile 15x -background '#1D9E74'" + "Parameters for making the SIDE deck. It's 15x cards wide,There's 5 px between cards.") + +(defparameter +command-line-params-main-deck-output+ + ;; "-resize '421x614>' -geometry +5+5 -tile 15x") + ;; "-geometry '4310x2496>'" + ;; "-geometry '2560x>' -background '#FF8B53' -gravity South -splice 0x10" + "-background '#FF8B53' -gravity South -splice 0x10" + "Parameters for final output of the MAIN deck. It's the width of the MAIN deck output, which will be set to 2560. + +TODO: Use identify to query this information.") + +(defparameter +command-line-params-extra-deck-output+ + ;; "-resize '421x614>' -geometry +5+5 -tile 15x") + ;; "-geometry '4310x2496>'" + "-geometry '4310>'" + "Parameters for final output of the EXTRA and SIDE decks. It's exactly the width of the MAIN deck output, which happens to be 4310x2496. + +TODO: Use identify to query this information. + +Parameters for final output of the EXTRA and SIDE decks. It's exactly the width of the MAIN deck output, which happens to be 4310x2496. Card images are almost always 421x614 px in size, and we add 5 px of padding around all sides. So that's where this number comes from (((421+10)*10) = 4310).") + +(defparameter +command-line-params-final-output+ + ;; "-resize '421x614>' -geometry +0+0 -tile 1x") + "-geometry +0+0 -tile 1x -background none" + "The options passed to convert for the final image result. Smoosh everything together into one 1x wide image!") + +(defparameter +command-line-params-watermark-lower-right+ + "-gravity southeast -geometry +10+10 -draw \"image Over 32,32 256,256 'runew.png'\"" + "Draw a watermark in the bottom right with this command.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun command-setup (file-list pathname &key (deck-type :main) (dry-run nil)) + "Set up the *COMMAND* parameter for use in COMMAND-EXEC. + +You can kind of see the structure of the command: + + ~A ~A ~{\"~A\" ~} miff:- | ~A miff:- ~A \"~A\" + +That's \"montage [input-options] [list-of-input-filenames] miff:- | convert miff:- [output-options] [output-filename]\". + +We use the MIFF format to speed up the whole montage process. + +ARGUMENTS: + +FILE-LIST The list of files to be used in the generation of the deck image. The files must exist. +PATHNAME The pathname of the output file. +DECK-TYPE The kind of deck we'll be generating. Allowed values are :MAIN :SIDE :EXTRA and :FINAL. The default is :MAIN. +DRY-RUN If DRY-RUN is T, instead of filling in a whole file list, which can be clunky to view, we replace the list of files with ~'(FILE LIST)~. Very kludgy. + +SEE *COMMAND* +SEE COMMAND-EXEC" + (let* ((extra-params (case deck-type + (:main +command-line-params-main-deck+) + (:extra +command-line-params-extra-deck+) + (:side +command-line-params-side-deck+) + (:final +command-line-params-final-output+) + (otherwise ""))) + (output-params (case deck-type + (:main +command-line-params-main-deck-output+) + ((:side :extra) +command-line-params-extra-deck-output+) + (otherwise ""))) + (command (format nil "~A ~A ~{\"~A\" ~} miff:- | ~A miff:- ~A \"~A\"" + +montage-binary+ + extra-params + (if dry-run + '(file list) + file-list) + +convert-binary+ + output-params + pathname))) + (setf *command* command))) + +(defun command-exec (&key (command *command*) (dry-run nil)) + "Execute the command with UIOP:RUN-PROGRAM, or do a dry run. + +ARGUMENTS: +COMMAND The command line to execute. It doesn't have to be anything to do with ImageMagick. This is a generic wrapper around UIOP:RUN-PROGRAM. The default command line to pass is *COMMAND*. +DRY-RUN If DRY-RUN is T, use FORMAT to output a string instead." + (if dry-run + (v:info :draw "~a" command) + (uiop:run-program command + :output t + :error-output t))) + + +(defun identify-image (pathname &optional (dry-run nil)) + "Wrapper around ImageMagick IDENTIFY. + +PATHNAME The path to the file to identify. +DRY-RUN Inform COMMAND-EXEC that this is a DRY-RUN. " + (command-exec :command (format nil "identify \"~A\"" pathname) + :dry-run dry-run)) + +;; TODO The way we pass args around like this is ugly af. Maybe a STRUCT? +;; +;; This absolutely needs a rewrite. I should be using classes for this. +(defun make-passcode-files-list (types pathname-name ydk) + "Build TYPES -> MERGE-PATHNAMES ASSOCIATION-LIST. TYPES gets filled in +with PATHNAME-NAME and PASSCODE info: + + (:MAIN OG_bechi_21-Chaos Turbo-main.jpg ( main deck ... )) + (:SIDE OG_bechi_21-Chaos Turbo-side.jpg ( side deck ... )) + (:EXTRA OG_bechi_21-Chaos Turbo-extra.jpg ( extra deck ... )) + +Then the :FINAL image instead of a list of passcodes, is a list of +files passed to STATIC-RELATIVE-FILE-LIST: + + (:FINAL OG_bechi_21-Chaos Turbo-final.jpg (OG_bechi_21-Chaos Turbo-main.jpg + OG_bechi_21-Chaos Turbo-extra.jpg + OG_bechi_21-Chaos Turbo-side.jpg)) + +ARGUMENTS: +types A LIST of the types of images we want to generate. May be one of :MAIN, :SIDE, :EXTRA, or :FINAL. +pathname-name The PATHNAME-NAME of the file you want to create. E.g. if you had a YDK file named \"Chaos Turbo 2023.ydk\" you should pass the output of (PATHNAME-NAME #P\"Chaos Turbo 2023.ydk\" +ydk The YDK information we're generating the deck for." + (loop for type in types + collect + (list type + (make-static-filename-kind pathname-name type) + (case type + (:main (ydk-main-deck-of ydk)) + (:extra (ydk-extra-deck-of ydk)) + (:side (ydk-side-deck-of ydk)))))) + +(defun draw-deck-image (ydk pathname &key (dry-run nil)) + "Parse a YDK, setup a bunch of command lines, and generate a deck image. Very clunky and clumsy, most likely candidate for a rewrite right here. + +ARGUMENTS +DATA Raw YDK data. May be anything that INITIALIZE-WITH-CONTENT can process. +PATHNAME The path to where the output images will be stored. The PATHNAME-NAME will be munged to have ~-main~,~-side~,~-extra-~, or ~-final~ appended to it. +DRY-RUN Do a dry run. + +SEE INITIALIZE-WITH-CONTENT" + + (v:info :draw "DRAW-DECK-IMAGE: ~a" pathname) + + (let* ((pathname-name (typecase pathname + (pathname (pathname-name pathname)) + (string pathname) + (integer (princ-to-string pathname)))) + (datum (make-passcode-files-list '(:main :extra :side) + pathname-name ydk))) + + ;; Build the three images + (loop for data in datum do + (destructuring-bind (kind path lst) + data + (if (not lst) + (progn + ;; Whatever item this is, there are no cards in + ;; it. Remove it from the query. + ;; (format t "~A~%" datum) + (setf datum (delete kind datum :key #'car))) + (progn + ;; There are cards in it. + (v:info :draw "Generating ~A deck: ~A (~A cards)" kind path (length lst)) + (if (probe-file path) + (v:info :draw "File exists: ~A" path) + (progn + (command-setup (ygo-probe-file-list lst) path :deck-type kind :dry-run dry-run) + (command-exec :dry-run dry-run))))))) + + (let* (;; KEY-PAIRS is :MAIN + (:MAIN :EXTRA:) + (:MAIN :SIDE) if they exist. + ;; If we filtered out something above, it won't show up in this list... + (key-pairs (alexandria:map-product 'list + '(:main) + (remove :main (mapcar #'first datum)))) + ;; COMBINATIONS-LIST is every file name combined, + ;; e.g. Deck-Name-MAIN, Deck-Name-MAIN-EXTRA, + ;; Deck-Name-FINAL; the ones from KEY-PAIRS noted above + ;; combined with PATHNAME-NAME. It's in a similar format as + ;; the output from MAKE-PASSCODE-FILES-LIST. + (combinations-list + (loop for key-pair in key-pairs + collect + (list + ;; double loop ... ? Who cares, it works... + (loop for key-pair in key-pair + collect (make-static-filename-kind pathname-name key-pair)) + (make-static-filename-kind pathname-name key-pair)))) + (final-filenames + (append + (mapcar #'second datum) + (mapcar #'second combinations-list) + (list (make-static-filename-kind pathname-name :final))))) + + (merge-images (car (last final-filenames)) (mapcar #'second datum)) + (loop for combination in combinations-list do + (merge-images (second combination) (first combination) dry-run)) + + (relative-file-list final-filenames)))) + +(defun merge-images (final-filename image-lst &optional dry-run) + "Merge images in IMAGE-LST into FINAL-FILENAME. + +IMAGE-LST The list of images to be merged. Will be merged in order specified. +FINAL-FILENAME The output filename. +DRY-RUN Do a dry run." + (v:info :draw "Generating final image: ~A ~a" final-filename image-lst) + ;; MAPCAR #'SECOND??? Oh Well, it works. + (if (probe-file final-filename) + (v:info :draw "File exists: ~A" final-filename) + (progn + (command-setup (static-probe-file-list image-lst) + final-filename :deck-type :final :dry-run dry-run) + (command-exec :dry-run dry-run)))) diff --git a/src/i18n.lisp b/src/i18n.lisp new file mode 100644 index 0000000..7021e67 --- /dev/null +++ b/src/i18n.lisp @@ -0,0 +1,146 @@ +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.i18n + (:use :cl) + (:import-from :gettext #:*current-locale*) + (:export + #:_ + #:n_ + #:reload-translations + #:list-loaded-locales + #:*current-locale* + #:set-locale + #:with-locale + #:update-djula.pot + #:generate-templates-list) + (:documentation "Internationalization Utilities")) + +(in-package #:cl-deck-builder2.i18n) + +(defun setup-gettext () + (setf djula:*translation-backend* :gettext + (gettext:textdomain) "cl-deck-builder2") + (gettext:setup-gettext #.*package* "cl-deck-builder2")) + +(setup-gettext) + +(defun reload-translations () + (format *debug-io* "~%Reading all *.mo files...") + ;; Clear gettext's cache + (clrhash gettext::*catalog-cache*) + (setup-gettext) + (gettext:preload-catalogs + ;; Tell gettext where to find the .mo files + #.(asdf:system-relative-pathname :cl-deck-builder2 "locale/")) + ;; What about this? + (setf djula::*gettext-domain* "cl-deck-builder2")) + +;; Only preload the translations into the image if we're not deployed yet. +(unless (deploy:deployed-p) + (reload-translations)) + +;; Run this when developping to reload the translations +#+ (or) +(progn + ;; Clear gettext's cache + (clrhash gettext::*catalog-cache*) + (gettext:preload-catalogs + ;; Tell gettext where to find the .mo files + #.(asdf:system-relative-pathname :cl-deck-builder2 "locale/"))) + +;; Run this to see the list of loaded message for a specific locale +#+ (or) +(gettext::catalog-messages + (gethash '("es_ES" :LC_MESSAGES "cl-deck-builder2") + gettext::*catalog-cache*)) + +;; Test the translation of a string +#+ (or) +(with-locale ("es_ES") + (_ "Please login to continue")) + + +#+ (or) +(set-locale "es_ES") + +#+ (or) +*current-locale* + +(defun list-loaded-locales () + "Get the list of locales loaded in gettext's cache." + (remove-duplicates + (mapcar #'first + (alexandria:hash-table-keys + gettext::*catalog-cache*)) + :test #'string=)) + +(defun set-locale (locale) + "Setf gettext:*current-locale* and djula:*current-language* if LOCALE seems valid." + ;; It is valid to set the locale to nil. + (when (and locale + (not (member locale (list-loaded-locales) + :test 'string=))) + (error "Locale not valid or not available: ~s" locale)) + (setf *current-locale* locale + djula:*current-language* locale)) + +(defmacro with-locale ((locale) &body body) + "Calls BODY with gettext:*current-locale* and djula:*current-language* set to LOCALE." + `(let (*current-locale* djula:*current-language*) + (set-locale ,locale) + ,@body)) + +;; (trace _) + +;; (trace djula:translate gettext:gettext* gettext::lookup) + +;; (_ "hi") + +#| +This could technically be just +(mapcan #'djula.locale:file-template-translate-strings + (djula:list-asdf-system-templates :cl-deck-builder2 "templates")) + +But I (fstamour) made it just a bit more complex in order to keep track of the source (just the +filename) of each translatable strings. Hence why the hash-table returned is named `locations`. +|# +(defun extract-translate-strings () + "Extract all {_ ... _} string from the djula templates." + (loop + :with locations = (make-hash-table :test 'equal) + :for path :in (djula:list-asdf-system-templates :cl-deck-builder2 "templates") + :for strings = (djula.locale:file-template-translate-strings path) + :do (loop :for string :in strings + :unless (gethash string locations) + :do (setf (gethash string locations) path)) + :finally (return locations))) + +(defun update-djula.pot () + "Update djula.pot from *.html files." + (with-open-file (s (asdf:system-relative-pathname + :cl-deck-builder2 "locale/templates/LC_MESSAGES/djula.pot") + :direction :output + :if-exists :supersede + :if-does-not-exist :create) + (let* ((locations (extract-translate-strings)) + (strings (alexandria:hash-table-keys locations))) + (loop + :for string :in strings + :for location = (gethash string locations) + :do + (format s "~%#: ~a~%#, lisp-format~%msgid ~s~%msgstr \"\" ~%" + (enough-namestring + location + (asdf:system-source-directory :cl-deck-builder2)) + string))))) + +;; TODO UPDATE-STATIC-TEMPLATE-FILES +(defun generate-templates-list () + "Generate a list of all files in the \"templates/\" subdirectory, in the form of (:STATIC-FILE \"file...\"). Currently you have to copy/paste this into the ASDF file whenever you add a template." + (let* ((lst '()) + (root (asdf:system-relative-pathname :cl-deck-builder2 "templates/"))) + (cl-fad:walk-directory + root + (lambda (name) + (push (list :static-file (namestring (uiop:enough-pathname name root))) lst))) + lst)) diff --git a/src/main.lisp b/src/main.lisp new file mode 100644 index 0000000..4871053 --- /dev/null +++ b/src/main.lisp @@ -0,0 +1,112 @@ +;;;; src/main.lisp +;;;; +;;;; Main App Definition And Entry Point +;;;; + +(in-package :cl-user) + +(defpackage #:cl-deck-builder2 + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.draw + #:cl-deck-builder2.toolkit + #:cl-deck-builder2.models + ;;#:cl-deck-builder2-test + ) + (:local-nicknames + (#:v #:org.shirakumo.verbose)) + (:import-from :cl-deck-builder2.config + :config + :*app-log-file*) + (:import-from :clack + :clackup) + (:export + :start + :stop + :my/start + :main) + (:documentation "The main package for the deck builder project. + +This package exports the START and STOP functions, as well as the convenience function MY/START.")) + +(in-package :cl-deck-builder2) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar *appfile-path* + (asdf:system-relative-pathname :cl-deck-builder2 #P"app.lisp") + "The application path we pass to CLACK:CLACKUP.") + +(defvar *clack-handler* nil + "The CLACK handler currently running. NIL if there is no server running.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun start (&rest args &key server port debug &allow-other-keys) + "Start the app. If the app is already running, raise an ERROR." + (declare (ignore server port debug)) + (when *clack-handler* + (restart-case (error "Server is already running.") + (restart-server () + :report "Restart the server" + (stop)))) + (v:info :main "CLACKUP") + (setf *clack-handler* (apply #'clackup *appfile-path* args))) + +(defun stop () + "Stop the app, if it is running." + (v:info :main "CLACK:STOP") + (prog1 + (clack:stop *clack-handler*) + (setf *clack-handler* nil))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun my/start (&rest args) + "Convenience function for invoking RESTART-SERVER in the case that the server is already running." + (handler-bind + ((simple-error (lambda (c) + (format t "~A~&Invoking RESTART-SERVER." c) + (invoke-restart 'restart-server)))) + ;; TODO Multiple Addresses? + ;; TODO Put it behind NGINX? + ;; TODO sslh? + (apply #'start args))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Loader Customize +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro start-helper (service-name &body body) + `(progn + (v:info :app "~&Starting ~a ...~&" ,service-name) + (if ,@body + (v:info :app "~&... started.~&") + (v:info :app "~&... failed?~&")))) + +;; Initialize Logging +;; +;; (v:output-here *standard-output*) +;; +(defun main () + (v:output-here (open *app-log-file* :direction :output + :if-does-not-exist :create + :if-exists :append)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (v:start v:*global-controller*) + + (start-helper "slynk server" + (apply #'slynk:create-server (config :slynk))) + + (v:info :app "~&Constructing database...~&") + ;; (apply #'create-table *class-list*) + (ensure-tables-exist) + (v:info :app "~&...complete~%") + + (start-helper "web app" + (apply #'my/start (config :server))) + + (start-helper "WebSocket Server Side Client" + (handler-case + (apply #'cl-deck-builder2.web::make-chat-client (config :websocket)) + (usocket:connection-refused-error () nil) + (usocket:connection-reset-error () nil)))) diff --git a/src/models/attachment.lisp b/src/models/attachment.lisp new file mode 100644 index 0000000..7462135 --- /dev/null +++ b/src/models/attachment.lisp @@ -0,0 +1,46 @@ +#| + +# File Attachments + +Attachment class for saving metadata into RDBMS + +See MITO-ATTACHMENT for more information: + +TODO AWS Storage / CC API Upload? + +Here's the bit of code for the FILEs: + +;; (destructuring-bind (content filename content-type) +;; file +;; (if (and (eq (type-of content) 'flex::vector-input-stream) +;; (> (length (flex::vector-stream-vector content)) 0)) +;; ...)) + +|# + +(in-package #:cl-deck-builder2.models.attachment) + +(defclass attachment (mito-attachment:attachment) () + (:metaclass registered-table-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun create-attachment (content file-key content-type) + "Use CREATE-DAO to construct a new ATTACHMENT instance. Return the instance if successful, NIL otherwise." + (create-dao 'attachment + :content content + :file-key file-key + :content-type content-type)) + +(defun attachment-valid-p (file) + "Helper function. Ensure FILE data from Caveman has valid: + +- Type: 'FLEX::VECTOR-INPUT-STREAM +- Filename: Length > 0 +- File Length: Content Length > 0" + (destructuring-bind (content filename content-type) + file + ;; XXX Don't ignore content type? + (declare (ignore content-type)) + (and (eq (type-of content) 'flex::vector-input-stream) + (> (length (flex::vector-stream-vector content)) 0) + (> (length filename) 0)))) diff --git a/src/models/category-2.lisp b/src/models/category-2.lisp new file mode 100644 index 0000000..370b4b0 --- /dev/null +++ b/src/models/category-2.lisp @@ -0,0 +1,198 @@ +#| + +src/models/category-2.lisp + +Category v2 + +Based on DB_NestedSet + +It looks like keepAsArray controls whether or not it outputs a list or +something passed through MAKE-INSTANCE. + +|# + +(defpackage #:cl-deck-builder2.models.category-2 + (:use #:cl)) + +(:in-package #:cl-deck-builder2.models.category-2) + +;; CLASSES + + +#| +# The table which holds the structure +CREATE TABLE tb_nodes ( + STRID int(11) NOT NULL auto_increment, + ROOTID int(11) NOT NULL default '0', + l int(11) NOT NULL default '0', + r int(11) NOT NULL default '0', + PARENT int(11) NOT NULL default '0', + STREH int(11) NOT NULL default '0', + LEVEL int(11) NOT NULL default '0', + STRNA char(128) NOT NULL default '', + PRIMARY KEY (STRID), + KEY ROOTID (ROOTID), + KEY STREH (STREH), + KEY l (l), + KEY r (r), + KEY LEVEL (LEVEL), + KEY SRLR (ROOTID,l,r), + KEY parent (PARENT) +) TYPE=MyISAM COMMENT='NestedSet table'; +|# + +(defclass db-nested-set-node (registered-table-class) + ((root-id :col-type :integer :default 1) + (left :col-type :integer :default 1) + (right :col-type :integer :default 1) + (parent :col-type :integer :default 1)) + ;; (streh :col-type :integer :default 0) + ;; (level :col-type :integer :default 0) + ;; (strna :col-type (:varchar 128) :default "") + (:documentation "Class for DB_NestedSet Nodes.")) + +(defmethod closer-mop:validate-superclass ((class db-nested-set-node) + (superclass registered-table-class)) + t) + + + +#| +# A table which is used for a little table locking to avoid conflicts +CREATE TABLE tb_locks ( + lockID char(32) NOT NULL default '', + lockTable char(32) NOT NULL default '', + lockStamp int(11) NOT NULL default '0', + PRIMARY KEY (lockID,lockTable) +) TYPE=MyISAM COMMENT='Table locks for NestedSet'; +|# + +(defclass db-nested-set-lock () + (;; TODO Being able to switch the table name would be cool. With + ;; MITO It would be a class. I'm not sure how to code that. + (lock-table :col-type (:varchar 32)) + (lock-stamp :col-type :timestamp + :initform (local-time:now) + :inflate #'local-time:universal-to-timestamp + :deflate #'local-time:timestamp-to-universal)) + (:metaclass registered-table-class) + (:documentation "Class for DB_NestedSet Locks Table.")) + +;; Helper Class +(defclass db-nested-set () + ((db-nested-set-nodes :accessor nested-set-nodes + :initform '()) + (db-nested-set-locks :accessor nested-set-nodes + :initform '()))) + +;; GENERICS + +(defgeneric create-root-node (obj)) +(defgeneric create-left-node (obj)) +(defgeneric create-right-node (obj)) +(defgeneric create-sub-node (obj)) + +(defgeneric delete-node (obj)) + +(defgeneric get-all-nodes (obj)) +(defgeneric get-root-nodes (obj)) + +(defgeneric pick-node (obj)) + +(defgeneric update-node (obj)) + +(defgeneric get-branch (obj)) +(defgeneric get-children (obj)) +(defgeneric get-parent (obj)) +(defgeneric get-parents (obj)) +(defgeneric get-siblings (obj)) +(defgeneric get-sub-branch (obj)) + +(defgeneric is-parent (obj)) + +(defgeneric move-across (obj)) +(defgeneric move-cleanup (obj)) +(defgeneric move-root-to-root (obj)) +(defgeneric move-tree (obj)) + +;; METHODS + +(defmethod create-root-node ((obj db-nested-set))) +(defmethod create-left-node ((obj db-nested-set))) +(defmethod create-right-node ((obj db-nested-set))) +(defmethod create-sub-node ((obj db-nested-set))) + +(defmethod delete-node ((obj db-nested-set))) + +(defmethod get-all-nodes ((obj db-nested-set))) +(defmethod get-root-nodes ((obj db-nested-set))) + +(defmethod pick-node ((obj db-nested-set))) + +(defmethod update-node ((obj db-nested-set))) + +(defmethod get-branch ((obj db-nested-set))) +(defmethod get-children ((obj db-nested-set))) +(defmethod get-parent ((obj db-nested-set))) +(defmethod get-parents ((obj db-nested-set))) +(defmethod get-siblings ((obj db-nested-set))) +(defmethod get-sub-branch ((obj db-nested-set))) + +(defmethod is-parent ((obj db-nested-set))) + +(defmethod move-across ((obj db-nested-set))) +(defmethod move-cleanup ((obj db-nested-set))) +(defmethod move-root-to-root ((obj db-nested-set))) +(defmethod move-tree ((obj db-nested-set))) + +#| +@$this->cache->flush('function_cache'); +function & factory($driver, $dsn, $params = array()) { +function DB_NestedSet($params) { +function _DB_NestedSet() { +function _addSQL($addSQL, $type, $prefix = false) { +function _debugMessage($msg) { +function _getMessage($code) { +function _getSelectFields($aliasFields) { +function _lockGC() { +function _moveAcross($source, $target, $pos, $first = false) { +function _moveCleanup($copy = false) { +function _moveRoot2Root($source, $target, $pos) { +function _processResultSet($sql, $keepAsArray, $fieldsAreAliased) { +function _raiseError($code, $mode, $option, $epr = array()) { +function _releaseLock($exclusive = false) { +function _secSort($nodeSet) { +function _secSortCollect($segment, $deepArray, $reset = false) { +function _setLock($exclusive = false) { +function _testFatalAbort($errobj, $file, $line) { +function _values2InsertQuery($values, $addval = false) { +function _values2UpdateQuery($values, $addval = false) { +function _verifyUserValues($caller, & $values) { +function addListener($event, & $listener) { +function apiVersion() { +function convertTreeModel(& $orig, & $copy, $_parent = false) { +function createLeftNode($id, $values) { +function createRightNode($id, $values) { +function createRootNode($values, $id = false, $first = false, $pos = NESE_MOVE_AFTER) { +function createSubNode($id, $values) { +function deleteNode($id) { +function getAllNodes($keepAsArray = false, $aliasFields = true, $addSQL = array()) { +function getBranch($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) { +function getChildren($id, $keepAsArray = false, $aliasFields = true, $forceNorder = false, $addSQL = array()) { +function getParent($id, $keepAsArray = false, $aliasFields = true, $addSQL = array(), $useDB = true) { +function getParents($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) { +function getRootNodes($keepAsArray = false, $aliasFields = true, $addSQL = array()) { +function getSiblings($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) { +function getSubBranch($id, $keepAsArray = false, $aliasFields = true, $addSQL = array()) { +function isParent($parent, $child) { +function moveTree($id, $targetid, $pos, $copy = false) { +function pickNode($id, $keepAsArray = false, $aliasFields = true, $idfield = 'id', $addSQL = array()) { +function removeListener($event, $listenerID) { +function setAttr($attr) { +function setDbOption($option, $val) { +function setsortMode($sortMode = false) { +function testLock() { +function triggerEvent($event, & $node, $eparams = false) { +function updateNode($id, $values, $_internal = false) { +register_shutdown_function(array(& $this, '_DB_NestedSet')); +|# diff --git a/src/models/category.lisp b/src/models/category.lisp new file mode 100644 index 0000000..6620f28 --- /dev/null +++ b/src/models/category.lisp @@ -0,0 +1,877 @@ +#| + +src/models/category.lisp + +TODO All of this needs a serious rewrite. + +|# + +(in-package #:cl-deck-builder2.models.category) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defclass category () + ((name :accessor name-of + :col-type (:varchar 64) + :initarg :name) + (left :accessor category-left-of + :col-type :integer + :initarg :left) + (right :accessor category-right-of + :col-type :integer + :initarg :right)) + (:metaclass registered-table-class) + (:unique-key name) + (:documentation "Category implementation based on Joe Celko's Nested Set Hierarchy from one of his books.")) + +(defun create-category (name left right) + (create-dao 'category :name name + :left left + :right right)) + +;; (defmacro category (&body body) +;; `(select-dao 'category ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod category-rename ((category category) new-name) + (setf (category-name-of category) new-name) + (update-dao category)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; Joe Celko Nested Set Hierarchy +;;;; +;;;; +;;;; +;;;; * The Nested Set Model +;;;; +;;;; #+BEGIN_SRC sql +;;;; CREATE TABLE nested_category ( +;;;; category_id INT AUTO_INCREMENT PRIMARY KEY, +;;;; name VARCHAR(20) NOT NULL, +;;;; lft INT NOT NULL, +;;;; rgt INT NOT NULL +;;;; ); +;;;; +;;;; INSERT INTO nested_category VALUES +;;;; (1,'ELECTRONICS',1,20), +;;;; (2,'TELEVISIONS',2,9), +;;;; (3,'TUBE',3,4), +;;;; (4,'LCD',5,6), +;;;; (5,'PLASMA',7,8), +;;;; (6,'PORTABLE ELECTRONICS',10,19), +;;;; (7,'MP3 PLAYERS',11,14), +;;;; (8,'FLASH',12,13), +;;;; (9,'CD PLAYERS',15,16), +;;;; (10,'2 WAY RADIOS',17,18); +;;;; +;;;; SELECT * FROM nested_category ORDER BY category_id; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-insert-fake-data () + "Insert Fake Data. These LEFT-IDs will mess with your stuff... + +TODO Figure out how to fix that." + (with-connection (db) + (with-transaction + (mapcar (lambda (values-list) + (destructuring-bind (name left right) + values-list + (mito:create-dao 'category + :name name + :left left + :right right))) + '(("ELECTRONICS" 1 20) + ("TELEVISIONS" 2 9) + ("TUBE" 3 4) + ("LCD" 5 6) + ("PLASMA" 7 8) + ("PORTABLE ELECTRONICS" 10 19) + ("MP3 PLAYERS" 11 14) + ("FLASH" 12 13) + ("CD PLAYERS" 15 16) + ("2 WAY RADIOS" 17 18)))))) + +;;;; #+END_SRC +;;;; +;;;; +;;;; +-------------+----------------------+-----+-----+ +;;;; | category_id | name | lft | rgt | +;;;; +-------------+----------------------+-----+-----+ +;;;; | 1 | ELECTRONICS | 1 | 20 | +;;;; | 2 | TELEVISIONS | 2 | 9 | +;;;; | 3 | TUBE | 3 | 4 | +;;;; | 4 | LCD | 5 | 6 | +;;;; | 5 | PLASMA | 7 | 8 | +;;;; | 6 | PORTABLE ELECTRONICS | 10 | 19 | +;;;; | 7 | MP3 PLAYERS | 11 | 14 | +;;;; | 8 | FLASH | 12 | 13 | +;;;; | 9 | CD PLAYERS | 15 | 16 | +;;;; | 10 | 2 WAY RADIOS | 17 | 18 | +;;;; +-------------+----------------------+-----+-----+ +;;;; +;;;; * Retrieving a Full Tree +;;;; +;;;; We can retrieve the full tree through the use of a self-join that +;;;; links parents with nodes on the basis that a node’s lft value will +;;;; always appear between its parent’s lft and rgt values: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT node.name +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND parent.name = 'ELECTRONICS' +;;;; ORDER BY node.lft; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-full-tree (fields name) + (retrieve-by-sql + (sxql:select fields + (sxql:from (:as :category :node) + (:as :category :parent)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (:= :parent.name name))) + (sxql:order-by :node.left)))) + +;;;; #+END_SRC +;;;; +;;;; +----------------------+ +;;;; | name | +;;;; +----------------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | PORTABLE ELECTRONICS | +;;;; | MP3 PLAYERS | +;;;; | FLASH | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; +----------------------+ +;;;; +;;;; * Finding all the Leaf Nodes +;;;; +;;;; Finding all leaf nodes in the nested set model even simpler than the +;;;; LEFT JOIN method used in the adjacency list model. If you look at the +;;;; nested_category table, you may notice that the lft and rgt values for +;;;; leaf nodes are consecutive numbers. To find the leaf nodes, we look +;;;; for nodes where rgt = lft + 1: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT name +;;;; FROM nested_category +;;;; WHERE rgt = lft + 1; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-leaf-nodes () + "Finding all the Leaf Nodes" + (select-dao 'category + (sxql:where (:= :right (sxql:make-op :+ :left 1))))) + + +;;;; #+END_SRC +;;;; +;;;; +--------------+ +;;;; | name | +;;;; +--------------+ +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | FLASH | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; +--------------+ +;;;; +;;;; * Retrieving a Single Path +;;;; +;;;; With the nested set model, we can retrieve a single path without +;;;; having multiple self-joins: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT parent.name +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND node.name = 'FLASH' +;;;; ORDER BY parent.lft; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-path-of (name) + (retrieve-by-sql + (sxql:select :parent.name + (sxql:from (:as :category :node) + (:as :category :parent)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (:= :node.name name))) + (sxql:order-by :parent.left)))) + + +;;;; #+END_SRC +;;;; +;;;; +----------------------+ +;;;; | name | +;;;; +----------------------+ +;;;; | ELECTRONICS | +;;;; | PORTABLE ELECTRONICS | +;;;; | MP3 PLAYERS | +;;;; | FLASH | +;;;; +----------------------+ +;;;; +;;;; * Finding the Depth of the Nodes +;;;; +;;;; We have already looked at how to show the entire tree, but what if we +;;;; want to also show the depth of each node in the tree, to better +;;;; identify how each node fits in the hierarchy? This can be done by +;;;; adding a COUNT function and a GROUP BY clause to our existing query +;;;; for showing the entire tree: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT node.name, (COUNT(parent.name) - 1) AS depth +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; GROUP BY node.name +;;;; ORDER BY node.lft; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-node-depth (name) + "Find the depth of a specific CATEGORY node named NAME." + (retrieve-by-sql + (sxql:select + (:node.name (:as (:- (:count :parent.name) 1) :depth)) + (sxql:from (:as :category :node) + (:as :category :parent)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (:= :node.name name))) + (sxql:group-by :node.name) + (sxql:order-by :node.left)))) + +(defun category-tree-depth () + "Show the depths of all nodes in the CATEGORY tree." + (retrieve-by-sql + (sxql:select + (:node.name (:as (:- (:count :parent.name) 1) :depth)) + (sxql:from (:as :category :node) + (:as :category :parent)) + (sxql:where (sxql:make-op :raw "node.left between parent.left and parent.right")) + (sxql:group-by :node.name) + (sxql:order-by :node.left)))) + +;;;; #+END_SRC +;;;; +;;;; +----------------------+-------+ +;;;; | name | depth | +;;;; +----------------------+-------+ +;;;; | ELECTRONICS | 0 | +;;;; | TELEVISIONS | 1 | +;;;; | TUBE | 2 | +;;;; | LCD | 2 | +;;;; | PLASMA | 2 | +;;;; | PORTABLE ELECTRONICS | 1 | +;;;; | MP3 PLAYERS | 2 | +;;;; | FLASH | 3 | +;;;; | CD PLAYERS | 2 | +;;;; | 2 WAY RADIOS | 2 | +;;;; +----------------------+-------+ +;;;; +;;;; We can use the depth value to indent our category names with the +;;;; CONCAT and REPEAT string functions: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT CONCAT( REPEAT(' ', COUNT(parent.name) - 1), node.name) AS name +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; GROUP BY node.name +;;;; ORDER BY node.lft; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-full-tree-format (&optional (depths (category-tree-depth))) + "Output a formatted display of the CATEGORY tree. + +DEPTHS is the depth tree provided by default by CATEGORY-TREE-DEPTH." + (with-output-to-string (s) + (mapcar (lambda (node) + (format s (format nil "~~~d@a~~a~~%" + (getf node :depth)) + "" (getf node :name))) + depths))) + +;;;; #+END_SRC +;;;; +;;;; +-----------------------+ +;;;; | name | +;;;; +-----------------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | PORTABLE ELECTRONICS | +;;;; | MP3 PLAYERS | +;;;; | FLASH | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; +-----------------------+ +;;;; +;;;; Of course, in a client-side application you will be more likely to use +;;;; the depth value directly to display your hierarchy. Web developers +;;;; could loop through the tree, adding
  • and
      tags as +;;;; the depth number increases and decreases. +;;;; +;;;; * Depth of a Sub-Tree +;;;; +;;;; When we need depth information for a sub-tree, we cannot limit either +;;;; the node or parent tables in our self-join because it will corrupt our +;;;; results. Instead, we add a third self-join, along with a sub-query to +;;;; determine the depth that will be the new starting point for our +;;;; sub-tree: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth +;;;; FROM nested_category AS node, +;;;; nested_category AS parent, +;;;; nested_category AS sub_parent, +;;;; ( +;;;; SELECT node.name, (COUNT(parent.name) - 1) AS depth +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND node.name = 'PORTABLE ELECTRONICS' +;;;; GROUP BY node.name +;;;; ORDER BY node.lft +;;;; ) AS sub_tree +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt +;;;; AND sub_parent.name = sub_tree.name +;;;; GROUP BY node.name +;;;; ORDER BY node.lft; +;;;; #+END_SRC + +(defun category-subtree-depth (name) + (retrieve-by-sql + (sxql:select + (:node.name (:as (:- (:count :parent.name) (:+ :sub_tree.depth 1)) :depth)) + (sxql:from (:as :category :node) + (:as :category :parent) + (:as :category :sub_parent) + (:as (sxql:select + (:node.name (:as (:- (:count :parent.name) 1) :depth)) + (sxql:from (:as :category :node) + (:as :category :parent)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (:= :node.name name))) + (sxql:group-by :node.name) + (sxql:order-by :node.left)) + :sub_tree)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (sxql:make-op :raw "node.left between sub_parent.left and sub_parent.right") + (:= :sub_parent.name :sub_tree.name))) + (sxql:group-by :node.name) + (sxql:order-by :node.left)))) + +;;;; +;;;; +----------------------+-------+ +;;;; | name | depth | +;;;; +----------------------+-------+ +;;;; | PORTABLE ELECTRONICS | 0 | +;;;; | MP3 PLAYERS | 1 | +;;;; | FLASH | 2 | +;;;; | CD PLAYERS | 1 | +;;;; | 2 WAY RADIOS | 1 | +;;;; +----------------------+-------+ +;;;; +;;;; This function can be used with any node name, including the root +;;;; node. The depth values are always relative to the named node. +;;;; +;;;; * Find the Immediate Subordinates of a Node +;;;; +;;;; Imagine you are showing a category of electronics products on a +;;;; retailer web site. When a user clicks on a category, you would want to +;;;; show the products of that category, as well as list its immediate +;;;; sub-categories, but not the entire tree of categories beneath it. For +;;;; this, we need to show the node and its immediate sub-nodes, but no +;;;; further down the tree. For example, when showing the PORTABLE +;;;; ELECTRONICS category, we will want to show MP3 PLAYERS, CD PLAYERS, +;;;; and 2 WAY RADIOS, but not FLASH. +;;;; +;;;; This can be easily accomplished by adding a HAVING clause to our previous query: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth +;;;; FROM nested_category AS node, +;;;; nested_category AS parent, +;;;; nested_category AS sub_parent, +;;;; ( +;;;; SELECT node.name, (COUNT(parent.name) - 1) AS depth +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND node.name = 'PORTABLE ELECTRONICS' +;;;; GROUP BY node.name +;;;; ORDER BY node.lft +;;;; ) AS sub_tree +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt +;;;; AND sub_parent.name = sub_tree.name +;;;; GROUP BY node.name +;;;; HAVING depth <= 1 +;;;; ORDER BY node.lft; +;;;; #+END_SRC + +(defun category-subtree-max-depth (name &optional (max-depth 1)) + "TODO This doesn't seem to work. It returns FLASH with DEPTH = 2" + (retrieve-by-sql + (sxql:select + (:node.name (:as (:- (:count :parent.name) (:+ :sub_tree.depth 1)) :depth)) + (sxql:from (:as :category :node) + (:as :category :parent) + (:as :category :sub_parent) + (:as (sxql:select + (:node.name (:as (:- (:count :parent.name) 1) :depth)) + (sxql:from (:as :category :node) + (:as :category :parent)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (:= :node.name name))) + (sxql:group-by :node.name) + (sxql:order-by :node.left)) + :sub_tree)) + (sxql:where (:and (sxql:make-op :raw "node.left between parent.left and parent.right") + (sxql:make-op :raw "node.left between sub_parent.left and sub_parent.right") + (:= :sub_parent.name :sub_tree.name))) + (sxql:group-by :node.name) + (sxql:having (:<= :depth max-depth)) + (sxql:order-by :node.left)))) + +;;;; +;;;; +----------------------+-------+ +;;;; | name | depth | +;;;; +----------------------+-------+ +;;;; | PORTABLE ELECTRONICS | 0 | +;;;; | MP3 PLAYERS | 1 | +;;;; | CD PLAYERS | 1 | +;;;; | 2 WAY RADIOS | 1 | +;;;; +----------------------+-------+ +;;;; +;;;; If you do not wish to show the parent node, change the HAVING depth <= 1 line to HAVING depth = 1. +;;;; +;;;; * Aggregate Functions in a Nested Set +;;;; +;;;; Let’s add a table of products that we can use to demonstrate aggregate +;;;; functions with: +;;;; +;;;; #+BEGIN_SRC sql +;;;; CREATE TABLE product +;;;; ( +;;;; product_id INT AUTO_INCREMENT PRIMARY KEY, +;;;; name VARCHAR(40), +;;;; category_id INT NOT NULL +;;;; ); +;;;; +;;;; INSERT INTO product(name, category_id) +;;;; VALUES +;;;; ('20" TV',3), +;;;; ('36" TV',3), +;;;; ('Super-LCD 42"',4), +;;;; ('Ultra-Plasma 62"',5), +;;;; ('Value Plasma 38"',5), +;;;; ('Power-MP3 5gb',7), +;;;; ('Super-Player 1gb',8), +;;;; ('Porta CD',9), +;;;; ('CD To go!',9), +;;;; ('Family Talk 360',10); +;;;; +;;;; SELECT * FROM product; +;;;; #+END_SRC +;;;; +;;;; +------------+-------------------+-------------+ +;;;; | product_id | name | category_id | +;;;; +------------+-------------------+-------------+ +;;;; | 1 | 20" TV | 3 | +;;;; | 2 | 36" TV | 3 | +;;;; | 3 | Super-LCD 42" | 4 | +;;;; | 4 | Ultra-Plasma 62" | 5 | +;;;; | 5 | Value Plasma 38" | 5 | +;;;; | 6 | Power-MP3 128mb | 7 | +;;;; | 7 | Super-Shuffle 1gb | 8 | +;;;; | 8 | Porta CD | 9 | +;;;; | 9 | CD To go! | 9 | +;;;; | 10 | Family Talk 360 | 10 | +;;;; +------------+-------------------+-------------+ +;;;; +;;;; Now let’s produce a query that can retrieve our category tree, along +;;;; with a product count for each category: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT parent.name, COUNT(product.name) +;;;; FROM nested_category AS node , +;;;; nested_category AS parent, +;;;; product +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; AND node.category_id = product.category_id +;;;; GROUP BY parent.name +;;;; ORDER BY node.lft; +;;;; #+END_SRC +;;;; +;;;; +----------------------+---------------------+ +;;;; | name | COUNT(product.name) | +;;;; +----------------------+---------------------+ +;;;; | ELECTRONICS | 10 | +;;;; | TELEVISIONS | 5 | +;;;; | TUBE | 2 | +;;;; | LCD | 1 | +;;;; | PLASMA | 2 | +;;;; | PORTABLE ELECTRONICS | 5 | +;;;; | MP3 PLAYERS | 2 | +;;;; | FLASH | 1 | +;;;; | CD PLAYERS | 2 | +;;;; | 2 WAY RADIOS | 1 | +;;;; +----------------------+---------------------+ +;;;; +;;;; This is our typical whole tree query with a COUNT and GROUP BY added, +;;;; along with a reference to the product table and a join between the +;;;; node and product table in the WHERE clause. As you can see, there is a +;;;; count for each category and the count of subcategories is reflected in +;;;; the parent categories. +;;;; +;;;; * Adding New Nodes +;;;; +;;;; ** category-insert-right-of +;;;; +;;;; If we wanted to add a new node between the TELEVISIONS and PORTABLE +;;;; ELECTRONICS nodes, the new node would have lft and rgt values of 10 +;;;; and 11, and all nodes to its right would have their lft and rgt values +;;;; increased by two. We would then add the new node with the appropriate +;;;; lft and rgt values. While this can be done with a stored procedure in +;;;; MySQL 5, I will assume for the moment that most readers are using 4.1, +;;;; as it is the latest stable version, and I will isolate my queries with +;;;; a LOCK TABLES statement instead: +;;;; +;;;; #+BEGIN_SRC sql +;;;; LOCK TABLE nested_category WRITE; +;;;; +;;;; SELECT @myRight := rgt FROM nested_category +;;;; WHERE name = 'TELEVISIONS'; +;;;; +;;;; UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight; +;;;; UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight; +;;;; +;;;; INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1, @myRight + 2); +;;;; +;;;; UNLOCK TABLES; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-insert-right-of (name &optional left-of) + "Create a new category tree. LEFT-OF is the NAME of the node to the +left of this new node named NAME. If LEFT-OF is NIL, insert +all-the-way at the right." + (with-connection (db) + (let* ((found (ignore-errors + (mito:find-dao 'category :name left-of))) + (my-right (if (and left-of found) + (ignore-errors + (category-right-of found)) + (max-dao 'category)))) + (with-transaction + (mapcar #'mito:execute-sql + (list + (sxql:update :category + (sxql:set= :right (:+ :right 2)) + (sxql:where (:> :right my-right))) + (sxql:update :category + (sxql:set= :left (:+ :left 2)) + (sxql:where (:> :left my-right))))) + (mito:create-dao 'category :name name + :left (+ my-right 1) + :right (+ my-right 2)))))) + +;;;; #+END_SRC +;;;; +;;;; We can then check our nesting with our indented tree query: +;;;; +;;;; +-----------------------+ +;;;; | name | +;;;; +-----------------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | GAME CONSOLES | +;;;; | PORTABLE ELECTRONICS | +;;;; | MP3 PLAYERS | +;;;; | FLASH | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; +-----------------------+ +;;;; +;;;; ** category-insert-new-child +;;;; +;;;; If we instead want to add a node as a child of a node that has no +;;;; existing children, we need to modify our procedure slightly. Let’s add +;;;; a new FRS node below the 2 WAY RADIOS node: +;;;; +;;;; #+BEGIN_SRC sql +;;;; LOCK TABLE nested_category WRITE; +;;;; +;;;; SELECT @myLeft := lft FROM nested_category +;;;; +;;;; WHERE name = '2 WAY RADIOS'; +;;;; +;;;; UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myLeft; +;;;; UPDATE nested_category SET lft = lft + 2 WHERE lft > @myLeft; +;;;; +;;;; INSERT INTO nested_category(name, lft, rgt) VALUES('FRS', @myLeft + 1, @myLeft + 2); +;;;; +;;;; UNLOCK TABLES; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-insert-new-child (name left-of) + "Create a new category tree. LEFT-OF is the NAME of the node to the +left of this new node named NAME." + (with-connection (db) + (let* ((my-left (category-left-of + (mito:find-dao 'category :name left-of)))) + (with-transaction + (mapcar #'mito:execute-sql + (list + (sxql:update :category + (sxql:set= :right (:+ :right 2)) + (sxql:where (:> :right my-left))) + (sxql:update :category + (sxql:set= :left (:+ :left 2)) + (sxql:where (:> :left my-left))))) + (mito:create-dao 'category :name name + :left (+ my-left 1) + :right (+ my-left 2)))))) + +;;;; #+END_SRC +;;;; +;;;; In this example we expand everything to the right of the left-hand +;;;; number of our proud new parent node, then place the node to the right +;;;; of the left-hand value. As you can see, our new node is now properly +;;;; nested: +;;;; +;;;; #+BEGIN_SRC sql +;;;; SELECT CONCAT( REPEAT( ' ', (COUNT(parent.name) - 1) ), node.name) AS name +;;;; FROM nested_category AS node, +;;;; nested_category AS parent +;;;; WHERE node.lft BETWEEN parent.lft AND parent.rgt +;;;; GROUP BY node.name +;;;; ORDER BY node.lft; +;;;; #+END_SRC +;;;; +;;;; +-----------------------+ +;;;; | name | +;;;; +-----------------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | GAME CONSOLES | +;;;; | PORTABLE ELECTRONICS | +;;;; | MP3 PLAYERS | +;;;; | FLASH | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; | FRS | +;;;; +-----------------------+ +;;;; +;;;; * Deleting Nodes +;;;; +;;;; ** DONE category-delete-tree +;;;; +;;;; The last basic task involved in working with nested sets is the +;;;; removal of nodes. The course of action you take when deleting a node +;;;; depends on the node’s position in the hierarchy; deleting leaf nodes +;;;; is easier than deleting nodes with children because we have to handle +;;;; the orphaned nodes. +;;;; +;;;; When deleting a leaf node, the process if just the opposite of adding +;;;; a new node, we delete the node and its width from every node to its +;;;; right: +;;;; +;;;; #+BEGIN_SRC sql +;;;; LOCK TABLE nested_category WRITE; +;;;; +;;;; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 +;;;; FROM nested_category +;;;; WHERE name = 'GAME CONSOLES'; +;;;; +;;;; DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight; +;;;; +;;;; UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; +;;;; UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight; +;;;; +;;;; UNLOCK TABLES; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-delete-tree (name) + (with-connection (db) + (with-transaction + (let ((found (mito:find-dao 'category :name name))) + (if found + (let* ((my-left (category-left-of found)) + (my-right (category-right-of found)) + (my-width (1+ (- my-right my-left)))) + (mapcar #'mito:execute-sql + (list + (sxql:delete-from :category + (sxql:where (sxql:make-op :raw + (format nil "left between ~d and ~d" + my-left my-right)))) + (sxql:update :category + (sxql:set= :right (:- :right my-width)) + (sxql:where (:> :right my-right))) + (sxql:update :category + (sxql:set= :left (:- :left my-width)) + (sxql:where (:> :left my-right))))))))))) + +;;;; #+END_SRC +;;;; +;;;; And once again, we execute our indented tree query to confirm that our +;;;; node has been deleted without corrupting the hierarchy: +;;;; +;;;; +-----------------------+ +;;;; | name | +;;;; +-----------------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | PORTABLE ELECTRONICS | +;;;; | MP3 PLAYERS | +;;;; | FLASH | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; | FRS | +;;;; +-----------------------+ +;;;; +;;;; This approach works equally well to delete a node and all its children: +;;;; +;;;; And once again, we query to see that we have successfully deleted an entire sub-tree: +;;;; +;;;; +-----------------------+ +;;;; | name | +;;;; +-----------------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | PORTABLE ELECTRONICS | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; | FRS | +;;;; +-----------------------+ +;;;; +;;;; ** category-delete-and-reparent +;;;; +;;;; The other scenario we have to deal with is the deletion of a parent +;;;; node but not the children. In some cases you may wish to just change +;;;; the name to a placeholder until a replacement is presented, such as +;;;; when a supervisor is fired. In other cases, the child nodes should all +;;;; be moved up to the level of the deleted parent: +;;;; +;;;; #+BEGIN_SRC sql +;;;; LOCK TABLE nested_category WRITE; +;;;; +;;;; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 +;;;; FROM nested_category +;;;; WHERE name = 'PORTABLE ELECTRONICS'; +;;;; +;;;; DELETE FROM nested_category WHERE lft = @myLeft; +;;;; +;;;; UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight; +;;;; UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight; +;;;; UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight; +;;;; +;;;; UNLOCK TABLES; +;;;; #+END_SRC +;;;; +;;;; #+BEGIN_SRC lisp + +(defun category-delete-and-reparent (name) + (with-connection (db) + (with-transaction + (let ((found (mito:find-dao 'category :name name))) + (if found + (let* ((my-left (category-left-of found)) + (my-right (category-right-of found))) + (mapcar #'mito:execute-sql + (list + (sxql:delete-from :category + (sxql:where (:= :left my-left))) + (sxql:update :category + (sxql:set= :right (:- :right 1) + :left (:- :left 1)) + (sxql:where (sxql:make-op + :raw (format nil "left between ~d and ~d" + my-left my-right)))) + (sxql:update :category + (sxql:set= :right (:- :right 2)) + (sxql:where (:> :right my-right))) + (sxql:update :category + (sxql:set= :left (:- :left 2)) + (sxql:where (:> :left my-right))))))))))) + +;;;; #+END_SRC +;;;; +;;;; In this case we subtract two from all elements to the right of the +;;;; node (since without children it would have a width of two), and one +;;;; from the nodes that are its children (to close the gap created by the +;;;; loss of the parent’s left value). Once again, we can confirm our +;;;; elements have been promoted: +;;;; +;;;; +---------------+ +;;;; | name | +;;;; +---------------+ +;;;; | ELECTRONICS | +;;;; | TELEVISIONS | +;;;; | TUBE | +;;;; | LCD | +;;;; | PLASMA | +;;;; | CD PLAYERS | +;;;; | 2 WAY RADIOS | +;;;; | FRS | +;;;; +---------------+ +;;;; +;;;; ** category-delete-and-promote +;;;; +;;;; Other scenarios when deleting nodes would include promoting one of the +;;;; children to the parent position and moving the child nodes under a +;;;; sibling of the parent node, but for the sake of space these scenarios +;;;; will not be covered in this article. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO SELECT-CATEGORY ? +(defgeneric by-category (class maybe-category-id) + (:method ((class t) (id category)) + (select-dao class (sxql:where (:= :category id)))) + (:method ((class t) (id integer)) + (select-dao class (sxql:where (:= :category-id id)))) + (:method ((class t) (id string)) + (by-category class (parse-integer id)))) + diff --git a/src/models/constructed-decks.lisp b/src/models/constructed-decks.lisp new file mode 100644 index 0000000..16760de --- /dev/null +++ b/src/models/constructed-decks.lisp @@ -0,0 +1,335 @@ +#| + +src/models/constructed-deck.lisp + +Constructed deck model: + +- CONSTRUCTED-DECK + +A CONSTRUCTED-DECK is based on a YDK-DECK. The YDK-DECK is built on +the YDK object, which keeps track of synchronizing the stuff in the +Deck Builder App with the database through YDK-SYNC. Since we don't +plan on modifying CONSTRUCTED-DECKs, there is nothing analogous to +YDK-SYNC for CONSTRUCTED-DECKs. I think that's what I'm implementing +right now, the pull logic. + +- CONSTRUCTED-DECK-ITEM + +Analogous to DECK-ITEM. Nothing fancy here. Extra columns: YGO-SET-ITEM, SELL-PRICE. + +- SOLD-DECK + +NODO Supposed to represent decks sold in the same way Deck Templates +-> Pulled Decks, Pulled Decks -> Sold Decks. + +|# + +(in-package #:cl-deck-builder2.models.constructed-decks) + +(defparameter +default-constructed-deck-sell-price+ "60.00" + "Default sell price for constructed decks. We have the price for every card set to 0.50 cents. That works out to $35 for a 70 card deck. This is about twice that.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass constructed-deck () + ((;; The name of this CONSTRUCTED-DECK + name :accessor deck-name-of + :col-type :text + :initarg :name) + + (category :accessor deck-category-of :col-type (or category :null)) + (created-by :accessor deck-created-by :col-type :text :initarg :created-by) + + (;; If the CONSTRUCTED-DECK has a SELL-PRICE, we override the total price of the deck with this price. + sell-price :accessor deck-sell-price-of + :col-type :integer + :initarg :sell-price + :initform +default-constructed-deck-sell-price+ + :deflate #'currency-deflate + :inflate #'currency-inflate) + + ;; The original ID of the deck, probably from YDK-DECK + (ydk-deck :accessor ydk-deck-of + :col-type ydk-deck + :initarg :ydk-deck) + (sold :accessor deck-sold + :col-type :binary + :initarg :deck-sold + :initform 0)) + (:metaclass registered-table-class) + (:documentation "A Constructed deck is just a deck that has been \"pulled.\" That is, +somebody built a deck template, clicked the \"Pull\" button. From +what has been explained to me, we'll only pull decks we have. So +we'll only construct decks with cards we know or think we know we have +or we plan on getting more of. Then the physical cards will need to +be collected and assembled into the deck following the template. This +physical, real, action is what is recorded by the \"Pull\" action. + +It's important to note that it is impossible to constrain somebody +from pulling arbitrary decks. A physical security device would need to +be in place and a framework for authentication with it would be +necessary (i.e. it is expected that one will use this tool with +intention, respectfully, and will be trained on how to do so). + +Anyway. You don't actually have to physically pull the cards yet. This +is just a record, think an earmark on a page, that these cards from +this deck template have been pulled. YDK-DECK-PULL-FROM-INVENTORY does +all the heavy lifting, and will return a CONSTRUCTED-DECK object if +it was successful. + +Then, finally, during the deck construction phase, you will be +prompted to select the variant of card, language, condition, etc. + +Once you are happy with your selection the constructed deck will be +marked as \"FOR-SALE\".")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass constructed-deck-item () + (;; The original ID of the CONSTRUCTED-DECK-ITEM, from DECK-ITEM + ;; (passcode :accessor deck-passcode-of :col-type :integer) + ;; (inventory-item :accessor deck-inventory-item-of :col-type inventory-item) + (ygo-card :accessor ygo-card-of + :col-type ygo-card + :initarg :ygo-card) + (deck-id :accessor deck-id-of + :col-type :integer) + (deck-item :accessor deck-item-of + :col-type deck-item + :initarg :deck-item) + (ygo-set-item :accessor deck-set-item-of + :col-type ygo-set-item + :initarg :ygo-set-item)) + ;; (variant :accessor deck-item-variant-of + ;; :col-type variant + ;; :initarg :variant) + ;; + ;; Shouldn't inventory items have Qty and not constructed deck items? + ;; + ;; (opt-qty :accessor opt-qty-of + ;; :col-type :integer + ;; :initarg :opt-qty + ;; :initform 0) + ;; (qty :accessor qty-of + ;; :col-type :integer + ;; :initarg :qty + ;; :initform 0) + (:metaclass registered-table-class) + (:documentation "A CONSTRUCTED-DECK-ITEM is pretty much the same as a DECK-ITEM, except it's a \"constructed\" deck. + +Same index idea and everything. The table is indexed into by CONSTRUCTED-DECK-ID. + +We subclass YGO-SETS for all the card metadata.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Analogous to YDK-DECK -- Do we even need this? I just loop over the SET-ITEMS anyway. +(defclass constructed-deck-intermediate () + ((deck-item :accessor cdi-deck-item + :initarg :deck-item) + (deck-set :accessor cdi-deck-set + :initarg :deck-set) + (set-item :accessor cdi-deck-set-item + :initarg :set-item) + (condition :accessor cdi-deck-condition + :initarg :condition) + (card :accessor cdi-deck-card + :initarg :card))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Just want the decks to be marked as sold. Easy. +;; +;; (defclass sold-deck (constructed-deck) +;; (;; Has the deck been sold? +;; (deck-sold :accessor constructed-deck-sold +;; :col-type :integer +;; :initarg :sold +;; :initform 0) +;; ;; 60 Doll Hairs +;; (sell-price :accessor deck-sell-price-of +;; :col-type :integer +;; :initarg :sell-price +;; :initform +default-constructed-deck-sell-price+ +;; :deflate #'currency-deflate +;; :inflate #'currency-inflate)) +;; (:metaclass registered-table-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; I think this got superseded by CDI-PULL-FROM-INVENTORY since we +;; aren't pulling from YDKs any more but from +;; CONSTRUCTED-DECK-INTERMEDIATE. +(defmethod ydk-deck-pull-from-inventory ((deck ydk-deck)) + "Attempt to find inventory stock for every card in this deck, and if we are able to secure it, construct and insert a new PULLED-DECK into the database." + (with-connection (db) + (let ((;; Get the OLD-DECK-ID + old-deck-id (mito:object-id deck)) + (;; Create a new DECK based on the old DECK + new (mito:create-dao 'constructed-deck + :name (ydk-name-of deck) + :category (ydk-category-of deck) + :created-by (ydk-created-by deck) + :sell-price +default-constructed-deck-sell-price+ + :ydk-deck deck))) + ;; If the new deck was created successfully, + (when new + (let ((;; Get the NEW-DECK-ID + new-deck-id (mito:object-id deck)) + (;; Get all the items from the old deck + deck-items (mito:select-dao 'deck-item + (sxql:where (:= :deck-id old-deck-id))))) + (with-transaction + (dolist (deck-item deck-items new) + ;; Create a bunch of CONSTRUCTED-DECK-ITEMs for the + ;; corresponding DECK-ITEMs. from the old DECK. + (create-dao 'constructed-deck-item + :deck-id new-deck-id + :ygo-card-id (deck-passcode-of deck-item))))))))) + +;; (defmethod mark-as-sold ((sold-deck sold-deck)) +;; "Mark a SOLD-DECK as having been sold." +;; (setf (deck-sold sold-deck) 1) +;; (update-dao sold-deck)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This expects a list of CONSTRUCTED-DECK-INTERMEDIATE which has no documented format. +;; +(defun deck-to-pull-set-items-qty-as-alist (cdi-items) + "Construct an ALIST of PASSCODE . QTY from DECK listing. We use a list of CONSTRUCTED-DECK-INTERMEDIATE objects." + (let ((alist '())) + (dolist (row cdi-items alist) + (push (cons (cdi-deck-set-item row) + (qty-of (cdi-deck-set-item row))) + alist)))) + +(defun deck-to-pull-desired-qty-as-alist (cdi-items) + "Construct an ALIST of PASSCODE . (COUNT PASSCODE) from DECK listing. We use a list of CONSTRUCTED-DECK-INTERMEDIATE objects." + (let ((alist '())) + (dolist (row cdi-items alist) + (if (assoc (cdi-deck-set-item row) alist :test #'mito:object=) + (incf (cdr (assoc (cdi-deck-set-item row) alist :test #'mito:object=))) + (push (cons (cdi-deck-set-item row) 1) alist))))) + +(defun subtract-desired-from-set-item-qty-as-alist (set-items-qty desired-items-qty) + "Perform a DECF on the CDR of the paired elements from (DECK-TO-PULL-SET-ITEMS-QTY-AS-ALIST) using (DECK-TO-PULL-DESIRED-QTY-AS-ALIST) as the source argument." + (dolist (pair desired-items-qty set-items-qty) + (if (assoc (car pair) set-items-qty :test #'mito:object=) + (decf (cdr (assoc (car pair) set-items-qty :test #'mito:object=)) + (cdr pair)) + (v:info :construct "Invalid ID pair: ~a" (car pair))))) + +(defun find-any-invalid-qtys (set-items-qty desired-items-qty) + (with-connection (db) + (with-transaction + (loop for pair in (subtract-desired-from-set-item-qty-as-alist set-items-qty desired-items-qty) + do (when (minusp (cdr pair)) + (return (ygo-passcode-of (item-of (car pair))))))))) + +(defun valid-pull-p (set-items-qty desired-items-qty) + "Loop over the results of SUBTRACT-DESIRED-FROM-SET-ITEM-QTY-AS-ALIST, looking for any less-than-zero values. If any exist, we took too much out of inventory, and the pull is \"invalid,\" we return NIL. Otherwise, the pull will be successful (we have enough inventory), so return T." + (not (find-any-invalid-qtys set-items-qty desired-items-qty))) + +;; Actually subtract the amounts from the set item +;; make the CONSTRUCTED-DECK with CONSTRUCTED-DECK-ITEM +;; Make the view panel + +(defun cdi-pull-from-inventory (cdi-list) + "Attempt to find inventory stock for every card in this deck, and if we are able to secure it, construct and insert a new PULLED-DECK into the database." + (with-connection (db) + (let* ((;; Get the OLD-DECK-ID + old-deck-id (deck-id-of (cdi-deck-item (car cdi-list)))) + (;; Get the OLD DECK + old-deck (mito:find-dao 'ydk-deck :id old-deck-id)) + (;; Create a new DECK based on the old DECK + new (mito:create-dao 'constructed-deck + :name (ydk-name-of old-deck) + :category (ydk-category-of old-deck) + :created-by (ydk-created-by old-deck) + :sell-price +default-constructed-deck-sell-price+ + :ydk-deck old-deck))) + ;; If the new deck was created successfully, + (when new + ;; Get the NEW-DECK-ID + (let ((new-deck-id (mito:object-id new))) + ;; iterate over all the items from the old deck + (with-transaction + (dolist (row cdi-list new) + ;; Create a bunch of CONSTRUCTED-DECK-ITEMs for the + ;; corresponding DECK-ITEMs. from the old DECK. + (mito:create-dao 'constructed-deck-item + :deck-id new-deck-id + :deck-item-id (mito:object-id (cdi-deck-item row)) + :ygo-set-item-id (mito:object-id (cdi-deck-set-item row)) + :ygo-card-id (deck-passcode-of (cdi-deck-item row)))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun return-to-inventory (cdi-items) + "Attempt to find inventory stock for every card in this deck, and increment the count by however many we specify in the CDR." + (let ((counts (deck-to-pull-desired-qty-as-alist cdi-items))) + (dolist (row counts) + (incf (qty-of (car row)) (cdr row)) + ;; Update each one and save - wrapping this in a transaction causes issues with pulling multiple items. + (save-dao (car row))))) + +(defun constructed-deck-as-cdi-list (constructed-deck-id) + (with-connection (db) + (let ((deck + (mito:select-dao 'constructed-deck-item + (mito:includes 'ygo-card 'deck-item 'ygo-set-item) + (sxql:order-by :asc :id) + (sxql:where (:= :deck-id constructed-deck-id)))) + (lst '())) + (dolist (row deck (reverse lst)) + (let ((deck-set-item (deck-set-item-of row))) + (push (make-instance 'constructed-deck-intermediate + :card (ygo-card-by-passcode (ygo-passcode-of (ygo-card-of row))) + :condition (variant-of deck-set-item) + :deck-item (deck-item-of row) + :deck-set (ygo-set-by-id (mito:object-id (item-of deck-set-item))) + :set-item deck-set-item) + lst)))))) + +;; TODO Use STORE-CONSTRUCTED-DECK-AS-CURRENT to load a +;; CONSTRUCTED-DECK out of the database into +;; *CURRENT-CONSTRUCTED-DECK* so we can use RETURN-TO-INVENTORY on it. +(defun cdi-return-to-inventory (cdi-list) + (error "TODO")) + +;; Where did this code from? I feel like it got easily superceded by CALCULATE-OPTIMUM-QTY. +;; I think it was used in CARDS-IN-ALL-DECKS? +(defun unique-constructed-deck-item-passcodes () + "Select the unique cards in all CONSTRUCTED-DECK-ITEMs" + (let ((deck-items + (with-connection (db) + (mito:select-dao 'constructed-deck-item + (sxql:group-by :passcode) + (sxql:order-by :passcode :desc)))) + (ht (make-hash-table))) + (with-connection (db) + (with-transaction + (dolist (deck-item (reverse deck-items) ht) + (pushnew (list :deck-id (deck-id-of deck-item) + :constructed-deck (mito:find-dao 'constructed-deck :id (deck-id-of deck-item)) + :ygo-card (mito:find-dao 'ygo-card :id (deck-passcode-of deck-item))) + (gethash (deck-passcode-of deck-item) ht) + :key (lambda (plist) (getf plist :deck-id)))))))) + +(defun calculate-optimum-qty () + (with-connection (db) + (let ((count-data + (with-transaction + (mito:retrieve-by-sql + (sxql:select (:passcode (:as (:count :passcode) :count)) + (sxql:from :deck_item) + (sxql:group-by :passcode) + (sxql:order-by (:count :passcode) :asc))))) + (lst '())) + (with-transaction + (mapcar #'mito:execute-sql + (dolist (row count-data lst) + (let ((passcode (getf row :passcode)) + (opt-qty (getf row :count))) + (push + (sxql:update :ygo_set_item + (sxql:set= :opt_qty (* 5 opt-qty)) + (sxql:where (:in :item_id + (sxql:select :id + (sxql:from :ygo_set) + (sxql:where (:= :passcode_id passcode)))))) + lst)))))))) diff --git a/src/models/crystal-commerce-csv.lisp b/src/models/crystal-commerce-csv.lisp new file mode 100644 index 0000000..da6fb0b --- /dev/null +++ b/src/models/crystal-commerce-csv.lisp @@ -0,0 +1,208 @@ +#| + +src/models/crystal-commerce-csv.lisp + +Crystal Commerce Search CSV Export / Import functionality. + +This code is so old. It dates back to May. I mistakenly assumed we +would get access to Crystal Commerce or TCGPlayer API data. + +Instead, I based my initial database design off of the CSV export +data. The CSV export data does not supply the following necessary +fields: Passcode (Card Image), Variants (Condition). + +I learned July 15 that this design was insufficient and set out to +replace it with something more robust. Within 2 weeks by the beginning +of August I had conceptualized a new design, and by the second week of +August, had implemented that design. That is currenly YGOPRODECK-2. + +There is some munging facility in here that I wrote while munging the +YGOProDeck API data during the v2 rewrite. + +TODO This needs to be rewritten to match the new YGOProDeck v3 +API. Currently we scan in the Product Name, it could be split out to +match the corresponding db entry. + +TODO Some time in October I became aware we have access to Crystal Commerce API now. + +https://crystal-service.readme.io/docs/get-started-with-the-admin-api +https://crystal-service.readme.io/reference/get_api-v1-activity-logs + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.models.crystal-commerce.csv + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.crystal-commerce) + (:import-from #:cl-deck-builder2.toolkit + #:grouped) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:*cc-csv-import-fields* + #:*cc-csv-header-fields* + #:csv-import-cc)) + +(in-package #:cl-deck-builder2.models.crystal-commerce.csv) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CSV Import +(defvar *csv* nil + "The currently loaded CSV data.") + +(defvar *card-sets* nil + "Data loaded from CARD-SET table. We use datafly for this one because +mito conses a lot for CLOS. A PLIST is faster.") + +(defvar *cc-csv-import-fields* + '("Product Name" + "Category" + "Total Qty" + "Wishlists" + "Buy Price" + "Sell Price" + "URL" + "Barcode" + "Manufacturer SKU" + "Amazon ASIN" + "MSRP" + "Brand" + "Weight" + "Description" + "Max Qty" + "Domestic Only" + "Tax Exempt") + "Crystal Commerce header fields via CSV import. We transform this into *CC-CSV-HEADER-FIELDS*. + +SEE *CC-CSV-HEADER-FIELDS*.") + +(defvar *cc-csv-header-fields* + (mapcar (lambda (s) + (alexandria:make-keyword + (string-upcase + (substitute #\- #\Space s)))) + *cc-csv-import-fields*) + "Transformed list of Keyword-ified header fields. That is, we take *CC-CSV-IMPORT-FIELDS* and transform the strings into keywords: + + \"Product Name\" => :PRODUCT-NAME.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun load-csv (maybe-pathname &optional (force nil)) + "Cache CSV data from MAYBE-PATHNAME which may be a PATHNAME or a STRING containing CSV data." + (if (or force + (null *csv*)) + (progn + (v:info :cc-csv "Loading CSV from ~A~%" + (typecase maybe-pathname + (pathname maybe-pathname) + (string 'STRING))) + (setf *csv* + ;; Skip the first header line + (rest + (cl-csv:read-csv maybe-pathname)))) + (v:info :cc-csv "Using previously loaded CSV data of length ~d~%" (length *csv*)))) + +(defun load-card-sets () + "Cache CARD-SET info to match up from PRODUCT-NAME." + (unless *card-sets* + (with-datafly-connection (db) + (with-datafly-transaction + (setf *card-sets* + (datafly:retrieve-all + (sxql:select (:code :passcode) + (sxql:from :ygo_set)))))))) + +;; TODO update this code with the new YGOProDeck stuff +(defun normalize-product-name (name) + "Given a PRODUCT-NAME, apply the extracted data from the string: Name, +Set Code, Rarity, and Edition. Return a property list that can be +passed to SXQL:SET=." + (let ((normalized-name name)) + ;; XXX EW + ;; Replace things like "Rare-" and "Common-" With "Rare" and "Common" + (setf normalized-name (cl-ppcre:regex-replace "(\\w+)- " normalized-name "\\1")) + ;; A few single cards is borked + (setf normalized-name (cl-ppcre:regex-replace "MRD-EN134 - Unlimited" normalized-name "MRD-EN134 - Common - Unlimited")) + (setf normalized-name (cl-ppcre:regex-replace "SXG3-ENE05" normalized-name "SGX3-ENE05")) + (setf normalized-name (cl-ppcre:regex-replace "wCPP-EN002" normalized-name "WCPP-EN002")) + ;; This set was borknd + ;; Maybe not, maybe it just doesn't have a Limited/Unlimited status? + ;; (setf normalized-name (cl-ppcre:regex-replace "TKN4-([^ ]+) - Super Rare" normalized-name "TKN4-\\1 - Super Rare - Unlimited")) + ;; Replace things like Rare 1st Edition with Rare - 1st Edition + (setf normalized-name (cl-ppcre:regex-replace "(Common|Rare) (1st|Limited|Unlimited)" normalized-name "\\1 - \\2")) + (let ((parts (reverse + (cl-ppcre:split " \\s?[-–] \\s?" normalized-name)))) + (case (length parts) + (1 `(:name ,(first parts))) + (2 `(:name ,(format nil "~{~A~^ - ~}" parts))) + (3 `(:name ,(first parts) + :code ,(second parts) + :rarity ,(third parts))) + (4 `(:name ,(first parts) + :code ,(second parts) + :rarity ,(third parts) + :edition ,(fourth parts))) + (5 `(:name ,(format nil "~{~A~^ - ~}" (subseq parts 0 2)) + :code ,(third parts) + :rarity ,(fourth parts) + :edition ,(fifth parts))) + (6 `(:name ,(format nil "~{~A~^ - ~}" (subseq parts 0 3)) + :code ,(fourth parts) + :rarity ,(fifth parts) + :edition ,(sixth parts))) + (otherwise (format t "Skipping data: ~a~%" name)))))) + +;; (defparameter *db-variants* +;; (let ((ht (make-hash-table))) +;; (loop for variant in (select-dao 'variant) do +;; (setf (gethash (variant-name-of variant) ht) variant)) +;; ht)) + +;; TODO clean up the field names to auto generate +;; +;; TODO I added some additional fields, opt-qty reserved-qty condition language +;; I guess we will need one of each in the db... +(defun csv-import-cc (csv) + "Import the perfect data from Crystal Commerce. Do nothing with it but import it into the table." + (load-csv csv) + + (v:info :cc-csv "CSV Loaded") + ;; (load-card-sets) + ;; (format t "Card sets loaded from DB~&") + + (let ((csv-items (seed-csv-items))) + (pre-seed-db-items csv-items)) + + (v:info :cc-csv "done.~&")) + +(defun seed-csv-items () + (let ((csv-items '())) + (with-connection (db) + (with-transaction + (dolist (row *csv*) + (let ((row-plist (mapcan #'list *cc-csv-header-fields* row))) + (push (or (mito:find-dao 'cc-item :product-name (getf row-plist :product-name)) + (apply #'make-instance 'cc-item row-plist)) + csv-items))))) + (v:info :cc-csv "CSV Items: ~d" (length csv-items)) + + (unless (mito:dao-synced (car csv-items)) + (do-grouped-insert csv-items)))) + +(defun pre-seed-db-items (cc-items) + (let ((db-items '())) + (with-connection (db) + (with-transaction + (dolist (cc-item cc-items) + (dolist (variant (select-dao 'variant)) + (push (or (mito:find-dao 'ygo-cc-item :item-id (mito:object-id cc-item) + :variant-id (mito:object-id variant)) + (make-instance 'ygo-cc-item + :item cc-item + :variant variant)) + db-items))))) + (v:info :cc-csv "DB Items: ~d" (length db-items)) + + (unless (mito:dao-synced (car db-items)) + (do-grouped-insert db-items)))) diff --git a/src/models/crystal-commerce.lisp b/src/models/crystal-commerce.lisp new file mode 100644 index 0000000..6da921c --- /dev/null +++ b/src/models/crystal-commerce.lisp @@ -0,0 +1,235 @@ +#| + +src/models/crystal-commerce.lisp + +Crystal Commerce-style Inventory Management Backend + +TODO All of the old style database accessores (*-BY-ID, etc) need to go. + +DONE It looks like the CSV listing is totally outdated after all: + +CL-DECK-BUILDER2> (count-dao 'cc-item) +48883 (16 bits, #xBEF3) +CL-DECK-BUILDER2> (count-dao 'cl-deck-builder2.models.ygoprodeck.classes::ygo-set) +49240 (16 bits, #xC058) + +That's awfully close enough for me, taking into account the art cards, +the duelist name tag, and prize cards! I wonder if there'd be a way to +see the diff. Some cards are name "Dark Magician A" which is +unfortunate! + +TODO Because of the previous item being DONE, this code is now +outdated and stale, and probably safe to delete, unless we intend on +doing more with Crystal Commerce in the future. + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.models.crystal-commerce + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.toolkit.money + #:cl-deck-builder2.models.registered-table + #:cl-deck-builder2.models.ygoprodeck) + (:import-from #:cl-deck-builder2.models.ygoprodeck.fields + #:variant-condition) + (:export + :cc-item + :ygo-cc-item + + :name-of + :cc-category-of + :cc-wishlists-of + :cc-buy-price-of + :cc-sell-price-of + :cc-url-of + :cc-barcode-of + :cc-manufacturer-sku-of + :cc-asin-of + :cc-msrp-of + :cc-brand-of + :cc-weight-of + :cc-description-of + :cc-qty-of + :cc-max-qty-of + :cc-total-qty-of + :cc-domestic-only-of + :cc-tax-exempt-of + :cc-name-of + :cc-code-of + :cc-rarity-of + :cc-edition-of + :cc-passcode-of + + :cc-table-ensure-exists + :cc-table-drop + :cc-table-delete + :cc-table-select + :cc-table-select-count + :cc-table-zero + + :cc-select-by-id + :cc-select-by-passcode + + :cc-delete-by-id + + :cc-create + + :cc-variant-of)) + +(in-package #:cl-deck-builder2.models.crystal-commerce) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Cards v2 +;; +(defclass cc-item () + ((name :accessor name-of + :col-type :text) + (category :accessor cc-category-of + :col-type :text) + (total-qty :accessor cc-total-qty-of + :col-type :integer + :initform 0) + (wishlists :accessor cc-wishlists-of + :col-type :integer + :initform 0) + (;; 10 cent buy price + buy-price :accessor cc-buy-price-of + :col-type :integer + :initform 0.10 + :inflate #'currency-inflate + :deflate #'currency-deflate) + (;; 50 cent sell price + sell-price :accessor cc-sell-price-of + :col-type :integer + :initform 0.50 + :inflate #'currency-inflate + :deflate #'currency-deflate) + (url :accessor cc-url-of + :col-type (or :text :null)) + (barcode :accessor cc-barcode-of + :col-type (or :text :null)) + (manufacturer-sku :accessor cc-manufacturer-sku-of + :col-type (or :text :null)) + ;; Some cards don't have an ASIN: Prize Cards, for example. + ;; + ;; ASIN is a built in LISP function so this seems to be causing + ;; issues with Djula. I renamed it to AMAZON-ASIN. + (amazon-asin :accessor cc-asin-of + :col-type (or :text :null)) + (msrp :accessor cc-msrp-of + :col-type (or :integer :null) + :inflate #'currency-inflate + :deflate #'currency-deflate) + (brand :accessor cc-brand-of + :col-type (or :text :null)) + ;; Weight in pounds, lb, lbs.. silly + (weight :accessor cc-weight-of + :col-type :text + :initform "0.0625") + (description :accessor cc-description-of + :col-type (or :text :null)) + (max-qty :accessor cc-max-qty-of + :col-type (or :integer :null)) + (domestic-only :accessor cc-domestic-only-of + :col-type :integer + :initform 0) + (tax-exempt :accessor cc-tax-exempt-of + :col-type :integer + :initform 0)) + (:metaclass registered-table-class) + (:documentation "The base data from CrystalCommerce. This is what's available in a CSV export. + +These fields can be updated from CC CSV Export Search feature. + +The product descriptors. Contains information about the actual content +of the card. None of this is available via CC CSV Export Feature. This +information has to be updated aftewards. The Mass Updater may have +some hints as to how this works. + +Other stuff that doesn't appear in any export or search feature, but on YGOProDeck, which we will probably take info from.")) + +;; Allow bogus keys so we can just apply #'make-instance 'cc-item _parsed in web view +; (defmethod initialize-instance :after ((cc-item cc-item) &key &allow-other-keys)) + +(defclass ygo-cc-item () + ((item :accessor cc-item-of + :col-type cc-item) + (variant :accessor cc-variant-of + :col-type variant-condition) + (qty :accessor cc-qty-of + :col-type :integer + :initform 0) + (opt-qty :accessor cc-opt-qty-of + :col-type :integer + :initform 0) + (;; 10 cent buy price + buy-price :accessor cc-buy-price-of + :col-type :integer + :initform 0.10 + :inflate #'currency-inflate + :deflate #'currency-deflate) + (;; 50 cent sell price + sell-price :accessor cc-sell-price-of + :col-type :integer + :initform 0.50 + :inflate #'currency-inflate + :deflate #'currency-deflate)) + (:metaclass registered-table-class) + (:documentation "CC-ITEM + VARIANT relationship table.")) + + +(defmacro cc-table-delete (&body clauses) + `(delete-from 'cc-item ,@clauses)) + +(defmacro cc-table-select (&body body) + `(select-dao 'cc-item ,@body)) + +(defun cc-table-select-count (&optional fields-and-values) + (count-dao 'cc-item fields-and-values)) + + +;; TODO toolkit/db.lisp maybe? +(defun zero-field (class field &optional clauses) + "Set the specified FIELD of CLASS zero. You may additionally specify CLAUSES." + (with-connection (db) + (with-transaction + (mito:execute-sql + (sxql:update (sxql:make-sql-symbol + (mito.dao::table-name (find-class class))) + (sxql:set= (sxql:make-sql-symbol field) 0) + clauses))))) + +(defun set-buy-sell-price (&optional (buy-price 10) (sell-price 50)) + "This bypasses MITO:DEFLATE (the argument is in CENTS)." + (with-connection (db) + (with-transaction + (mito:execute-sql + (sxql:update :cc_item + (sxql:set= (sxql:make-sql-symbol "buy_price") buy-price + (sxql:make-sql-symbol "sell_price") sell-price)) + (sxql:update :ygo_cc_item + (sxql:set= (sxql:make-sql-symbol "buy_price") buy-price + (sxql:make-sql-symbol "sell_price") sell-price)))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun cc-select-by-id (id &rest args) + (apply #'find-dao 'cc-item :id id args)) + +(defun cc-select-by-passcode (passcode &rest args) + (apply #'find-dao 'cc-item :passcode passcode args)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; INSERT + +(defun cc-create (&rest args) + (apply #'create-dao 'cc-item args)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; DELETE +(defun cc-delete-by-id (id &rest args) + (apply #'delete-by-values 'cc-item :id id args)) diff --git a/src/models/feedback.lisp b/src/models/feedback.lisp new file mode 100644 index 0000000..6b9e02d --- /dev/null +++ b/src/models/feedback.lisp @@ -0,0 +1,27 @@ +#| + +User Feedback Module + +TODO User Messaging Module + +|# + +(in-package #:cl-deck-builder2.models.feedback) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass feedback () + ((user :accessor feedback-user-of + :col-type user + :initarg :user) + (body :accessor feedback-body-of + :col-type :text + :initarg :body)) + (:metaclass registered-table-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defgeneric create-feedback (user body) + (:documentation "Use CREATE-DAO to construct a new FEEDBACK instance. Return the instance, if successful, NIL otherwise.") + (:method ((user user) (body string)) + (create-dao 'feedback + :user user + :body body))) diff --git a/src/models/generics.lisp b/src/models/generics.lisp new file mode 100644 index 0000000..2a2e8ae --- /dev/null +++ b/src/models/generics.lisp @@ -0,0 +1,40 @@ +#| + +When I started this file I was going to generalize EVERY method then I +realized that's too time consuming. So I am going to generalize +methods as they conflict. Hybrid Power! + +There were some issues with merging symbols, I was using +:SHADOWING-IMPORT-FROM which is probably not correct. So instead, what +we do, is, every package that gets :USE-REEXPORTed, we :USE +#:CL-DECK-BUILDER2.MODELS.GENERICS. + +|# + +(in-package #:cl-deck-builder2.models.generics) + +;; Get the NAME slot of an object +(defgeneric name-of (obj)) + +;; Get the PASSCODE slot of an object +(defgeneric ygo-passcode-of (obj)) + +;; Get the Containing Deck ID of the DECK-ITEM, CONSTRUCTED-DECK-ITEM +(defgeneric deck-id-of (obj)) + +;; Get the PRICE slot of an object +(defgeneric ygo-price-of (obj)) + +(defgeneric ygo-archetype-of (obj)) +(defgeneric ygo-atk-of (obj)) +(defgeneric ygo-attribute-of (obj)) +(defgeneric ygo-def-of (obj)) +(defgeneric ygo-description-of (obj)) +(defgeneric ygo-frame-type-of (obj)) +(defgeneric ygo-level-of (obj)) +(defgeneric ygo-linkmarkers-of (obj)) +(defgeneric ygo-linkval-of (obj)) +(defgeneric ygo-passcode-of (obj)) +(defgeneric ygo-race-of (obj)) +(defgeneric ygo-scale-of (obj)) +(defgeneric ygo-type-of (obj)) diff --git a/src/models/label-maker.lisp b/src/models/label-maker.lisp new file mode 100644 index 0000000..188ecfa --- /dev/null +++ b/src/models/label-maker.lisp @@ -0,0 +1,136 @@ +#| + +Conceptually, The Label Maker is very similar to the DRAW component of The Deck Builder. + +We currently support Avery 5160 and ULINE S-20246 labels. + +|# + +(in-package #:cl-user) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This package doesn't touch the DB ... yet +(defpackage #:cl-deck-builder2.models.label-maker + (:use :cl) + (:import-from #:cl-deck-builder2.toolkit + #:latex-escape) + (:export #:latex-label + #:latex-label-page + #:latex-label-uline-s-20247 + #:latex-label-avery-5160 + #:render-page)) + +(in-package #:cl-deck-builder2.models.label-maker) + +(defclass latex-label () + ((barcode :accessor latex-label-barcode + :initform nil + :initarg :barcode) + (description :accessor latex-label-description + :initform nil + :initarg :description) + (price :accessor latex-label-price + :initform nil + :initarg :price)) + (:documentation "An individual LaTeX label. Encapsulate metadata for a particular label.")) + +(defclass latex-label-page () + ((;; List of LATEX-LABEL content + labels :accessor latex-label-page-labels + :initarg :page-labels + :initform '()) + (;; Number of Labels this page thinks it has + labels-length :accessor latex-label-page-labels-length + :initarg :labels-length + :initform 0) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Templates + (;; Template for this page + label-template :accessor latex-label-label-template + :initform nil + :initarg :label-template) + (;; Header template for this page + header-template :accessor latex-label-header-template + :initform #P"latex/header.tex" + :initarg :header-template)) + (:documentation "Class for a LaTeX Label Page, which encapsulates label information and additional output templating information.")) + +(defclass latex-label-uline-s-20247 (latex-label-page) + ((labels-length :initform 32) + (label-template :initform #P"latex/ULINE-S-20247.tpl.tex"))) + +(defclass latex-label-avery-5160 (latex-label-page) + ((labels-length :initform 30) + (label-template :initform #P"latex/Avery-5160.tpl.tex"))) + +(defmethod initialize-instance :after ((page latex-label-page) &rest initargs &key csv &allow-other-keys) + (declare (ignore initargs)) + (when csv + (initialize-with-csv page csv))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod add-label ((page latex-label-page) (label latex-label)) + "Add a LABEL to PAGE." + (push label (latex-label-page-labels page))) + +(defmethod initialize-with-csv ((page latex-label-page) pathspec) + "Create LATEX-LABEL information for this LATEX-LABEL-PAGE by reading CSV information from PATHSPEC." + (let ((csv (cl-csv:read-csv pathspec))) + (loop for i from 1 upto (latex-label-page-labels-length page) do + (destructuring-bind (label-no description price barcode) + (elt csv i) + (declare (ignore label-no)) + (when (> (length description) 0) + (barcode-png barcode) + (let ((label (ignore-errors + (make-instance 'latex-label + :barcode barcode + :description (latex-escape description) + :price (cl-ppcre:regex-replace "\\$" price "\\\\$"))))) + (add-label page label))))))) + +(defmethod render-page ((page latex-label-page)) + "Use Djula to render the LATEX-LABEL-LABEL-TEMPLATE." + (cl-deck-builder2.view:render (latex-label-label-template page) + (list :labels (latex-label-page-labels page)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun label-info (&optional t-or-nil) + (let ((arg (if t-or-nil "true" "false"))) + (format nil "\LabelGrid~a\LabelInfo~a%%~%" arg arg))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO (defclass barcode () ()) +(defun barcode-png (barcode &optional (directory #P"/tmp/labels/") (size "1x30")) + (ensure-directories-exist directory) + (let* ((barcode (etypecase barcode + (string barcode) + (integer (princ-to-string barcode)))) + (output (merge-pathnames + (make-pathname :name barcode :type "png") + (probe-file directory)))) + (unless (probe-file output) + (and + (> (length barcode) 0) + (multiple-value-bind (stdout stderr rc) + (inferior-shell:run + `("ZXingWriter" -size ,size + ,(case (length barcode) + ;; 12 and 11 digit barcodes for products in store on the shelves usually - UPC-A + ((12 11) 'UPC-A) + ;; 10 digit barcodes for "fake" Amazon ASIN "X0037THWV7" + (10 'Code128) + ;; Not sure why we have UPC-E in here + ;; but presumbly we have encountered + ;; products with a 6 digit barcode... + (6 'UPC-E) + ;; Treat everything else as 13 digit + ;; international EAN-13 barcodes like + ;; stuff from Pokemon Stores: + ;; 8206508090648 + (t 'EAN-13)) + ,barcode + ,output)) + (declare (ignore stdout stderr)) + (eq rc 0)) + (probe-file output))))) diff --git a/src/models/mtg.lisp b/src/models/mtg.lisp new file mode 100644 index 0000000..bc9f4f9 --- /dev/null +++ b/src/models/mtg.lisp @@ -0,0 +1,65 @@ +#| + +scryfall API parser + +WIP + +This downloads the API data. We haven't even parsed that data +yet. It's hundreds of GB. + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.models.mtg + (:use :cl)) + +(in-package #:cl-deck-builder2.models.mtg) + +(defparameter *base-scryfall-api-uri* "https://api.scryfall.com/") + +(defun scryfall-api-uri (path) + (format nil "~a~a" *base-scryfall-api-uri* path)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar *scryfall-api-data* nil + "A list of SCRYFALL-API-DATA objects.") + +(defclass scryfall-api-data () + ((|content_encoding| :accessor scryfall-content-encoding :initarg :|content_encoding|) + (|content_type| :accessor scryfall-content-type :initarg :|content_type|) + (|description| :accessor scryfall-description :initarg :|description|) + (|download_uri| :accessor scryfall-download-uri :initarg :|download_uri|) + (|id| :accessor scryfall-id :initarg :|id|) + (|name| :accessor scryfall-name :initarg :|name|) + (|object| :accessor scryfall-object :initarg :|object|) + (|size| :accessor scryfall-size :initarg :|size|) + (|type| :accessor scryfall-type :initarg :|type|) + (|updated_at| :accessor scryfall-updated-at :initarg :|updated_at|) + (|uri| :accessor scryfall-uri :initarg :|uri|))) + +(defun get-scryfall-data () + (let ((json (getf + (jonathan:parse + (flex:octets-to-string + (drakma:http-request (scryfall-api-uri "bulk-data")))) + :|data|))) + (mapcar (lambda (row) + (apply #'make-instance 'scryfall-api-data row)) + json))) + +(defun load-scryfall-data () + (setf *scryfall-api-data* + (get-scryfall-data))) + +(defun size-in-gb (size) + (float (/ size (* 1024 1024 1024)))) + +(defun get-bulk-data-uris (path) + (with-open-file (out path + :direction :output + :if-exists :supersede + :if-does-not-exist :create) + (format out "~{~A~%~}" + (mapcar #'scryfall-download-uri *scryfall-api-data*)))) diff --git a/src/models/packages.lisp b/src/models/packages.lisp new file mode 100644 index 0000000..4ee7747 --- /dev/null +++ b/src/models/packages.lisp @@ -0,0 +1,502 @@ +#| + +src/models/packages.lisp + +Models Main Package List + +|# + +(in-package #:cl-user) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.generics + (:use #:cl) + (:export #:deck-id-of + #:name-of + #:ygo-archetype-of + #:ygo-atk-of + #:ygo-attribute-of + #:ygo-def-of + #:ygo-description-of + #:ygo-frame-type-of + #:ygo-level-of + #:ygo-linkmarkers-of + #:ygo-linkval-of + #:ygo-passcode-of + #:ygo-price-of + #:ygo-race-of + #:ygo-scale-of + #:ygo-type-of)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.registered-table + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics) + (:import-from #:cl-deck-builder2.toolkit + #:relative-pathname) + (:export #:registry + #:registered-classes + #:registered-table-class + #:ensure-tables-exist + #:migration-expressions + #:migrate-table + #:export-registered-classes + #:export-registered-classe-to-file)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.attachment + (:use #:cl + #:cl-deck-builder2.models.generics) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-table-class) + (:import-from #:cl-deck-builder2.db + #:create-dao) + (:export #:attachment + #:create-attachment + #:attachment-valid-p)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.category + (:use #:cl + #:cl-deck-builder2.models.generics) + (:import-from #:cl-deck-builder2.db + #:create-dao + #:select-dao) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-table-class) + (:export #:category + #:category-name-of + #:category-left-of + #:category-right-of + + #:category-rename + + #:category-insert-right-of + #:category-insert-new-child + #:category-insert-fake-data + #:category-full-tree + #:category-leaf-nodes + #:category-full-tree-format + #:category-delete-tree + #:category-delete-and-reparent + #:category-tree-depth + #:category-node-depth + #:category-subtree-depth + #:category-subtree-max-depth + + #:by-category)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.ygoprodeck.fields + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-table-class) + (:export #:variant + #:name-of + #:define-variant-list + #:variant-condition + #:variant-language + #:ygo-banlist-name + #:ygo-format-name + #:ygo-linkmarker-name + #:ygo-set-rarity-code)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.ygoprodeck + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.registered-table) + (:export :ygo-info + #:ygo-description-of + #:ygo-frame-type-of + #:ygo-type-of + #:ygo-passcode-of + #:name-of + #:ygo-race-of + #:ygo-archetype-of + #:ygo-attribute-of + #:ygo-linkmarkers-of + #:ygo-atk-of + #:ygo-def-of + #:ygo-level-of + #:ygo-linkval-of + #:ygo-scale-of)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.ygoprodeck.classes + (:use #:cl + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.ygoprodeck.fields) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-table-class) + (:import-from #:cl-deck-builder2.models.ygoprodeck.fields + #:variant-condition + #:ygo-banlist-name + #:ygo-format-name + #:ygo-linkmarker-name + #:ygo-set-rarity-code) + (:import-from :cl-deck-builder2.toolkit + :currency-deflate + :currency-inflate) + (:export #:ygo-card-name + #:ygo-card-type + #:ygo-card-frame-type + #:ygo-card-race + #:ygo-card-archetype + #:ygo-card-attribute + #:ygo-card + #:ygo-card-misc-info + #:ygo-set-name + #:ygo-set-code + #:ygo-set-rarity-code + #:ygo-set-rarity + #:ygo-set-edition + #:ygo-set + #:ygo-set-item + #:ygo-linkmarker + #:ygo-format + ;; #:ygo-banlist-name + ;; #:ygo-format-name + ;; #:ygo-linkmarker-name + #:ygo-banlist + #:ygo-alternative-artwork + #:ygo-price + + ;; Accessor Methods + #:ygo-alternate-id-of + #:ygo-archetype-of + #:ygo-atk-of + #:ygo-attribute-of + #:ygo-ban-goat + #:ygo-ban-ocg + #:ygo-ban-tcg + #:ygo-beta-id-of + #:ygo-beta-name-of + #:ygo-card-type-of + #:ygo-code-of + #:ygo-common-charity-format + #:ygo-def-of + #:ygo-desc-of + #:ygo-downvotes-of + #:ygo-duel-links-format + #:ygo-edison-format + #:ygo-edition-of + #:ygo-frame-type-of + #:ygo-goat-format + #:ygo-has-effect + #:ygo-is-staple + #:ygo-konami-id-of + #:ygo-level-of + #:ygo-linkmarker-bottom + #:ygo-linkmarker-bottom-left + #:ygo-linkmarker-bottom-right + #:ygo-linkmarker-left + #:ygo-linkmarker-right + #:ygo-linkmarker-top + #:ygo-linkmarker-top-left + #:ygo-linkmarker-top-right + #:ygo-name-of + #:ygo-ocg-date-of + #:ygo-ocg-format + #:ygo-ocg-goat-format + #:ygo-passcode-of + #:ygo-question-atk + #:ygo-question-def + #:ygo-race-of + #:ygo-rarity-of + #:ygo-scale-of + #:ygo-set-code-of + #:ygo-set-rarity-code-of + #:ygo-speed-duel-format + #:ygo-tcg-date-of + #:ygo-tcg-format + #:ygo-treated-as + #:ygo-upvotes-of + #:ygo-url-of + #:ygo-views-of + #:ygo-viewsweek-of + #:ygo-amazon-price-of + #:ygo-cardmarket-price-of + #:ygo-coolstuffinc-price-of + #:ygo-ebay-price-of + #:ygo-tcgplayer-price-of + #:ygo-price-of + + #:qty-of + #:item-of + #:variant-of + #:sell-price-of + #:opt-qty-of + #:buy-price-of) + (:documentation "New YGOProDeck database schema - classes.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.ygoprodeck.methods + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.ygoprodeck.classes + #:cl-deck-builder2.models.ygoprodeck.fields) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-classes + #:registry) + (:export #:print-object + #:split-code + #:ygo-card + #:ygo-card-by-name + #:ygo-card-by-passcode + #:ygo-card-names-by-deck-id + #:ygo-card-names-from-list + #:ygo-card-sets + #:ygo-set + #:ygo-set-by-id + #:ygo-set-by-code + #:ygo-set-by-name + #:ygo-set-by-passcode + #:ygo-select-set-item) + (:documentation "New YGOProDeck database schema - methods.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.ygoprodeck.cardinfo + (:use #:cl) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:cardinfo + #:cardinfo-input + #:cardinfo-output + #:cardinfo-input-exists-p + #:cardinfo-output-exists-p + #:cardinfo-json + #:cardinfo-list + #:cardinfo-download + #:cardinfo-cleanup + #:cardinfo-convert + #:cardinfo-update-and-cleanup)) + +(defpackage #:cl-deck-builder2.models.ygoprodeck.json + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.toolkit + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.ygoprodeck.fields + #:cl-deck-builder2.models.ygoprodeck.cardinfo + #:cl-deck-builder2.models.ygoprodeck.classes) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:cardinfo-import)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.user + (:use #:cl + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.db) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-table-class) + (:import-from :mito-auth + :has-secure-password) + (:export #:user + #:name-of + #:user-email-of + #:user-roles-of + + #:role + + #:user-role + #:user-role-user + #:user-role-role + + #:user-admin-p + #:user-verified-p + + #:create-user + #:find-user + #:create-role + #:add-role)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.feedback + (:use #:cl) + (:import-from #:cl-deck-builder2.db + #:create-dao) + (:import-from #:cl-deck-builder2.models.registered-table + #:registered-table-class) + (:import-from #:cl-deck-builder2.models.user + #:user) + (:export #:feedback + #:feedback-body-of + #:feedback-user-of + #:create-feedback)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This package doesn't touch the DB ... yet +(defpackage #:cl-deck-builder2.models.qr-code + (:use :cl + :cl-deck-builder2.db) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:import-from :cl-deck-builder2.toolkit + :query-param) + (:export :qr-settings + :qr-string + :qr-min-version + :qr-ec-level + :qr-encoding-mode + :qr-mask-number + :qr-to-plist + :qr-generate)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.ydk + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.registered-table + #:cl-deck-builder2.toolkit) + (:import-from #:cl-deck-builder2.models.category + #:category) + (:import-from #:cl-deck-builder2.models.ygoprodeck + #:ygo-info) + (:export #:ydk + #:deck-item + #:ydk-deck + + #:deck-id-of + #:deck-kind-of + #:deck-passcode-of + #:ydk-created-by + #:ydk-name-of + #:ydk-id-of + #:ydk-deck-delete-from + #:ydk-deck-item-delete-from + #:ydk-deck-delete-by-id + #:ydk-deck-delete-by-name + #:ydk-deck-by-id + #:ydk-deck-by-name + #:ydk-deck-info-by-id + #:ydk-rename-deck + #:ydk-add + #:ydk-clear + #:ydk-sync + + #:ydk-main-deck-of + #:ydk-extra-deck-of + #:ydk-side-deck-of + #:ydk-category-of + + #:ydk-all + #:ydk-concatenate + #:ydk-unique + #:ydk-query + #:ydk-sorted + + #:ydk-delete + #:ydk-delete-index + + #:ydk-table-ensure-exists + #:ydk-table-drop + #:ydk-table-delete + #:ydk-table-select + #:ydk-table-select-count + + #:ydk-to-kde) + (:documentation "This model is just a thin wrapper around YDK format. + +The YDK Format is like this: + + created by... + #main + + #extra + + !side + + +TODO CL-YDK + YDK-STORAGE-MIXIN?")) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Psuedo class for working with the SQLite database. +;; This will probably go away once we switch over to MariaDB. +(defpackage #:cl-deck-builder2.models.sqlite-schema + (:use :cl + :cl-deck-builder2.db) + (:export #:sqlite-schema + #:kind-of + #:sql-of + #:rootpage-of + #:table-names)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.models.constructed-decks + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.toolkit + #:cl-deck-builder2.models.generics + #:cl-deck-builder2.models.registered-table + #:cl-deck-builder2.models.ygoprodeck.classes + #:cl-deck-builder2.models.ygoprodeck.methods) + (:import-from #:cl-deck-builder2.models.category + #:category) + (:import-from #:cl-deck-builder2.models.ydk + #:deck-item + #:deck-id-of + #:deck-passcode-of + #:ydk-category-of + #:ydk-created-by + #:ydk-name-of + #:ydk-deck) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:constructed-deck + #:constructed-deck-item + #:constructed-deck-intermediate + + #:cdi-deck-item + #:cdi-deck-set + #:cdi-deck-set-item + #:cdi-deck-condition + #:cdi-deck-card + + #:constructed-deck-as-cdi-list + + ;; :deck-category-of + ;; :deck-created-by + ;; :deck-id-of + :deck-name-of + ;; :deck-sell-price + :deck-sell-price-of + :deck-sold + ;; :ydk-deck-of + ;; :ygo-card-of + + ;; #:sold-deck + + #:ydk-deck-pull-from-inventory + #:find-any-invalid-qtys + #:deck-to-pull-set-items-qty-as-alist + #:deck-to-pull-desired-qty-as-alist + #:subtract-desired-from-set-item-qty-as-alist + #:valid-pull-p + + #:cdi-pull-from-inventory)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(uiop:define-package #:cl-deck-builder2.models + (:use #:cl) + (:use-reexport #:cl-deck-builder2.models.attachment + #:cl-deck-builder2.models.category + #:cl-deck-builder2.models.constructed-decks + #:cl-deck-builder2.models.feedback + #:cl-deck-builder2.models.qr-code + #:cl-deck-builder2.models.registered-table + #:cl-deck-builder2.models.sqlite-schema + #:cl-deck-builder2.models.user + #:cl-deck-builder2.models.ydk + #:cl-deck-builder2.models.ygoprodeck + #:cl-deck-builder2.models.ygoprodeck.fields + #:cl-deck-builder2.models.ygoprodeck.cardinfo + #:cl-deck-builder2.models.ygoprodeck.json + ;; TODO replace ygoprodeck with ygoprodeck2 + #:cl-deck-builder2.models.ygoprodeck.classes + #:cl-deck-builder2.models.ygoprodeck.methods)) diff --git a/src/models/qr-code.lisp b/src/models/qr-code.lisp new file mode 100644 index 0000000..7795349 --- /dev/null +++ b/src/models/qr-code.lisp @@ -0,0 +1,100 @@ +#| + +QR Code Model + +Constantly fixing their user input was getting janky. DEFCLASS to the rescue! + +TODO This is still pretty clumbsy. I'm generating HTML in here?? + +Originally I was using INITIALIZE-INSTANCE but that would clobber the +object with the DEFAULT-INITARGS. Now we're overriding +REINITIALIZE-INSTANCE, which seems to work exactly as you'd expect. + +|# + +(in-package #:cl-deck-builder2.models.qr-code) + +;; XXX +(defparameter *qr-public-file* #P"~/public/qr.png") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass qr-settings () + ((string :accessor qr-string + :initarg :string) + (min-version :accessor qr-min-version + :initarg :min-version) + (ec-level :accessor qr-ec-level + :initarg :ec-level) + (encoding-mode :accessor qr-encoding-mode + :initarg :encoding-mode) + (mask-number :accessor qr-mask-number + :initarg :mask-number) + (output-path :accessor qr-output-path + :initarg :output)) + (:default-initargs + :ec-level :L + :encoding-mode :8-BIT-BYTE + :mask-number -1 + :min-version 1 + :output #P"/tmp/qr.png" + :string "Hello World")) + +(defmethod reinitialize-instance :after ((qr-settings qr-settings) &rest initargs &key content &allow-other-keys) + (declare (ignore initargs)) + (when content + (reinitialize-with-content qr-settings content))) + +;; Overwrite existing slots even if they're SLOT-BOUNDP with SLOT-VALUE +(defmethod reinitialize-with-content ((qr-settings qr-settings) content) + ;; TODO ALIST-PARAMS-to-class? The idea is there... + (alexandria:if-let ((string (query-param "string" content))) + (setf (qr-string qr-settings) string)) + + (alexandria:if-let ((min-version (query-param "min-version" content))) + (setf (qr-min-version qr-settings) (parse-integer min-version))) + + (alexandria:if-let ((ec-level (query-param "ec-level" content))) + (setf (qr-ec-level qr-settings) (alexandria:make-keyword ec-level))) + + (alexandria:if-let ((encoding-mode (query-param "encoding-mode" content))) + (setf (qr-encoding-mode qr-settings) (alexandria:make-keyword encoding-mode))) + + (alexandria:if-let ((mask-number (query-param "mask-number" content))) + (setf (qr-mask-number qr-settings) (parse-integer mask-number))) + qr-settings) + +;; TODO %from-plist +;; TODO %from-alist? +(defmethod qr-to-plist ((qr-settings qr-settings) &optional html) + (let ((params + (list :ec-level (qr-ec-level qr-settings) + :encoding-mode (qr-encoding-mode qr-settings) + :mask-number (qr-mask-number qr-settings) + :min-version (qr-min-version qr-settings)))) + (when html + (setf (getf params :string) (qr-string qr-settings) + (getf params :ec-level) (princ-to-string (getf params :ec-level)) + (getf params :encoding-mode) (princ-to-string (getf params :encoding-mode)))) + params)) + +(defmethod qr-generate ((qr qr-settings)) + (v:info :qr "QR-STRING: ~a" (qr-string qr)) + + (v:info :qr "UIOP:DELETE-FILE-IF-EXISTS: ~a" + (ignore-errors + (mapcar #'uiop:delete-file-if-exists '(#P"/tmp/qr.png" #P"~/public/qr.png")))) + + (v:info :qr "LISPQR:ENCODE->IMAGE: ~a" + (ignore-errors + (apply #'lispqr:encode->image (qr-string qr) (qr-output-path qr) + (qr-to-plist qr)))) + + (v:info :qr "UIOP:COPY-FILE: ~a" + (ignore-errors + (uiop:copy-file (qr-output-path qr) *qr-public-file*))) + + (if (probe-file *qr-public-file*) + (qr-format-html (get-universal-time)))) + +(defun qr-format-html (when) + (format nil "\"QR" when)) diff --git a/src/models/registered-table-mixin.lisp b/src/models/registered-table-mixin.lisp new file mode 100644 index 0000000..162eacf --- /dev/null +++ b/src/models/registered-table-mixin.lisp @@ -0,0 +1,99 @@ +#| + +src/models/registered-table-mixin.lisp + +Model Object Class. Instead of writing new code every time for every +new Model (Model, View, Component), encapsulate the behavior here. + +Now using mixins! + +|# + +(in-package #:cl-deck-builder2.models.registered-table) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Thanks mariari! https://lispcookbook.github.io/cl-cookbook/clos.html#metaclasses +(defclass model-registry () + ((registry :allocation :class + :accessor model-registry + :initform (make-hash-table)))) + +(defmethod registered-classes ((registry model-registry) &optional package) + (if package + (gethash (find-package package) (model-registry registry)) + ;; If no package was provided return a LIST of all packages tracked by the MODEL-REGISTRY. + (apply #'concatenate 'list + (alexandria:hash-table-values (model-registry registry))))) + +(defparameter *registry* (make-instance 'model-registry) + "Global model registry instance.") + +(defun registry (&optional (registry *registry*)) + "Get the MODEL-REGISTRY associated with REGISTRY. Default is *REGISTRY*." + registry) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Inherit from MITO:DAO-TABLE-CLASS because MITO:DAO-TABLE-MIXIN +;; doesn't have the additional PRIMARY-KEY or TIMESTAMP mechanisms or +;; anything. +(defclass registered-table-class (mito:dao-table-class) ()) + +(defmethod closer-mop:validate-superclass ((class registered-table-class) + (superclass mito:dao-table-class)) + t) + +(defmethod initialize-instance :after ((class registered-table-class) &rest initargs &key &allow-other-keys) + (declare (ignore initargs)) + (pushnew (class-name class) + (gethash (symbol-package (class-name class)) + (model-registry (registry))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tables +(defmethod create-tables ((registry model-registry) &optional package) + "Create tables for CLASS-LIST by wrapping DBD:CREATE-TABLE." + (apply #'create-table + (registered-classes registry package))) + +(defmethod drop-tables ((registry model-registry) &optional package) + "Drop tables for CLASS-LIST by wrapping DB:DROP-TABLE." + (apply #'drop-table + (registered-classes registry package))) + +(defmethod recreate-tables ((registry model-registry) &optional package) + (drop-tables registry package) + (create-tables registry package)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod migration-expressions ((registry model-registry) &optional package) + (with-connection (db) + (with-transaction + (mapcar #'mito:migration-expressions + (registered-classes registry package))))) + +(defmethod migrate-table ((registry model-registry) &optional package) + (with-connection (db) + (with-transaction + (mapcar #'mito:migrate-table + (registered-classes registry package))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun ensure-tables-exist (&optional package) + (create-tables (registry) package)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod export-registered-classes ((registry model-registry) &key package (stream *standard-output*)) + "Export REGISTERED-CLASSES for REGISTRY to STREAM, which by default, is *STANDARD-OUTPUT*." + (with-connection (db) + (with-transaction + (format stream "~{~a~^;~%~};" + (mapcar (lambda (class) (sxql:yield (car (mito:table-definition class)))) + (registered-classes registry package)))))) + +(defun export-registered-classes-to-file (&key (output (relative-pathname "db/schema.sql")) package) + (with-open-file (stream output + :direction :output + :if-exists :supersede + :if-does-not-exist :create) + (export-registered-classes (registry) :package package :stream stream) + (probe-file output))) diff --git a/src/models/search.lisp b/src/models/search.lisp new file mode 100644 index 0000000..aeeff64 --- /dev/null +++ b/src/models/search.lisp @@ -0,0 +1,76 @@ +#| + +Search Session Object + +TODO + +The idea was I basically just wanted to copy the code from web/search.lisp + +Maybe each database object could have their own search sessions? Like +the web deck builder could subclass SEARCH-SESSION with the correct +DEFAULT-INITARGS and relevant CLASS info + +|# + +(defpackage #:cl-deck-builder2.models.search + (:use #:cl)) + +(in-package #:cl-deck-builder2.models.search) + +;; TODO Where does this go? +(defparameter +search-param-whitelist+ + '("amazon-asin" "barcode" "brand" "buy-price" "category" "code" "created-by" + "deck-id" "desc" "description" "domestic-only" "edition" "email" "id" "kind" + "linkmarkers" "linkval" "manufacturer-sku" "max-qty" "msrp" "parent" "price" + "product-name" "rarity" "rarity-code" "sell-price" "tax-exempt" "total-qty" + "url" "weight" "wishlists" "atk" "def" "level" "scale" "passcode" "type" + "frame-type" "race" "attribute" "archetype" "name" "opt-qty" "qty" "condition") + "A whitelist of keywords allowed by user input on the database side.") + +(defclass search-session-base () ()) + +;; TODO SLOTs or MAKE-HASH-TABLE? Or an ALIST? +(defclass search-session (search-session-base) + ((direction) + (limit) + (offset) + (sort-by) + (variant)) + (:default-initargs + :direction "asc" + :limit 10 + :offset 0 + :sort-by "id")) + +(defmethod initialize-instance :after ((obj search-session) &rest initargs &key &allow-other-keys) + (declare (ignore initargs))) + +;; FILTER-ALIST +(defgeneric search-query-filter (obj)) + +;; MAKE-SEARCH-QUERY +(defgeneric search-query-exec (obj)) + +;; MAKE-COUNT-QUERY +(defgeneric search-query-count (obj)) + +;; MAKE-ORDER-BY +(defgeneric search-query-order-by (obj direction sort-by)) + +;; MAKE-WHERE-CLAUSE +(defgeneric search-where-and (obj direction sort-by)) + +(defun make-sql-clause (op field value) + (let ((op (typecase op + (symbol op) + (string (alexandria:make-keyword (string-upcase + (substitute #\_ #\- op)))))) + (field (typecase field + (symbol field) + (string (alexandria:make-keyword (string-upcase + (substitute #\_ #\- field))))))) + (list op field (if (eq op :like) + (format nil "%~a%" value) + value)))) + +;; (defun make-kind (kind alist)) diff --git a/src/models/sqlite-schema.lisp b/src/models/sqlite-schema.lisp new file mode 100644 index 0000000..149df70 --- /dev/null +++ b/src/models/sqlite-schema.lisp @@ -0,0 +1,40 @@ +#| + +SQLite has a built-in schema table where you can do a level of +introspection on the database. I've used this functionality a few +times now so this encapsulates that behavior. + +|# + +(in-package #:cl-deck-builder2.models.sqlite-schema) + +;; https://www.sqlite.org/schematab.html +(defclass sqlite-schema () + ((type :col-type :text) + (name :col-type :text) + (tbl-name :col-type :text) + (rootpage :col-type :integer) + (sql :col-type :text)) + (:auto-pk nil) + (:record-timestamps nil) + (:metaclass mito:dao-table-mixin)) + +(defgeneric kind-of (obj) + (:method ((sqlite-schema sqlite-schema)) + (slot-value sqlite-schema 'type))) + +(defmethod name-of ((sqlite-schema sqlite-schema)) + (slot-value sqlite-schema 'name)) + +(defgeneric rootpage-of (obj) + (:method ((sqlite-schema sqlite-schema)) + (slot-value sqlite-schema 'rootpage))) + +(defgeneric sql-of (obj) + (:method ((sqlite-schema sqlite-schema)) + (slot-value sqlite-schema 'sql))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun table-names () + (mapcar #'name-of + (select-dao 'sqlite-schema))) diff --git a/src/models/user.lisp b/src/models/user.lisp new file mode 100644 index 0000000..8d96668 --- /dev/null +++ b/src/models/user.lisp @@ -0,0 +1,127 @@ +#| + +src/models/user.lisp + +# User Interaction Backend + +## Create a superuser + +(create-superuser name email password) + +## Create a normal user + + (bookshops.models::create-user "Joe Blogg" "JoeBlogg@example.com" "i<3books") + +## Give him rights + + (add-role user :admin) + +Bootstrap roles: see database.lisp bootstrap-base-roles. + +# More Information + +- + +|# + +(in-package #:cl-deck-builder2.models.user) + +(defclass user (has-secure-password) + ((name :accessor name-of + :col-type :text + :initarg :name) + (email :accessor user-email-of + :col-type :text + :initarg :email)) + (:unique-keys email) + ;; (:primary-key email) + (:metaclass registered-table-class) + (:documentation "The USER class encapsulates information about the users of The Deck Builder: name, email, password, password salt, user roles.")) + +(defclass role () + ((name ;;:col-type :text + :col-type (:varchar 64) + :initarg :role + :inflate (alexandria:compose #'alexandria:make-keyword #'string-upcase) + :deflate #'string-downcase + :accessor name-of)) + (:documentation "The ROLE class encapsulates information about the roles of actions a user may perform, e.g. :ADMIN, :USER, :BANNED.") + (:metaclass registered-table-class) + (:primary-key name) + (:auto-pk nil) + (:record-timestamps nil)) + +(defclass user-role () + ((user :col-type user + :initarg :user + :accessor user-role-user) + (role :col-type role + :initarg :role + :accessor user-role-role)) + (:metaclass registered-table-class) + (:primary-key user role) + (:auto-pk nil) + (:record-timestamps nil)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun create-user (name email password) + "Create a USER object with NAME, EMAIL, and PASSWORD fields. Will automatically be synced to db via CREATE-DAO. Password hashing provided by MITO-AUTH." + (create-dao 'user :name name + :email email + :password password)) + +(defun find-user (email) + "Find a user by EMAIL." + (find-dao 'user :email email)) + +;; (defun create-superuser (name email password) +;; "Create a USER with the ADMIN role." +;; (let ((user (create-user name email password))) +;; (add-role user :admin))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO Role Viewer / Editor +(defun create-role (name) + "Create a ROLE called NAME. If the ROLE exists, return that, instead." + (with-connection (db) + (with-transaction + (or (mito:find-dao 'role :name name) + (mito:create-dao 'role :name name))))) + +(defun %find-role-from-keyword (role-name) + "Return a role object from a keyword and throw an error if an associated role cannot be found" + (let ((role (find-dao 'role :name role-name))) + (assert role (role) "There is no role named ~a" role-name) + role)) + +(defgeneric add-role (user role) + (:documentation "Add the given role to this user. ROLE is either a role object or a symbol. An example role is ':admin`.") + (:method ((user user) (role role)) + (or (find-dao 'user-role :user user :role role) + (create-dao 'user-role :user user :role role))) + (:method ((user user) (role-name symbol)) + (let ((role (%find-role-from-keyword role-name))) + (add-role user role)))) + +;; Retrieving roles from "user-role" table +(defmethod user-roles-of ((user user)) + (with-connection (db) + (with-transaction + (mapcar #'user-role-role + (mito:retrieve-dao 'user-role :user user))))) + +(defgeneric user-has-role (user role) + (:documentation "Given a USER and a ROLE, return T if ROLE is a MEMBER of USER-ROLES for USER, NIL otherwise.") + (:method ((user user) (role role)) + (member (name-of role) (user-roles-of user) :key #'name-of)) + (:method ((user user) (role-name symbol)) + (let ((role (%find-role-from-keyword role-name))) + (user-has-role user role)))) + +(defmethod user-admin-p ((user user)) + "Is USER an admin? Return T if so. NIL otherwise." + (user-has-role user :admin)) + +(defmethod user-verified-p ((user user)) + "Is USER in the VERIFIED role? Return T if so. NIL otherwise." + (user-has-role user :verified)) diff --git a/src/models/ydk.lisp b/src/models/ydk.lisp new file mode 100644 index 0000000..9248362 --- /dev/null +++ b/src/models/ydk.lisp @@ -0,0 +1,591 @@ +#| + +src/models/ydk/classes.lisp + +# YDK + +Yu-Gi-Oh! Pro Deck YDK Importer And Models + +TODO This can be a stand-alone package CL-YDK + the CL-YDK+MITO mixin probably. + +|# + +(in-package #:cl-deck-builder2.models.ydk) + +(defclass deck-item () + (;; 0 => Main Deck + ;; 1 => Extra Deck + ;; 2 => Side Deck + (deck-id :accessor deck-id-of :col-type :integer) + (kind :accessor deck-kind-of :col-type :integer) + (passcode :accessor deck-passcode-of :col-type :integer)) + (:metaclass registered-table-class) + (:documentation "DECK-LIST is a representation of a single card in a YDK deck in +SQL. Since SQL doesn't have a LIST type, we use a bunch of +these (60-90 rows generally make up a deck). The ID field supplied by +MITO:DAO-TABLE-CLASS is the \"Deck ID\" in the builder system.")) + +(defclass ydk-deck () + (;; (id :accessor ydk-id-of :col-type :integer :primary-key t) + (category :accessor ydk-category-of :col-type (or category :null)) + (created-by :accessor ydk-created-by :col-type :text :initarg :created-by) + (name :accessor ydk-name-of :col-type :text :initarg :name)) + (:unique-keys name) + (:metaclass registered-table-class) + (:documentation "A YDK-DECK is an in-database representation of a deck. It's really actually just an index to keep track of metadata, with the ID field used to key into DECK-ITEM. + +SQL doesn't allow us to store anything as a list. We take advantage +of Mito's INSERT-DAO, SELECT-DAO, etc. being methods which we can +define :BEFORE :AFTER and :AROUND methods for. The YDK-DECK class is +an intermediate representation of a deck. Bookeeping for the \"Deck +ID\", CREATED-BY, and NAME.")) + +(defclass ydk () + ((;; I think this is supposed to be the MITO:OBJECT-ID to keep tabs + ;; on the SYNC status... Looks like I never wrote it down, but I'm + ;; pretty sure that was my intention with this SLOT. It's been a + ;; pain point previously (PPP). + deck-id :accessor ydk-id-of :initarg :deck-id) + (created-by :accessor ydk-created-by :initarg :created-by :initform nil) + (name :accessor ydk-name-of :initarg :name :initform nil) + (main-deck :accessor ydk-main-deck-of :initarg :main-deck :initform '()) + (extra-deck :accessor ydk-extra-deck-of :initarg :extra-deck :initform '()) + (side-deck :accessor ydk-side-deck-of :initarg :side-deck :initform '())) + (:documentation "Intermediate YDK representation. + +This is what's stored in-memory during program operation, and then gets synced to the database. + +Notice this class does not draw from MITO:DAO-TABLE-CLASS.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helper functions +;; +;; TODO maybe I don't have to duplicate this data? +;; +;; I tried using defparameter but defmacro complained when I tried to +;; load it. +;; +;; Aha, The Original Variant. +;; This code needs to be incorporated into the VARIANT code. + +(defmacro %sql-to-keyword (type) + `(ecase ,type + (0 :main-deck) + (1 :extra-deck) + (2 :side-deck))) + +(defmacro %keyword-to-sql (type) + `(ecase ,type + (:main-deck 0) + (:extra-deck 1) + (:side-deck 2))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO I think I'm going to erase all these DEFUNs since they're kinda +;; outdated now with the changes I made in db.lisp +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete +(defun ydk-deck-delete-from (&rest clauses) + (apply #'delete-from 'ydk-deck clauses)) + +(defun ydk-deck-item-delete-from (&rest clauses) + (apply #'delete-from 'deck-item clauses)) + +(defun ydk-deck-delete-by-id (id) + "Remove all matching ID from YDK-DECK and DECK-ITEM, effectively erasing the deck from the database." + (ydk-deck-delete-from + (sxql:where (:= :id id))) + (ydk-deck-item-delete-from + (sxql:where (:= :deck_id id)))) + +(defun ydk-deck-delete-by-name (name) + (let ((found (ydk-deck-by-name name))) + (when found + ;; Delete from both the DECK-ITEM and the YDK-DECK reference + ;; table. The :deck_item table doesn't have a :NAME field, it + ;; only has the :DECK_ID field. So we need to use + ;; SELECT-DECK-BY-NAME anyway. + (ydk-deck-delete-by-id (mito:object-id found))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Select +(defmacro ydk-table-select (&body body) + `(select-dao 'ydk-deck ,@body)) + +(defun ydk-table-select-count (&optional fields-and-values) + (apply #'count-dao 'ydk-deck fields-and-values)) + +(defun ydk-deck-by-id (id &rest args) + (apply #'find-dao 'ydk-deck :id id args)) + +(defun ydk-deck-by-name (name &rest args) + (apply #'find-dao 'ydk-deck :name name args)) + +;; Very cool to have figured this out. To get info on a deck, select +;; the deck_item where the deck_id is the deck you want. Then INNER +;; JOIN on :card_info. It just works. One db trip. Aweseome! +;; +;; TODO how can we do this with SELECT-DAO? +;; +;; For now since this is only getting used in one place, RENDER-YDK, +;; which is albeit, a pretty core component, ... I'll use +;; datafly. Until I figure out how to relate these columns. +;; +;; I think this is a duplicate of YDK-QUERY... Okay it occured to +;; me. I used to use YDK-QUERY before I figured this one out and I +;; think I know what's going on. +;; +;; So YDK-DECK-INFO-BY-ID uses SQL to do an INNER JOIN to get the data +;; from the deck. YDK-QUERY works on decks that aren't in the database +;; and only returns partial information. I have to use that info to +;; re-construct the data. The original purpose of YDK-QUERY was to use +;; it like a look-up table, based on that sorting dev.to article. +;; +;; But then I realized, it's a lot more efficient to do it at the +;; database level. so that's where YDK-DECK-INFO-BY-ID was born +;; from. It works on existing decks in the database. +(defgeneric ydk-query (id)) + +(defmethod ydk-query ((id integer)) + "Query the database for information from YGO_INFO about the DECK ID." + (with-connection (db) + (with-transaction + (mito:retrieve-by-sql + (sxql:select :* + (sxql:from :ygo_info) + (sxql:inner-join :deck_item + :on (:= :deck_item.passcode :ygo_info.passcode)) + (sxql:order-by :asc :deck_item.id) + (sxql:where (:= :deck_id id))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod ydk-query ((ydk-deck ydk-deck)) + "Query the YDK-DECK by its MITO:OBJECT-ID" + (ydk-query (mito:object-id ydk-deck))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod ydk-query ((ydk ydk)) + "Select entries from :YGOPRODECK-DATA corresponding to the unique list +of cards in DECK. This can be used as a look up table to construct a +deck description. + +It looks like I updated YDK-QUERY to work with the new YDK-ALL output. Neat!" + (let* ((all (ydk-all ydk)) + (cards (ydk-concatenate ydk)) + (info (with-datafly-connection (db) + (with-datafly-transaction (db) + (datafly:retrieve-all + (sxql:select :* + (sxql:from :ygo_info) + (sxql:where (:in :passcode cards)))))))) + (labels ((query-deck (kind) + (loop for passcode in (getf all kind) + collect + (find passcode info + :key (lambda (plist) (getf plist :passcode)))))) + (list + :main-deck (query-deck :main-deck) + :extra-deck (query-deck :extra-deck) + :side-deck (query-deck :side-deck))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +(defmethod ydk-rename-deck ((ydk-deck ydk-deck) new-name) + (setf (ydk-name-of ydk-deck) new-name) + (save-dao ydk-deck)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun normalize-content (content) + "Strip UTF-8 BOM, \"\r\n\" sequences, then split on #\Newline into a list." + (etypecase content + ;; It's a LIST - pass it on + (list content) + ;; It's a PATHNAME - UIOP:READ-FILE-STRING and then treat it like + ;; a STRING. + (pathname + (split-sequence:split-sequence + #\Newline + (normalize-newlines + (strip-bom (uiop:read-file-string content))))) + ;; It's a STRING: strip BOM and normalize newlines. + (string + (split-sequence:split-sequence + #\Newline + (normalize-newlines + (strip-bom content)))))) + +(defmethod initialize-with-content ((ydk ydk) content) + (let ((created-by nil) + (main-deck '()) + (extra-deck '()) + (side-deck '()) + (state :in-created-by)) + + (dolist (itm (normalize-content content)) + ;; A bunch of :END2 checks to prevent overrun. + (unless + ;; Detect state change + (cond ((string-equal "#main" itm :end2 (min (length itm) 5)) + (setf state :in-main) + t) + ((string-equal "#extra" itm :end2 (min (length itm) 6)) + (setf state :in-extra) + t) + ((string-equal "!side" itm :end2 (min (length itm) 5)) + (setf state :in-side) + t) + ((equal "" itm) + t)) ; Skip blank lines! + ;; Otherwise, we assume it's a valid PASSCODE. + (ccase state + (:in-created-by (setf created-by itm)) + (:in-main (push (parse-integer itm) main-deck)) + (:in-side (push (parse-integer itm) side-deck)) + (:in-extra (push (parse-integer itm) extra-deck))))) + + (unless (and (slot-boundp ydk 'created-by) + (slot-value ydk 'created-by)) + (setf (ydk-created-by ydk) created-by)) + ;; Currently we don't read the NAME of the YDK from anywhere + ;; *inside* the file contents, it is provided externally, + ;; e.g. file name. + ;; + ;; (unless (and (slot-boundp ydk 'name) + ;; (slot-value ydk 'name)) + ;; (setf (ydk-name-of ydk) name)) + (unless (and (slot-boundp ydk 'main-deck) + (slot-value ydk 'main-deck)) + (setf (ydk-main-deck-of ydk) + (reverse main-deck))) + (unless (and (slot-boundp ydk 'extra-deck) + (slot-value ydk 'extra-deck)) + (setf (ydk-extra-deck-of ydk) + (reverse extra-deck))) + (unless (and (slot-boundp ydk 'side-deck) + (slot-value ydk 'side-deck)) + (setf (ydk-side-deck-of ydk) + (reverse side-deck))))) + +(defmethod initialize-instance :after ((ydk ydk) &rest initargs + &key content &allow-other-keys) + (declare (ignore initargs)) + (when content + (initialize-with-content ydk content))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod ydk-add ((ydk ydk) target-deck passcode) + (let ((deck + (ecase target-deck + (:main (ydk-main-deck-of ydk)) + (:extra (ydk-extra-deck-of ydk)) + (:side (ydk-side-deck-of ydk))))) + (ccase target-deck + (:main + (setf (ydk-main-deck-of ydk) (append deck (list passcode)))) + (:extra + (setf (ydk-extra-deck-of ydk) (append deck (list passcode)))) + (:side + (setf (ydk-side-deck-of ydk) (append deck (list passcode))))))) + +;; TODO Deprecated. Remove this code. YDK-DELETE-INDEX is +;; superior. How are we going to tackle moving cards? +(defmethod ydk-delete ((ydk ydk) kind passcode &optional (count 1)) + "Sloppily remove PASSCODE from YDK KIND deck. KIND is one of :MAIN +:EXTRA or :SIDE. Optionally specify how many to remove with COUNT, +default of 1." + (labels ((remove-fn (array) + (remove-if (lambda (lst) (eq lst passcode)) array :count count))) + (with-slots (main-deck extra-deck side-deck) ydk + (ccase kind + (:main (setf main-deck (remove-fn main-deck))) + (:side (setf side-deck (remove-fn side-deck))) + (:extra (setf extra-deck (remove-fn extra-deck))))))) + +(defmethod ydk-delete-index ((ydk ydk) kind index &optional (count 1)) + "Delete COUNT sequential cards at INDEX from the KIND of deck. KIND +is one of :MAIN :EXTRA or :SIDE." + (with-slots (main-deck extra-deck side-deck) ydk + (ccase kind + (:main (setf main-deck + (append + (subseq main-deck 0 index) + (subseq main-deck (+ count index))))) + (:extra (setf extra-deck + (append + (subseq extra-deck 0 index) + (subseq extra-deck (+ count index))))) + (:side (setf side-deck + (append + (subseq side-deck 0 index) + (subseq side-deck (+ count index)))))))) + +(defmethod ydk-clear ((ydk ydk) &optional target-deck) + "Clear out the YDK." + (case target-deck + (:main (setf (ydk-main-deck-of ydk) nil)) + (:side (setf (ydk-side-deck-of ydk) nil)) + (:extra (setf (ydk-extra-deck-of ydk) nil)) + (t (progn + (ydk-clear ydk :main) + (ydk-clear ydk :extra) + (ydk-clear ydk :side))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod ydk-sync ((ydk ydk)) + "Synchronize this YDK intermediate deck to the database. If there's +already a deck by this name, we \"update\" it by erasing all the +entries matching that DECK ID using YDK-DECK-ITEM-DELETE-FROM. That is, YDK => SQL." + (let ((deck-list '()) + (deck (ydk-deck-by-name (ydk-name-of ydk)))) + + ;; Create the new deck if it does not exist. + (unless deck + (setf deck + (create-dao 'ydk-deck + :name (ydk-name-of ydk) + :created-by (ydk-created-by ydk)))) + + ;; If A deck with this name already exists: clear out the old + ;; list. This no longer erases the YDK-DECK entry, so DECK-ID + ;; should stay consistent now. + (ydk-deck-item-delete-from + (sxql:where + (:= :deck_item.deck_id (mito:object-id deck)))) + + ;; Insert new deck_items into the database. How do we do + ;; that? We create a bunch of objects then MITO:INSERT-DAO them. + (loop for (kind lst) on (ydk-all ydk) by #'cddr do + (dolist (passcode lst) + (push (make-instance 'deck-item + :deck-id (mito:object-id deck) + :kind (%keyword-to-sql kind) + :passcode passcode) + deck-list))) + + (if (do-grouped-insert deck-list) + deck))) + +(defmethod ydk-sync ((ydk-deck ydk-deck)) + "Query the database deck with this YDK-DECK. That is, SQL => YDK. + +TODO Why not WITH-SLOTS?" + (with-connection (db) + (let* ((id (mito:object-id ydk-deck)) + (created-by (ydk-created-by ydk-deck)) + (name (ydk-name-of ydk-deck)) + (deck-list (mito:retrieve-dao 'deck-item :deck-id id)) + (main-deck '()) + (extra-deck '()) + (side-deck '())) + + (dolist (deck-item deck-list) + (let ((passcode (deck-passcode-of deck-item))) + ;; TODO There could be a macro (with-main-side-extra-decks) + ;; or something to suplliment %keyword-to-sql and + ;; %sql-to-keyword. + (ccase (deck-kind-of deck-item) + (0 (push passcode main-deck)) + (1 (push passcode extra-deck)) + (2 (push passcode side-deck))))) + + (make-instance 'ydk + :deck-id id + :name name + :created-by created-by + :main-deck main-deck + :extra-deck extra-deck + :side-deck side-deck)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod ydk-all ((ydk ydk)) + "Get the list of cards from a deck, duplicates included. Generally this order is retained from e.g. file upload or deck creation. + +SEE YDK-SORTED." + (with-slots (main-deck extra-deck side-deck) ydk + (list :main-deck main-deck + :side-deck side-deck + :extra-deck extra-deck))) + +(defmethod ydk-concatenate ((ydk ydk)) + "Get the list of cards from a deck, duplicates included. Generally this order is retained from e.g. file upload or deck creation. + +SEE YDK-SORTED." + (with-slots (main-deck extra-deck side-deck) ydk + (concatenate 'list main-deck extra-deck side-deck))) + +(defmethod ydk-unique ((ydk ydk)) + "Get the unique list of cards from a deck." + (remove-duplicates (ydk-concatenate ydk))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; (with-datafly-connection (db) +;; (with-datafly-transaction +;; (datafly:retrieve-all +;; (select (:frame_type) (from :ygo_info) (group-by :frame_type))))) +;; ((:FRAME-TYPE "effect") (:FRAME-TYPE "effect_pendulum") (:FRAME-TYPE "fusion") +;; (:FRAME-TYPE "fusion_pendulum") (:FRAME-TYPE "link") (:FRAME-TYPE "normal") +;; (:FRAME-TYPE "normal_pendulum") (:FRAME-TYPE "ritual") +;; (:FRAME-TYPE "ritual_pendulum") (:FRAME-TYPE "skill") (:FRAME-TYPE "spell") +;; (:FRAME-TYPE "synchro") (:FRAME-TYPE "synchro_pendulum") (:FRAME-TYPE "token") +;; (:FRAME-TYPE "trap") (:FRAME-TYPE "xyz") (:FRAME-TYPE "xyz_pendulum")) +(defmethod ydk-sorted ((ydk ydk)) + "Sort the YDK according to FRAME-TYPE-PRIO." + (let ((deck-info (copy-list (ydk-query ydk)))) + (labels ((frame-type-prio (plist) + (let ((kind (getf plist :frame-type))) + (cond ((string= kind "normal") 0) + ((string= kind "effect") 1) + ((string= kind "ritual") 2) + ((string= kind "fusion") 3) + ((string= kind "link") 4) + ((string= kind "skill") 5) + ((string= kind "synchro") 6) + ((string= kind "token") 7) + ((string= kind "xyz") 8) + ((string= kind "spell") 9) + ((string= kind "trap") 10) + ((string= kind "effect_pendulum") 11) + ((string= kind "fusion_pendulum") 12) + ((string= kind "normal_pendulum") 13) + ((string= kind "ritual_pendulum") 14) + ((string= kind "synchro_pendulum") 15) + ((string= kind "xyz_pendulum") 16) + (t 17)))) + (sort-and-extract (kind) + (mapcar (lambda (plist) (getf plist :passcode)) + (sort + (sort (copy-list (getf deck-info kind)) + #'string< :key (lambda (plist) (princ-to-string + (getf plist :name)))) + #'< :key #'frame-type-prio)))) + (make-instance 'ydk + :created-by (ydk-created-by ydk) + ;; Sometimes unbound - on unsaved decks. + :deck-id (ignore-errors (ydk-id-of ydk)) + :name (ydk-name-of ydk) + :main-deck (sort-and-extract :main-deck) + :extra-deck (sort-and-extract :extra-deck) + :side-deck (sort-and-extract :side-deck))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Now where on earth does this belong? Probably with the YDK stuff maybe? +;; +;; Maybe this will go back to the toolkit This code might be useful for parsing CSVs or similar. +;; +#+nil +(defun parse-decklist (string) + (let ((split-query + (mapcar #'(lambda (s) + (string-trim '(#\Newline #\Return #\Tab #\Space) s)) + (remove "" + (split-sequence:split-sequence + #\Newline + (normalize-newlines string)) + :test #'equal))) + (new-list '())) + (dolist (line split-query) + (multiple-value-bind (string substrings) + (cl-ppcre:scan-to-strings "^(\\d+)x (.*)" line) + (declare (ignore string)) + (cond (substrings + (let ((card-name (svref substrings 1)) + (as-number (read-from-string (svref substrings 0)))) + ;; 10x => dotimes 10 + (dotimes (_ as-number) + (push card-name new-list)))) + (t (push line new-list))))) + (reverse new-list))) + +(defmethod ydk-filter-frame-type ((ydk ydk) kind) + (let ((monster '("normal" "effect" "ritual" "fusion" "link" "synchro" + "xyz" "effect_pendulum" "fusion_pendulum" "normal_pendulum" + "ritual_pendulum" "synchro_pendulum" "xyz_pendulum")) + (spell '("spell")) + (trap '("trap")) + (deck-info (ydk-query ydk))) + (labels ((filter (plist) + (remove-if (lambda (itm) + (if (member (getf itm :frame-type) + (case kind + (:monster monster) + (:spell spell) + (:trap trap)) + :test #'equal) + nil + t)) + plist))) + (filter (getf deck-info :main-deck))))) + +(defmethod ydk-monster-cards ((ydk ydk)) + (ydk-filter-frame-type ydk :monster)) +(defmethod ydk-spell-cards ((ydk ydk)) + (ydk-filter-frame-type ydk :spell)) +(defmethod ydk-trap-cards ((ydk ydk)) + (ydk-filter-frame-type ydk :trap)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod ydk-length ((ydk ydk)) + (reduce #'+ (list (length (ydk-main-deck-of ydk)) + (length (ydk-extra-deck-of ydk)) + (length (ydk-side-deck-of ydk))))) + +(defmethod ydk-to-kde ((ydk ydk)) + "Transform the YDK according to KDE Team List." + (let ((created-by (ydk-created-by ydk)) + (deck-info (ydk-query ydk)) + (monster-cards (ydk-monster-cards ydk)) + (spell-cards (ydk-spell-cards ydk)) + (trap-cards (ydk-trap-cards ydk))) + (let ((main-deck (getf deck-info :main-deck)) + (extra-deck (getf deck-info :extra-deck)) + (side-deck (getf deck-info :side-deck))) + (let ((main-deck-rle (rle-encode-plist main-deck)) + (extra-deck-rle (rle-encode-plist extra-deck)) + (side-deck-rle (rle-encode-plist side-deck)) + (monster-cards-rle (rle-encode-plist monster-cards)) + (spell-cards-rle (rle-encode-plist spell-cards)) + (trap-cards-rle (rle-encode-plist trap-cards))) + (labels ((pad (lst) + (let ((max 17)) + (append lst + (loop for i from (length lst) upto max collect + '("" :name "")))))) + (list + :name (ydk-name-of ydk) + :set nil + :length (ydk-length ydk) + :created-by created-by + :first-date nil + :final-date nil + :monster-cards monster-cards + :spell-cards spell-cards + :trap-cards trap-cards + :main-deck main-deck + :side-deck side-deck + :extra-deck extra-deck + :monster-cards-rle (pad monster-cards-rle) + :spell-cards-rle (pad spell-cards-rle) + :trap-cards-rle (pad trap-cards-rle) + :main-deck-rle (pad main-deck-rle) + :side-deck-rle (pad side-deck-rle) + :extra-deck-rle (pad extra-deck-rle))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod %to-string ((ydk ydk)) + (with-slots (created-by main-deck extra-deck side-deck) ydk + (with-output-to-string (s) + (format s "~A~%" + (if created-by + created-by + "created by...")) + (format s "#main~%") + (dolist (card-name (coerce main-deck 'list)) + (format s "~A~%" card-name)) + (format s "#extra~%") + (dolist (card-name (coerce extra-deck 'list)) + (format s "~A~%" card-name)) + (format s "!side~%") + (dolist (card-name (coerce side-deck 'list)) + (format s "~A~%" card-name)) + s))) diff --git a/src/models/ygoprodeck-cardinfo.lisp b/src/models/ygoprodeck-cardinfo.lisp new file mode 100644 index 0000000..67a9156 --- /dev/null +++ b/src/models/ygoprodeck-cardinfo.lisp @@ -0,0 +1,158 @@ +#| + +src/models/ygoprodeck-cardinfo.lisp + +Version 2 JSON Downloader + +The idea behind this code is you'll be able to one click button download and update the db: + + (cardinfo-update-and-cleanup t) + +TODO Use INFERIOR-SHELL + +|# + +(in-package #:cl-deck-builder2.models.ygoprodeck.cardinfo) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +(defparameter +ygoprodeck-api-uri+ "https://db.ygoprodeck.com/api/v7/cardinfo.php") +(defparameter +ygoprodeck-api-uri-misc+ :|misc|) +(defparameter +ygoprodeck-api-uri-tcgplayer-data+ :|tcgplayer_data|) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Specials +(defparameter *cardinfo* nil + "The current CARDINFO object.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Class Definitions +(defclass cardinfo () + ((input :initarg :input) + (output :initarg :output))) + +(defclass cardinfo-php (cardinfo) + () + (:default-initargs + :input +ygoprodeck-api-uri+ + :output #P"/tmp/cardinfo.php" + :tcgplayer-data "yes" + :misc "yes")) + +(defclass cardinfo-json (cardinfo) + () + (:default-initargs + :input #P"/tmp/cardinfo.php" + :output #P"/tmp/cardinfo.json")) + +;; Extract the card_images image_url from the cardinfo.json +;; +;; jq '.[].card_images[].image_url' < /tmp/cardinfo.json +(defclass cardinfo-list (cardinfo) + () + (:default-initargs + :input #P"/tmp/cardinfo.json" + :output #P"/tmp/cardinfo.list")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Generics +(defgeneric cardinfo-cleanup (obj)) + +(defgeneric cardinfo-convert (from to &optional force)) + +(defgeneric cardinfo-download (obj &optional force)) + +(defgeneric cardinfo-input (obj) + (:method ((obj cardinfo)) + (let ((slot-value (slot-value obj 'input))) + (typecase slot-value + (pathname (namestring slot-value)) + (t slot-value))))) + +(defgeneric cardinfo-output (obj) + (:method ((obj cardinfo)) + (let ((slot-value (slot-value obj 'output))) + (typecase slot-value + (pathname (namestring slot-value)) + (t slot-value))))) + +(defgeneric (setf cardinfo-input) (new-value obj) + (:method (new-value (obj cardinfo)) + (setf (slot-value obj 'input) new-value))) + +(defgeneric (setf cardinfo-output) (new-value obj) + (:method (new-value (obj cardinfo)) + (setf (slot-value obj 'output) new-value))) + +(defgeneric cardinfo-input-exists-p (obj) + (:method ((obj cardinfo)) + (probe-file (cardinfo-output obj)))) + +(defgeneric cardinfo-output-exists-p (obj) + (:method ((obj cardinfo)) + (probe-file (cardinfo-output obj)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Methods +(defmethod initialize-instance :after ((obj cardinfo-php) &rest initargs &key misc tcgplayer-data &allow-other-keys) + (declare (ignore initargs)) + (let ((params '())) + (when misc (setf (getf params +ygoprodeck-api-uri-misc+) misc)) + (when tcgplayer-data (setf (getf params +ygoprodeck-api-uri-tcgplayer-data+) tcgplayer-data)) + (when params + (setf (cardinfo-input obj) + (caveman2.helper::add-query-parameters + (cardinfo-input obj) params)))) + obj) + +(defmethod cardinfo-cleanup ((obj cardinfo)) + (v:info :ygoprodeck.json "CARDINFO-CLEANUP: ~a" obj) + (uiop:delete-file-if-exists (cardinfo-output obj))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO This could probably be replaced with CL-JSON processor. +(defun cardinfo-run-program (command from to &optional force) + (let* ((from-output (cardinfo-output from)) + (from-output-exists-p (probe-file from-output)) + ;; TO-INPUT is the same as FROM-OUTPUT + (to-input (cardinfo-input to)) + (to-output (cardinfo-output to)) + (to-output-exists-p (probe-file to-output))) + (if (and (string= from-output to-input) + from-output-exists-p + (or force + (not to-output-exists-p))) + (let ((command (format nil command from-output to-output))) + (v:info :ygoprodeck.cardinfo "Running command: \"~a\"" command) + (if (uiop:run-program command :output '(:string :stripped t)) + (cardinfo-output-exists-p to))) + (v:info :ygoprodeck.cardinfo "Detected existing file: \"~a\"" to-output)))) + +(defmethod cardinfo-convert ((from cardinfo-php) (to cardinfo-json) &optional force) + (cardinfo-run-program "jq '.data' < \"~a\" > \"~a\"" from to force)) + +(defmethod cardinfo-convert ((from cardinfo-json) (to cardinfo-list) &optional force) + (cardinfo-run-program "jq '.[].card_images[].image_url' < \"~a\" > \"~a\"" from to force)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod cardinfo-download ((obj cardinfo) &optional force) + (v:info :ygoprodeck.json "CARDINFO-DOWNLOAD: ~a" + (cardinfo-input obj)) + (let* ((input (cardinfo-input obj)) + (output (cardinfo-output obj)) + (output-exists-p (probe-file output))) + (if (or force + (not output-exists-p)) + (trivial-download:download input output) + (cardinfo-output-exists-p obj)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun cardinfo-update-and-cleanup (&optional cleanup) + (let ((cardinfo-php (make-instance 'cardinfo-php)) + (cardinfo-json (make-instance 'cardinfo-json)) + (cardinfo-list (make-instance 'cardinfo-list))) + (cardinfo-download cardinfo-php) + (cardinfo-convert cardinfo-php cardinfo-json) + (cardinfo-convert cardinfo-json cardinfo-list) + (when cleanup + (mapcar #'cardinfo-cleanup (list cardinfo-php cardinfo-json cardinfo-list))))) diff --git a/src/models/ygoprodeck-classes.lisp b/src/models/ygoprodeck-classes.lisp new file mode 100644 index 0000000..2e4694a --- /dev/null +++ b/src/models/ygoprodeck-classes.lisp @@ -0,0 +1,472 @@ +#| + +src/models/ygoprodeck-classes.lisp + +Yu-Gi-Oh! Pro Deck Database Interface v2 + +TODO Documentation; The diagram from schemacrawler would be nice. + +|# + +(in-package #:cl-deck-builder2.models.ygoprodeck.classes) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This is part of cardsv3 now. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defclass ygo-name-mixin () + ((name :accessor name-of + :col-type :text + :initarg :name)) + (:unique-keys name) + (:documentation "A table for the names of all Yu-Gi-Oh! card properties.") + (:metaclass registered-table-class)) + +(defclass ygo-card-name (ygo-name-mixin) + () + (:documentation "A table for the names of all Yu-Gi-Oh! card names. Dark Magician, Red-Eyes Black Dragon, and so on.") + (:metaclass registered-table-class)) + +(defclass ygo-card-type (ygo-name-mixin) + () + (:documentation "A table for the types of Yu-Gi-Oh! cards. Spell, Flip Effect Monster, and so on.") + (:metaclass registered-table-class)) + +(defclass ygo-card-frame-type (ygo-name-mixin) + () + (:documentation "A table for the frame types of Yu-Gi-Oh! cards. spell, effect_pendulum, and so on.") + (:metaclass registered-table-class)) + +(defclass ygo-card-race (ygo-name-mixin) + () + (:documentation "A table for the races of Yu-Gi-Oh! cards. Insect, Beast, and so on. + +YGOProDeck stores the kind of spell card here too, like Equip or Continuous.") + (:metaclass registered-table-class)) + +(defclass ygo-card-archetype (ygo-name-mixin) + () + (:documentation "A table for the archetypes of Yu-Gi-Oh! cards. Archfiend, Elemental HERO, A.I, and so on.") + (:metaclass registered-table-class)) + +(defclass ygo-card-attribute (ygo-name-mixin) + () + (:documentation "A table for the attributes of Yu-Gi-Oh! cards. FIRE, WIND, EARTH, DARK, and so on.") + (:metaclass registered-table-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; YGO-CARD uses the PASSCODE as its UNIQUE ID, so you don't see it in +;; here, because it's added implicitly by MITO as ID slot. +(defclass ygo-card () + ((id :accessor ygo-passcode-of + :col-type :integer + :primary-key t + :initarg :id) + (;; Cards *always* have a UNIQUE Name, and a description. Description isn't necessarily unique. + name :accessor name-of + :col-type :text + :initarg :name) + (desc :accessor ygo-desc-of + :col-type :text + :initarg :desc) + + ;; Some cards pre-date Archetype (e.g. Normal Monsters) + (archetype :accessor ygo-archetype-of + :col-type (or ygo-card-archetype :null) + :initarg :archetype) + ;; Some cards don't have Attribute/ATK/DEF/Level/Scale: + ;; - Skill + ;; - Spell + ;; - Trap + (attribute :accessor ygo-attribute-of + :col-type (or ygo-card-attribute :null) + :initarg :attribute) + (atk :accessor ygo-atk-of + :col-type (or :integer :null) + :initarg :atk) + (def :accessor ygo-def-of + :col-type (or :integer :null) + :initarg :def) + (scale :accessor ygo-scale-of + :col-type (or :integer :null) + :initarg :scale) + (level :accessor ygo-level-of + :col-type (or :integer :null) + :initarg :level) + + ;; Every card has a Frame-Type and a Race; Even Spell and Trap cards have a Race. + ;; YGOProDeck uses this field to keep "Continuous", or "Field" ... yuck. + (frame-type :accessor ygo-frame-type-of + :col-type ygo-card-frame-type + :initarg :frame-type) + (race :accessor ygo-race-of + :col-type ygo-card-race + :initarg :race) + + ;; There's a conflict with the existing lisp builtin TYPE-OF. Call + ;; it CARD-TYPE and access with CARD-TYPE-OF. + (card-type :accessor ygo-card-type-of + :col-type ygo-card-type + :initarg :type)) + (:primary-key id) + (:auto-pk nil) + (:documentation "A table for most info of Yu-Gi-Oh! cards. + +This corresponds to the top level data for each card in the YGOProDeck API export.") + (:metaclass registered-table-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; From here onward, the code depends on YGO-CARD. +(defclass ygo-card-misc-info () + ((passcode :accessor ygo-passcode-of + :col-type ygo-card + :initarg :passcode) + (views :accessor ygo-views-of + :col-type :integer + :initform 0 + :initarg :views) + (viewsweek :accessor ygo-viewsweek-of + :col-type :integer + :initform 0 + :initarg :viewsweek) + (upvotes :accessor ygo-upvotes-of + :col-type :integer + :initform 0 + :initarg :upvotes) + (downvotes :accessor ygo-downvotes-of + :col-type :integer + :initform 0 + :initarg :downvotes) + (beta-id :accessor ygo-beta-id-of + :col-type (or :integer :null) + :initarg :beta--id) + (beta-name :accessor ygo-beta-name-of + :col-type (or ygo-name-mixin :null) + :initarg :beta--name) + (staple :accessor ygo-is-staple + :col-type (or :integer :null) + :initarg :staple) + (konami-id :accessor ygo-konami-id-of + :col-type :integer + :initarg :konami--id) + ;; This :INTEGER is just an index into this table, YGO-CARDS. + (treated-as :accessor ygo-treated-as + :col-type (or :null ygo-name-mixin) + :initarg :treated--as) + (has-effect :accessor ygo-has-effect + :col-type (or :integer :null) + :initarg :has--effect) + (question-atk :accessor ygo-question-atk + :col-type (or :integer :null) + :initarg :question--atk) + (question-def :accessor ygo-question-def + :col-type (or :integer :null) + :initarg :question--def) + + ;; When the TCG-DATE or OCG-DATE is NULL, that means it hasn't been released in TCG/OCG... + (tcg-date :accessor ygo-tcg-date-of + :col-type (or :timestamp :null) + :initarg :tcg--date) + (ocg-date :accessor ygo-ocg-date-of + :col-type (or :timestamp :null) + :initarg :ocg--date)) + (:metaclass registered-table-class) + (:documentation "This corresponds to the \"misc_info\" data for each card in the YGOProDeck API export.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Now Card Set Stuff +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass ygo-set-name (ygo-name-mixin) + () + (:documentation "A table for the sets of Yu-Gi-Oh! cards. Lightning Overdrive, 2021 Tin of Ancient Battles, and so on.") + (:metaclass registered-table-class)) + +(defclass ygo-set-code (ygo-name-mixin) + () + (:documentation "A table for the set codes of Yu-Gi-Oh! cards. LOB-001, LOB-002, and so on. + +SET-CODE is the full code, e.g. \"LOB-001\". + +CODE is just \"001\" part, SET is just \"LOB\" part, both of which can be accessed by SPLIT-CODE.") + (:metaclass registered-table-class)) + +;; Rarity => "Common", "Ghost Rare", "Ultra Rare", "Secret Rare" +(defclass ygo-set-rarity (ygo-name-mixin) + () + (:documentation "A table for the rarities of Yu-Gi-Oh! cards. Common, Secret Rare, Ghost Rare, and so on.") + (:metaclass registered-table-class)) + +;; jq '.[]?.card_sets[]?.set_rarity_code' < /tmp/cardinfo.json | sort -h | uniq +(defclass ygo-set-rarity-code (ygo-name-mixin) + () + (:documentation "A table for the rarity codes of Yu-Gi-Oh! cards. (C), (SR), (GUR), etc.") + (:metaclass registered-table-class)) + +(defclass ygo-set-edition (ygo-name-mixin) + () + (:documentation "A table for the editions of Yu-Gi-Oh! cards. 1st Edition, Unlimited, and so on.") + (:metaclass registered-table-class)) + +(defclass ygo-set () + ((passcode :accessor ygo-passcode-of + :col-type ygo-card + :initarg :passcode) + (name :accessor name-of + :col-type ygo-set-name + :initarg :name) + (code :accessor ygo-code-of + :col-type ygo-set-code + :initarg :code) + (rarity :accessor ygo-rarity-of + :col-type ygo-set-rarity + :initarg :rarity) + (rarity-code :accessor ygo-rarity-code--of + :col-type ygo-set-rarity-code + :initarg :rarity-code) + (edition :accessor ygo-edition-of + :col-type ygo-set-edition + :initarg :edition) + + (url :accessor ygo-url-of + :col-type :text + :initarg :url) + + ;; PRICE is $X,XX * 100 => XXX,00 That way we don't have to store floats at all. + ;; + ;; Default set price is Half a Doll Hair (0.50$) if none supplied + (price :accessor ygo-price-of + :col-type :integer + :initarg :price + :initform 0.50 + :inflate #'currency-inflate + :deflate #'currency-deflate)) + (:documentation "A table for the sets of Yu-Gi-Oh! cards. + +This corresponds to the \"card_sets\" field in the YGOProDeck API export.") + (:metaclass registered-table-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass ygo-linkmarker () + ((passcode :accessor ygo-passcode-of + :col-type ygo-card + :initarg :passcode) + (bottom :accessor ygo-linkmarker-bottom + :col-type (or :null ygo-linkmarker-name) + :initarg :bottom) + (bottom-left :accessor ygo-linkmarker-bottom-left + :col-type (or :null ygo-linkmarker-name) + :initarg :bottom-left) + (bottom-right :accessor ygo-linkmarker-bottom-right + :col-type (or :null ygo-linkmarker-name) + :initarg :bottom-right) + (left :accessor ygo-linkmarker-left + :col-type (or :null ygo-linkmarker-name) + :initarg :left) + (right :accessor ygo-linkmarker-right + :col-type (or :null ygo-linkmarker-name) + :initarg :right) + (top :accessor ygo-linkmarker-top + :col-type (or :null ygo-linkmarker-name) + :initarg :top) + (top-left :accessor ygo-linkmarker-top-left + :col-type (or :null ygo-linkmarker-name) + :initarg :top-left) + (top-right :accessor ygo-linkmarker-top-right + :col-type (or :null ygo-linkmarker-name) + :initarg :top-right)) + (:metaclass registered-table-class) + (:documentation "A table for Yu-Gi-Oh! Link Monster Link markers. + +This corresponds to the \"linkmarkers\" field in each card.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass ygo-format () + ((passcode :accessor ygo-passcode-of + :col-type (or :null ygo-format-name) + :initarg :passcode) + (common-charity :accessor ygo-common-charity-format + :col-type (or :null ygo-format-name) + :initarg :common-charity) + (duel-links :accessor ygo-duel-links-format + :col-type (or :null ygo-format-name) + :initarg :duel-links) + (edison :accessor ygo-edison-format + :col-type (or :null ygo-format-name) + :initarg :edison) + (goat :accessor ygo-goat-format + :col-type (or :null ygo-format-name) + :initarg :goat) + (ocg :accessor ygo-ocg-format + :col-type (or :null ygo-format-name) + :initarg :ocg) + (ocg-goat :accessor ygo-ocg-goat-format + :col-type (or :null ygo-format-name) + :initarg :ocg-goat) + (speed-duel :accessor ygo-speed-duel-format + :col-type (or :null ygo-format-name) + :initarg :speed-duel) + (tcg :accessor ygo-tcg-format + :col-type (or :null ygo-format-name) + :initarg :tcg)) + (:metaclass registered-table-class) + (:documentation "A table for Yu-Gi-Oh! rule formats. + +This corresponds to the \"formats\" field in the \"misc_info\" for each card. + +If the card is available in this format, e.g. listed under misc_info.formats \"TCG\", that means the card is allowed in TCG games. The YGO-TCG-FORMAT slot for this object would have a non-NIL YGO-FORMAT-NAME value.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass ygo-banlist () + ((passcode :accessor ygo-passcode-of + :col-type ygo-card + :initarg :passcode) + (ban-goat :accessor ygo-ban-goat + :col-type (or :null ygo-banlist-name) + :initarg :ban--goat) + (ban-ocg :accessor ygo-ban-ocg + :col-type (or :null ygo-banlist-name) + :initarg :ban--ocg) + (ban-tcg :accessor ygo-ban-tcg + :col-type (or :null ygo-banlist-name) + :initarg :ban--tcg)) + (:unique-keys passcode) + (:metaclass registered-table-class) + (:documentation "A table for Yu-Gi-Oh! rule banlist. + +This corresponds to the \"banlist_info\" field for each card.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass ygo-alternative-artwork () + ((passcode :accessor ygo-passcode-of + :col-type ygo-card + :initarg :passcode) + (alternate :accessor ygo-alternate-id-of + :col-type ygo-card + :initarg :alternate)) + (:metaclass registered-table-class) + (:documentation "A table for Yu-Gi-Oh! alternative artwork. + +This corresponds to the \"card_images\" array for each card.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass ygo-price () + ((passcode :accessor ygo-passcode-of + :col-type ygo-card + :initarg :passcode + :deflate #'currency-deflate + :inflate #'currency-inflate) + (cardmarket-price :accessor ygo-cardmarket-price-of + :col-type :integer + :initarg :cardmarket--price + :deflate #'currency-deflate + :inflate #'currency-inflate) + (tcgplayer-price :accessor ygo-tcgplayer-price-of + :col-type :integer + :initarg :tcgplayer--price + :deflate #'currency-deflate + :inflate #'currency-inflate) + (ebay-price :accessor ygo-ebay-price-of + :col-type :integer + :initarg :ebay--price + :deflate #'currency-deflate + :inflate #'currency-inflate) + (amazon-price :accessor ygo-amazon-price-of + :col-type :integer + :initarg :amazon--price + :deflate #'currency-deflate + :inflate #'currency-inflate) + (coolstuffinc-price :accessor ygo-coolstuffinc-price-of + :col-type :integer + :initarg :coolstuffinc--price + :deflate #'currency-deflate + :inflate #'currency-inflate)) + (:metaclass registered-table-class) + (:documentation "A table for Yu-Gi-Oh! pricing information. Represents the API information about Card Pricing. Encapsulates the following fields: +- Passcode +- CardMarket +- TCGPlayer +- eBay +- Amazon +- CoolStuffInc + +This corresponds to the \"card_prices\" array for each card.")) + +;; YGO-FORMAT-NAME, YGO-LINKMARKER-NAME, YGO-BANLIST-NAME, +;; YGO-SET-RARITY-CODE, and the like have moved to YGOPRODECK.FIELDS. + +(defclass ygo-set-item () + ((item :accessor item-of + :initarg :item + :col-type ygo-set) + (variant :accessor variant-of + :initarg :variant + :col-type variant-condition) + (qty :accessor qty-of + :col-type :integer + :initform 0 + :initarg :qty) + (opt-qty :accessor opt-qty-of + :col-type :integer + :initform 0 + :initarg :opt-qty) + (;; 10 cent buy price + buy-price :accessor buy-price-of + :col-type :integer + :initform 0.10 + :initarg :buy-price + :inflate #'currency-inflate + :deflate #'currency-deflate) + (;; 50 cent sell price + sell-price :accessor sell-price-of + :col-type :integer + :initform 0.50 + :initarg :sell-price + :inflate #'currency-inflate + :deflate #'currency-deflate)) + (:metaclass registered-table-class) + (:documentation "YGO-SET + VARIANT-CONDITION relationship table.")) + + +#| +;;;; models/ygoprodeck.lisp + (defstruct ygoprodeck-item + (:documentation "Represents the top level API information queried from YGOProDeck. Encapsulates the following fields: ~Passcode~,~Name~,~Type~, ~Frame Type~, ~Documentation~,~Race~,~ATK~, ~DEF~,~Level~,~Attribute~, and ~Archetype~.")) + (defstruct ygoprodeck-card-set + (:documentation "Represents the API information about Card Sets. Encapsulates the following fields: ~Passcode~,~Name~,~Code~, ~Rarity~, ~Rarity-code~, and ~Price~.")) + (defstruct ygoprodeck-card-image + (:documentation "Card images. We don't use this much.")) + (defstruct ygoprodeck-card-price + (defun drop-table-ygoprodeck-data + (:documentation "Destroy the table for this data.")) + (defun create-table-ygoprodeck-data + (:documentation "Create the corresponding database structure for the YGOProDeck API data import.")) + (defun json-import-ygoprodeck-data + (:arguments + ((data "The data to be imported.")) + :documentation "Import the base API data (ATK, DEF,Level, ...) information from the Card Info API data.")) + (defun json-import-ygoprodeck-card-images + (:arguments + ((data "The data to be imported.")) + :documentation "Import the images information from the Card Info API data.")) + (defun json-import-ygoprodeck-card-prices + (:arguments + ((data "The data to be imported.")) + :documentation "Import the pricing information from the Card Info API data.")) + (defun json-import-ygoprodeck-card-sets + (:arguments + ((data "The data to be imported.")) + :documentation "Import the card set information from the Card Info API data.")) + (defun json-import-ygoprodeck-banlist-info + (:arguments + ((data "The data to be imported.")) + :documentation "Import the banlist information from the Card Info API data.")) + (defun json-import-ygoprodeck + (:arguments + ((json "The JSON query to iterate over. Response from YGOProDeck API.")) + :documentation "Import YGOProDeck data. Wraps the ~JSON-IMPORT-*~ functions.")) + (defun card-rarity-list + (:documentation "List all card rarities.")) + (defun card-code-list + (:documentation "List all card set codes.")) + +|# diff --git a/src/models/ygoprodeck-fields.lisp b/src/models/ygoprodeck-fields.lisp new file mode 100644 index 0000000..583db59 --- /dev/null +++ b/src/models/ygoprodeck-fields.lisp @@ -0,0 +1,62 @@ +#| + +If you wanted to add an item, you would add to the end of the list +or the matching position in the list to the matching DEFPARAMETER +and DEFCLASS. + +These are all extracted from cardinfo.php + +|# + +(in-package #:cl-deck-builder2.models.ygoprodeck.fields) + +;; TODO Merge this with YGO-CARD-NAME? What is even going on +;; here. This could be mixed with any DAO/table with a NAME +;; column. That *feels* like it's a lot more general than I +;; realize.... +(defclass variant () + ((name :col-type :text + :initarg :name)) + (:metaclass registered-table-class)) + +(defmethod name-of ((obj variant)) + (slot-value obj 'name)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun create-variant (class variant) + (or (mito:find-dao class :name variant) + (mito:create-dao class :name variant))) + +(defun create-variants (class &rest variants) + (with-connection (db) + (with-transaction + (dolist (v variants) + (create-variant class v))))) + +(defmacro define-variant-list (name &rest lst) + `(progn + (defclass ,name (variant) + () + (:metaclass registered-table-class)) + (create-table ',name) + (create-variants (find-class ',name) ,@lst))) + +;; Card Conditions +(define-variant-list variant-condition + "Near Mint" "Lightly Played" "Moderately Played" "Heavily Played" "Damaged") + +;; Languages +(define-variant-list variant-language + "English" "French" "Italian" "Spanish" "German" "Portuguese") + +;; Ban List Info +(define-variant-list ygo-banlist-name + "Unlimited" "Semi-Limited" "Limited" "Banned") + +;; Formats +(define-variant-list ygo-format-name + "Common Charity" "Duel Links" "Edison" "GOAT" "OCG" "OCG GOAT" "Speed Duel" "TCG") + +;; Link Markers +(define-variant-list ygo-linkmarker-name + "Top" "Top-Right" "Right" "Bottom-Right" "Bottom" "Bottom-Left" "Left" "Top-Left") diff --git a/src/models/ygoprodeck-json.lisp b/src/models/ygoprodeck-json.lisp new file mode 100644 index 0000000..e89bfa2 --- /dev/null +++ b/src/models/ygoprodeck-json.lisp @@ -0,0 +1,477 @@ +#| + +src/models/ygoprodeck.json.lisp + +Version 3 JSON Importer + +TODO Write a test suite - it should be easier now that it's in FNF + +The idea behind this code is you'll be able to one click button download and update the db. + + (cl-deck-builder2.models.ygoprodeck.json2::json-import-cardinfo #P"/tmp/cardinfo.json") + +|# + +(in-package #:cl-user) + +(in-package #:cl-deck-builder2.models.ygoprodeck.json) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar *json* nil + "The currently loaded CARDINFO data pulled from YGOProDeck API.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod cardinfo-import ((obj cardinfo) &optional force) + "Set *JSON* to the decoded JSON data from CARDINFO, by processing it into CARDINFO-JSON using CARDINFO-CONVERT. + +Optionally, FORCE the data to be loaded anyway. + +If the (CARDINFO-OUTPUT OBJ) does not exist, try to (CARDINFO-INPUT *CARDINFO*)." + (cardinfo-load obj force) + (process-all-nodes)) + +(defmethod cardinfo-load ((obj cardinfo) &optional force) + (let ((output (cardinfo-output obj))) + (v:info :ygoprodeck.json "CARDINFO-LOAD ~a (exists:~a) (force:~a)" output (cardinfo-output-exists-p obj) force) + (if (cardinfo-output-exists-p obj) + (progn + (setf *json* (with-open-file (f output) + (cl-json:decode-json f))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun json-cardinfo-base-fields (plist) + "Filter only the base fields for this CARD-INFO PLIST." + (set-difference plist + '((:banlist--info) + (:card--images) + (:card--prices) + (:card--sets) + (:linkmarkers) + (:misc--info)) + :key #'car)) + +;; (defparameter +ygo-card-ids+ (make-hash-table :test #'equal)) +;; (defparameter +ygo-card-descs+ (make-hash-table :test #'equal)) + +;; TODO put all of these hash tables inside another hash table, then +;; make an API around it. I've seen that done before with CLOS, maybe +;; in cl-opengl defvao stuff. Remember? +(defparameter +ygo-card-names+ (make-hash-table :test #'equal)) +(defparameter +ygo-card-types+ (make-hash-table :test #'equal)) +(defparameter +ygo-card-races+ (make-hash-table :test #'equal)) +(defparameter +ygo-card-frame-types+ (make-hash-table :test #'equal)) +(defparameter +ygo-card-archetypes+ (make-hash-table :test #'equal)) +(defparameter +ygo-card-attributes+ (make-hash-table :test #'equal)) + +(defparameter +ygo-set-names+ (make-hash-table :test #'equal)) +(defparameter +ygo-set-codes+ (make-hash-table :test #'equal)) +(defparameter +ygo-set-rarity+ (make-hash-table :test #'equal)) +(defparameter +ygo-set-editions+ (make-hash-table :test #'equal)) + +;; Here we use the ID/PASSCODE so EQ is Okay +(defparameter +ygo-card-prices+ (make-hash-table)) ;; One to one: setf place +(defparameter +ygo-alternative-artwork+ (make-hash-table)) ;; Many to many: push place + +(defparameter +ygo-cards+ (make-hash-table)) ;; One to one: setf place +(defparameter +ygo-sets+ (make-hash-table)) ;; Many to many: push place + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Field Names +(defparameter +ygo-banlist-info+ (make-hash-table)) +(defparameter +ygo-format-info+ (make-hash-table)) +(defparameter +ygo-linkmarker-info+ (make-hash-table)) + +(defparameter +ygo-banlist-names+ (make-hash-table :test #'equal)) +(defparameter +ygo-format-names+ (make-hash-table :test #'equal)) +(defparameter +ygo-linkmarker-names+ (make-hash-table :test #'equal)) +(defparameter +ygo-set-rarity-codes+ (make-hash-table :test #'equal)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The bulk of the operation ... FIND-OR-MAKE-INSTANCE + SYNC-HASH-TABLE +(defun sync-hash-table (ht) + (let ((values (alexandria:hash-table-values ht))) + (cond ((null values) nil) + (;; The ht is a bunch of lists (card sets, prices, images) + (listp (car values)) + (dolist (lst values) + (unless (mito:dao-synced (car lst)) + (do-grouped-insert lst)))) + (t + (unless (mito:dao-synced (car values)) + (do-grouped-insert values)))))) + +;; TODO Where should this go? I'm seeing bits of code like this all over. +(defun %keywordify (s) + (alexandria:make-keyword + (string-upcase + (substitute #\- #\Space s)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Importer v3 +(defun extract-json-tag (json tag) + "Get all the tags from JSON matching TAG." + (mapcar (lambda (alist) (assoc-utils:aget alist tag)) json)) + +(defun extract-concat (json tag) + "Concatenate the results of EXTRACT-JSON-TAG into a single list for MAPCAR'ing." + (apply #'concatenate 'list (extract-json-tag json tag))) + +(defun extract-card-sets (json) + "Just extract :CARD--SETS. No Processing" + (extract-concat json :card--sets)) + +(defun extract-card-set-map (json) + "Extract PASSCODE => SETS mapping." + (let ((lst '())) + (dolist (node json (copy-alist + (apply #'concatenate 'list (reverse lst)))) + (let* ((node-id (assoc-utils:aget node :id)) + (maybe-card-sets + (mapcar (lambda (alist) + (acons :passcode-id node-id alist)) + (assoc-utils:aget node :card--sets)))) + (when maybe-card-sets + (push maybe-card-sets lst)))))) + +(defun extract-card-images (json) + "Just extract :CARD--IMAGES. No Processing" + (extract-concat json :card--images)) + +(defun extract-card-alternative-images-map (json) + "Mapping of ID to alternative art IDs." + (let ((lst '())) + (dolist (node json (reverse lst)) + (let* ((alternate-id (assoc-utils:aget node :id)) + (maybe-alternative-images + (remove alternate-id + (mapcar (lambda (alist) + (assoc-utils:aget alist :id)) + (assoc-utils:aget node :card--images))))) + (when maybe-alternative-images + (mapcar (lambda (node-id) + (push (list :passcode-id node-id + :alternate-id alternate-id) + lst)) + maybe-alternative-images)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Working with the base fields +(defun extract-card-tag (json tag) + "Extract TAG from JSON using EXTRACT-JSON-TAG. Also, SORT, and SUBSTITUTE NIL for \"\"." + (sort + (substitute "" NIL + (remove-duplicates + (extract-json-tag json tag) + :test #'string=)) + #'string<)) + +(defun extract-card-name (json) + (extract-card-tag json :name)) + +(defun extract-card-type (json) + (extract-card-tag json :type)) + +(defun extract-card-race (json) + (extract-card-tag json :race)) + +(defun extract-card-frame-type (json) + (extract-card-tag json :frame-type)) + +(defun extract-card-archetype (json) + (extract-card-tag json :archetype)) + +(defun extract-card-attribute (json) + (extract-card-tag json :attribute)) + +(defun extract-cards (json) + "Just extract the base field for every card." + (mapcar #'json-cardinfo-base-fields json)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun extract-card-banlist-info (json) + "Lots of cards don't have banlist info records." + (let ((lst '())) + (dolist (node json (copy-list lst)) + (let ((node-id (assoc-utils:aget node :id)) + (maybe-banlist-info + (assoc-utils:alist-plist + (assoc-utils:aget node :banlist--info)))) + (when maybe-banlist-info + (push (append (list :passcode-id node-id) + maybe-banlist-info) + lst)))))) + +(defun extract-card-banlist-names (json) + "Just extract the names of the ban status. \"Banned\" etc." + (let ((lst '())) + (dolist (node json (copy-list lst)) + (let ((maybe-banlist-names + (assoc-utils:alist-values + (assoc-utils:aget node :banlist--info)))) + (when maybe-banlist-names + (loop for name in maybe-banlist-names do + (pushnew name lst :test #'string=))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun extract-card-prices (json) + (extract-json-tag json :card--prices)) + +(defun extract-card-price-map (json) + "Extract :CARD--PRICE and process into ID => PRICES mapping" + (let ((lst '())) + (dolist (node json lst) + (let ((node-id (assoc-utils:aget node :id)) + (maybe-price-data + (assoc-utils:alist-plist + (apply #'concatenate 'list + (assoc-utils:aget node :card--prices))))) + (when maybe-price-data + (push (append (list :passcode-id node-id) + maybe-price-data) + lst)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun extract-card-misc-info (json) + (extract-json-tag json :misc--info)) + +(defun extract-card-misc-info-map (json) + "Extract :MISC--INFO and process into ID => FORMATS mapping" + (let ((lst '())) + (dolist (node json lst) + (let* ((node-id (assoc-utils:aget node :id)) + (maybe-misc-info + (assoc-utils:alist-plist + (remove :formats + (apply #'concatenate 'list + (assoc-utils:aget node :misc--info)) + :key #'car)))) + (when maybe-misc-info + (push (append (list :passcode-id node-id) + maybe-misc-info) + lst)))))) + +;; TODO +(defun extract-card-format-info (json) + "Extract :MISC--INFO and process into ID => FORMATS mapping" + (let ((lst '())) + (dolist (node json lst) + (let* ((node-id (assoc-utils:aget node :id)) + (maybe-misc-info + (assoc-utils:aget + (apply #'concatenate 'list + (assoc-utils:aget node :misc--info)) + :formats))) + (when maybe-misc-info + (push (list node-id maybe-misc-info) lst)))))) + +;; TODO - The card formats need to be processed like the card-sets and cards +(defun process-card-format-info-map (json) + (let ((info (extract-card-format-info json))) + (with-connection (db) + (with-transaction + (mapcar (lambda (row) + (list :passcode-id (car row) + (apply #'concatenate 'list + (mapcar (lambda (tag) + (let ((db-tag (or (find-or-create-instance 'ygo-format-name :name tag)))) + (list (%keywordify tag) db-tag))) + (cadr row))))) + info))))) + +(defun extract-card-format-names (json) + (sort + (remove-duplicates + (apply #'concatenate 'list + (mapcar (lambda (alist) (assoc-utils:aget alist :formats)) + (apply #'concatenate 'list + (extract-card-misc-info json)))) + :test #'string=) + #'string<)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun extract-card-linkmarker-names (json) + (sort (remove-duplicates + (extract-concat json :linkmarkers) + :test #'string=) + #'string<)) + +;; TODO +(defun extract-card-linkmarker-info (json) + "Just extract :CARD--IMAGES. No Processing" + (let ((lst '())) + (dolist (node json lst) + (let ((node-id (assoc-utils:aget node :id)) + (maybe-linkmarkers + (assoc-utils:aget node :linkmarkers))) + (when maybe-linkmarkers + (push (list node-id maybe-linkmarkers) lst)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Working with CARD--SETS + +;; Here, JSON shuld be: (extract-card-sets json) +(defun extract-set-tag (json tag) + (sort (remove-duplicates + (extract-json-tag json tag) + :test #'string=) + #'string<)) + +(defun extract-set-name (json) + (extract-set-tag json :set--name)) + +(defun extract-set-code (json) + (extract-set-tag json :set--code)) + +(defun extract-set-rarity (json) + (extract-set-tag json :set--rarity)) + +(defun extract-set-rarity-code (json) + (extract-set-tag json :set--rarity--code)) + +(defun extract-set-edition (json) + (extract-set-tag json :set--edition)) + +(defun extract-set-price (json) + (extract-set-tag json :set--price)) + +(defun extract-set-url (json) + (extract-set-tag json :set--url)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Card Images +(defun extract-card-main-image (json) + "From :CARD--IMAGES, extract the first IMAGE--URL. The other two are small and cropped images." + (sort + (mapcar (lambda (alist) (assoc-utils:aget alist :image--url)) + (extract-concat json :card--images)) + #'string<)) + +(defun gen-hash-table (ht class tags) + "Just a list mapping like \"Normal\" => 1, \"Tested\" => 2, and so on. Could be used for anything that has a NAME column." + (v:info :ygoprodeck.json "GEN-HASH-TABLE: ~a" class) + (with-connection (db) + (with-transaction + (dolist (tag tags ht) + (unless (gethash tag ht) + (setf (gethash tag ht) + (or (mito:find-dao class :name tag) + (mito:create-dao class :name tag)))))))) + +(defun gen-hash-mapping-table (class tags &key (test 'equal)) + "A more complex mapping of PASSCODE -> DATA" + (let ((ht (make-hash-table :test test))) + (with-connection (db) + (with-transaction + (dolist (node tags ht) + (let ((passcode-id (getf node :passcode-id))) + (setf (gethash passcode-id ht) + (or (mito:find-dao class :passcode-id passcode-id) + (apply #'mito:create-dao class node))))))))) + +(defun gen-hash-map-list-table (class tags &key (test 'equal)) + "A more complex mapping of PASSCODE -> (DATA, DATA, DATA)" + (let ((ht (make-hash-table :test test))) + (with-connection (db) + (with-transaction + (dolist (node tags ht) + (let ((passcode-id (getf node :passcode-id))) + (push (or (apply #'mito:find-dao class node) + (apply #'mito:create-dao class node)) + (gethash passcode-id ht)))))))) + +(defun process-card-nodes (json) + "Mostly preserved from V2." + (let* ((base-fields (json-cardinfo-base-fields json)) + (id (assoc-utils:aget base-fields :id)) + (args (list + :name (assoc-utils:aget base-fields :name) + :desc (assoc-utils:aget base-fields :desc) + :archetype (gethash (assoc-utils:aget base-fields :archetype) +ygo-card-archetypes+) + :attribute (gethash (assoc-utils:aget base-fields :attribute) +ygo-card-attributes+) + :frame-type (gethash (assoc-utils:aget base-fields :frame-type) +ygo-card-frame-types+) + :race (gethash (assoc-utils:aget base-fields :race) +ygo-card-races+) + :card-type (gethash (assoc-utils:aget base-fields :type) +ygo-card-types+) + :atk (assoc-utils:aget base-fields :atk) + :def (assoc-utils:aget base-fields :def) + :scale (assoc-utils:aget base-fields :scale) + :level (assoc-utils:aget base-fields :level))) + (found (or (mito:find-dao 'ygo-card :id id) + (apply #'create-dao 'ygo-card :id id args)))) + (unless (gethash id +ygo-cards+) + (setf (gethash id +ygo-cards+) found)))) + +(defun process-card-sets (&rest sets) + "Mostly preserved from V2." + (dolist (node sets +ygo-sets+) + (let* ((args + (list :passcode-id (assoc-utils:aget node :passcode-id) + :code (gethash (assoc-utils:aget node :set--code) +ygo-set-codes+) + :name (gethash (assoc-utils:aget node :set--name) +ygo-set-names+) + :edition (gethash (assoc-utils:aget node :set--edition) +ygo-set-editions+) + :rarity (gethash (assoc-utils:aget node :set--rarity) +ygo-set-rarity+) + :rarity-code (gethash (assoc-utils:aget node :set--rarity--code) +ygo-set-rarity-codes+))) + (rest-args + (list :price (assoc-utils:aget node :set--price) + :url (assoc-utils:aget node :set--url))) + (found (or (apply #'mito:find-dao 'ygo-set args) + (apply #'mito:create-dao 'ygo-set (append args rest-args))))) + (pushnew found + (gethash (assoc-utils:aget node :passcode-id) +ygo-sets+) + :test #'mito:object=)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO for Import-v4 - It might be useful to have PASSCODE as the ID, +;; that way, there is a uniform interface for querying card info... +;; +;; TODO Split this up? +(defun pre-process-nodes (json) + (let ((card-names (extract-card-name json)) + (card-types (extract-card-type json)) + (card-races (extract-card-race json)) + (card-frame-types (extract-card-frame-type json)) + (card-archetypes (extract-card-archetype json)) + (card-attributes (extract-card-attribute json)) + (card-sets (extract-card-sets json))) + + (gen-hash-table +ygo-card-names+ 'ygo-card-name card-names) + (gen-hash-table +ygo-card-archetypes+ 'ygo-card-archetype card-archetypes) + (gen-hash-table +ygo-card-attributes+ 'ygo-card-attribute card-attributes) + (gen-hash-table +ygo-card-frame-types+ 'ygo-card-frame-type card-frame-types) + (gen-hash-table +ygo-card-races+ 'ygo-card-race card-races) + (gen-hash-table +ygo-card-types+ 'ygo-card-type card-types) + + (gen-hash-table +ygo-set-names+ 'ygo-set-name (extract-set-name card-sets)) + (gen-hash-table +ygo-set-codes+ 'ygo-set-code (extract-set-code card-sets)) + (gen-hash-table +ygo-set-rarity+ 'ygo-set-rarity (extract-set-rarity card-sets)) + (gen-hash-table +ygo-set-editions+ 'ygo-set-edition (extract-set-edition card-sets)) + (gen-hash-table +ygo-set-rarity-codes+ 'ygo-set-rarity-code (extract-set-rarity-code card-sets)) + + (gen-hash-table +ygo-banlist-names+ 'ygo-banlist-name (extract-card-banlist-info card-sets)) + (gen-hash-table +ygo-linkmarker-names+ 'ygo-linkmarker-name (extract-card-linkmarker-names card-sets)) + (gen-hash-table +ygo-format-names+ 'ygo-format-name (extract-card-format-names json)))) + +(defun pre-process-card-prices (json) + (setf +ygo-card-prices+ (gen-hash-mapping-table 'ygo-price (extract-card-price-map json) :test 'eq))) + +(defun pre-process-card-alternative-artwork (json) + (setf +ygo-alternative-artwork+ (gen-hash-map-list-table 'ygo-alternative-artwork (extract-card-alternative-images-map json) :test 'eq))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun process-all-nodes (&optional (json *json*)) + "Process all nodes from JSON." + (v:info :ygoprodeck.json "PROCESS-ALL-NODES: PRE-PROCESS-NODES") + (pre-process-nodes json) + (pre-process-card-prices json) + (pre-process-card-alternative-artwork json) + + ;; +ygo-cards+ + (v:info :ygoprodeck.json "PROCESS-ALL-NODES: PROCESS-CARD-NODES") + (with-connection (db) + (with-transaction + (mapcan #'process-card-nodes json))) + + ;; +ygo-sets+ + (v:info :ygoprodeck.json "PROCESS-ALL-NODES: PROCESS-CARD-SETS") + (with-connection (db) + (with-transaction + (apply #'process-card-sets (extract-card-set-map json))))) diff --git a/src/models/ygoprodeck-methods.lisp b/src/models/ygoprodeck-methods.lisp new file mode 100644 index 0000000..e6815e6 --- /dev/null +++ b/src/models/ygoprodeck-methods.lisp @@ -0,0 +1,258 @@ +#| + +src/models/ygoprodeck-2.lisp + +Yu-Gi-Oh! Pro Deck Database Interface v2 + +TODO Documentation + +|# + +(in-package #:cl-deck-builder2.models.ygoprodeck.methods) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmacro ygo-card (&body body) + `(with-includes + 'ygo-card + (mito:includes + 'ygo-card-archetype + 'ygo-card-attribute + 'ygo-card-frame-type + 'ygo-card-race + 'ygo-card-type) + ,@body)) + +(defmacro ygo-set (&body body) + `(with-includes 'ygo-set + (mito:includes + 'ygo-card + 'ygo-set-code + 'ygo-set-edition + 'ygo-set-name + 'ygo-set-rarity + 'ygo-set-rarity-code) + ;; (mito:includes + ;; 'ygo-card-name) + ,@body)) + +(defun ygo-set-by-id (id) + (first (ygo-set (sxql:where (:= :id id))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod split-code ((set-code ygo-set-code)) + (cl-ppcre:split "-" (name-of set-code))) + +(defmethod ygo-card-sets ((obj ygo-card)) + (ygo-card-sets (ygo-passcode-of obj))) + +(defmethod ygo-card-sets ((passcode integer)) + ;; If alternate IDs exist, just mush them in + (let ((alt-id (has-alternative-artwork passcode))) + (when alt-id + ;; XXX Why the long accessor name here? How can we get this accessor? Maybe I have to specify it manually. + (with-slots ((alternate-id cl-deck-builder2.models.ygoprodeck.classes::alternate-id)) alt-id + (setf passcode alternate-id))) + (let ((sets + (ygo-set + ;; (sxql:left-join :ygo_card_name :on (:= :ygo-card.name-id :ygo-card-name.id)) + (sxql:where (:= :passcode-id passcode)) + (sxql:order-by :price :desc)))) + sets))) + +;; Only showing cards with inventory + +;; (with-connection (db) +;; (mito:select-dao 'cl-deck-builder2.models.ygoprodeck.classes::ygo-set-item +;; (sxql:where (:and (:in :item_id +;; (sxql:select :id +;; (sxql:from :ygo_set) +;; (sxql:where (:= :passcode-id 89631139)))) +;; (:> :qty 0))))) + +;; (mapcar (alexandria:compose #'cl-deck-builder2.models.ygoprodeck2::ygo-card-sets #'deck-passcode-of) +;; (retrieve-dao 'deck-item :deck-id 5)) + +;; (reduce #'+ (mapcar (alexandria:compose #'read-from-string #'cl-deck-builder2.models.ygoprodeck2::ygo-price-of) +;; (cl-deck-builder2.models.ygoprodeck2::ygo-card-sets (deck-passcode-of (find-dao 'deck-item))))) + +;; price of a deck by id using #'first of the ygo-card-sets +;; +;; (reduce '+ (mapcar (alexandria:compose #'read-from-string #'cl-deck-builder2.models.ygoprodeck.classes::ygo-price-of) +;; (mapcar #'first +;; (mapcar (alexandria:compose #'cl-deck-builder2.models.ygoprodeck.methods::ygo-card-sets #'deck-passcode-of) +;; (retrieve-dao 'deck-item :deck-id 308))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun ygo-card-by-name (name &optional (test :=)) + (let ((clauses (case test + (:= (sxql:where (:= :name name))) + (:like (sxql:where (:like :name (format nil "%~a%" name))))))) + (ygo-card clauses))) + +(defun ygo-card-by-passcode (passcode) + (first (ygo-card (sxql:where (:= :id passcode))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun ygo-set-by-code (name &optional (test :=)) + (let ((clauses (case test + (:= (sxql:where (:= :name name))) + (:like (sxql:where (:like :name (format nil "%~a%" name))))))) + (ygo-set + (sxql:where (:in :code-id + (sxql:select :id + (sxql:from (sxql:make-sql-symbol + (mito.class.table:table-name + (find-class 'ygo-set-code)))) + clauses)))))) + +(defun ygo-set-by-name (name &optional (test :=)) + (let ((clauses (case test + (:= (sxql:where (:= :set_name name))) + (:like (sxql:where (:like :set_name (format nil "%~a%" name))))))) + (ygo-set + (sxql:where (:in :code-id + (sxql:select :id + (sxql:from (sxql:make-sql-symbol + (mito.class.table:table-name + (find-class 'ygo-set-name)))) + clauses)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun ygo-select-set-item (item-id variant-id) + "Helper function. Select a single YGO-SET-ITEM by ITEM-ID and VARIANT-ID." + (mito:select-dao 'ygo-set-item + (mito:includes 'ygo-set 'variant-condition) + (sxql:where (:and (:= :item-id item-id) + (:= :variant-id variant-id))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; https://lispcookbook.github.io/cl-cookbook/clos.html#pretty-printing +;; +;; (defmethod print-object ((obj person) stream) +;; (print-unreadable-object (obj stream :type t) +;; (with-accessors ((name name) +;; (lisper lisper)) +;; obj +;; (format stream "~a, lisper: ~a" name lisper)))) +;; +;; (defmethod print-object ((obj person) stream) +;; (print-unreadable-object (obj stream :type t) +;; (format stream "~a, lisper: ~a" (name obj) (lisper obj)))) +;; +;; (defmethod print-object ((obj person) stream) +;; (print-unreadable-object (obj stream :type t :identity t))) +;; +;; Caution: trying to access a slot that is not bound by default will lead to an error. Use slot-boundp. + +(defmethod print-object ((obj ygo-card) stream) + (print-unreadable-object (obj stream :type t) + (let ((args)) + (when (ygo-passcode-of obj) + (push (ygo-passcode-of obj) args)) + (ignore-errors + (with-accessors ((name name-of)) + obj + (when (and (slot-boundp obj 'name) + (slot-value obj 'name)) + (push (name-of name) args)))) + (when args + (format stream "~{~a~^ - ~}" (reverse args)))))) + +(defmethod print-object ((obj ygo-set) stream) + (print-unreadable-object (obj stream :type t) + (let ((args)) + (when (mito:object-id obj) + (push (mito:object-id obj) args)) + (ignore-errors + (with-accessors ((passcode ygo-passcode-of) + (name name-of) + (code ygo-code-of) + (rarity ygo-rarity-of) + (edition ygo-edition-of)) + obj + (when (and (slot-boundp obj 'passcode) + (slot-value obj 'passcode)) + (push (ygo-passcode-of passcode) args)) + (when (and (slot-boundp obj 'code) + (slot-value obj 'code)) + (push (ygo-set-code-of code) args)) + (when (and (slot-boundp obj 'rarity) + (slot-value obj 'rarity)) + (push (ygo-rarity-of rarity) args)) + (when (and (slot-boundp obj 'edition) + (slot-value obj 'edition)) + (push (ygo-edition-of edition) args)) + (when (and (slot-boundp obj 'name) + (slot-value obj 'name)) + (push (name-of name) args)))) + (when args + (format stream "~{~a~^ - ~}" (reverse args)))))) + +(defun ygo-card-names-from-list (&rest args) + (with-connection (db) + (with-transaction + (mito:retrieve-by-sql + (sxql:select (:ygo_card.id :name) + (sxql:from :ygo_card) + (sxql:left-join :ygo_card_name :on (:= :ygo_card.name_id :ygo_card_name.id)) + (sxql:where (:in :ygo_card_name.id + (sxql:select :name_id + (sxql:from :ygo_card) + (sxql:where (:in :id args)))))))))) + +(defun ygo-card-names-by-deck-id (id) + (with-connection (db) + (with-transaction + (mito:retrieve-by-sql + (sxql:select (:ygo_card.id :name) + (sxql:from :ygo_card) + (sxql:left-join :ygo_card_name :on (:= :ygo_card.name_id :ygo_card_name.id)) + (sxql:where (:in :ygo_card_name.id + (sxql:select :name_id + (sxql:from :ygo_card) + (sxql:where (:in :id + (sxql:select :passcode + (sxql:from :deck_item) + (sxql:where (:= :deck_id id))))))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun count-all-dao () + (let ((class-list (append (registered-classes (registry) :cl-deck-builder2.models.ygoprodeck.fields) + (registered-classes (registry) :cl-deck-builder2.models.ygoprodeck.classes))) + (lst '())) + (with-connection (db) + (with-transaction + (dolist (class class-list lst) + (push (list class (mito:count-dao class)) lst)))))) + +;; If you have altnerative artwork and you are the main card, youll get a list with length > 1 +;; If you have alternative artwork and you are an alternative artwork card, you'll get the main list with length 1 +(defmethod has-alternative-artwork ((id integer)) + (find-dao 'ygo-alternative-artwork :passcode-id id)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; So in the CSV importer I had a function PRE-SEED-DB-ITEMS that +;; would copy CC-ITEM x5 VARIANT-CONDITION to seed something like 250k +;; items in the db. We're just going to duplicate the logic here. +;; +;; INPUT: (select-dao 'ygo-set) +(defun pre-seed-db-items (ygo-sets) + (let ((variants (select-dao 'variant-condition))) + (with-connection (db) + (with-transaction + (dolist (itm ygo-sets) + (dolist (variant variants) + (mito:create-dao 'ygo-set-item :item-id (mito:object-id itm) + :variant-id (mito:object-id variant)))) + (mito:count-dao 'ygo-set-item))))) + +(defun set-ygo-set-item-qty (amt) + "Helper function. Set the QTY of every YGO-SET-ITEM to AMT." + (with-connection (db) + (with-transaction + (mito:execute-sql + (sxql:update (sxql:make-sql-symbol + (mito.class.table:table-name + (find-class 'ygo-set-item))) + (sxql:set= :qty amt)))))) diff --git a/src/models/ygoprodeck.lisp b/src/models/ygoprodeck.lisp new file mode 100644 index 0000000..41e47a2 --- /dev/null +++ b/src/models/ygoprodeck.lisp @@ -0,0 +1,38 @@ +#| + +src/models/ygoprodeck.lisp + +Yu-Gi-Oh! Pro Deck JSON Importer v1 + +Looks like this is old/outdated code and due to be deleted soon! + +|# + +(in-package #:cl-deck-builder2.models.ygoprodeck) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; These models correspond to the YGOProDeck JSON dump. API Guide: +;; https://ygoprodeck.com/api-guide/ +(defclass ygo-info () + ((desc :accessor ygo-description-of :col-type :text) + (frame-type :accessor ygo-frame-type-of :col-type :text) + (type :accessor ygo-type-of :col-type :text) + (passcode :accessor ygo-passcode-of :col-type :integer) + (name :accessor name-of :col-type :text) + (race :accessor ygo-race-of :col-type :text) + (archetype :accessor ygo-archetype-of :col-type (or :text :null)) + (attribute :accessor ygo-attribute-of :col-type (or :text :null)) + (linkmarkers :accessor ygo-linkmarkers-of :col-type (or :text :null)) + (atk :accessor ygo-atk-of :col-type (or :integer :null)) + (def :accessor ygo-def-of :col-type (or :integer :null)) + (level :accessor ygo-level-of :col-type (or :integer :null)) + (linkval :accessor ygo-linkval-of :col-type (or :integer :null)) + (scale :accessor ygo-scale-of :col-type (or :integer :null))) + (:metaclass registered-table-class)) + +;; banlist_info +;; card_images +;; card_prices +;; card_sets +;; jq '.[][]|{card_sets}|select( .card_sets != null )|.[][]' cardinfo.php + diff --git a/src/toolkit/git.lisp b/src/toolkit/git.lisp new file mode 100644 index 0000000..9112a42 --- /dev/null +++ b/src/toolkit/git.lisp @@ -0,0 +1,70 @@ +#| + +src/toolkit/git.lisp + +Tools For Working with git repository information + +e.g. commit id, commit author, message, date, etc. + +TODO Use INFERIOR-SHELL? Turns out it just calls UIOP:RUN-PROGRAM... + +|# + +(in-package #:cl-deck-builder2.toolkit.git) + +(defparameter *git-directory* + (relative-pathname ".git") + "The base path to the directory we run git in during UIOP:RUN-PROGRAM.") + +(defparameter +git-binary+ + (format nil "git --no-pager --git-dir ~a" *git-directory*) + "The full git command line, with *GIT-DIRECTORY* passed in already. What could go wrong?") + +(defparameter +git-log+ + (concatenate 'string + +git-binary+ + " log --format=\"%h%nAuthor: %an%nDate: %cD%n%s%n%b%n----\"") + "Full command line to git log.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun git-revision () + "Query git rev-parse for the current HEAD revision id." + (let ((command + (concatenate 'string + +git-binary+ + " rev-parse --short HEAD"))) + (string-trim '(#\Newline) + (uiop:run-program command :output :string :error-output nil)))) + +(defun git-log () + "Query git log into a somewhat parseable format." + (cl-ppcre:split + "\\n+----\\n" + (uiop:run-program +git-log+ :output :string))) + +;; Not terrible... +(defun parse-git-log (msg) + "Split the output of GIT-MESSAGES-LIST into an actual LIST. + +ARGUMENTS +MSG The output of GIT-MESSAGES-LIST. + +SEE GIT-LOG" + (let ((state :in-commit) + (c '())) + (loop for line in (split-sequence:split-sequence #\Newline msg) do + (case state + (:in-commit (setf (getf c :commit) line + state :in-author)) + (:in-author (setf (getf c :author) line + state :in-date)) + (:in-date (setf (getf c :date) line + state :in-subject)) + (:in-subject (setf (getf c :subject) line + state :in-message)) + (:in-message (setf (getf c :message) + (format nil "~a~a~%" + (getf c :message "") + line))))) + c)) diff --git a/src/toolkit/money.lisp b/src/toolkit/money.lisp new file mode 100644 index 0000000..18a7db9 --- /dev/null +++ b/src/toolkit/money.lisp @@ -0,0 +1,62 @@ +#| + +Money Stuff + +Storing Floating Point values in an SQL database is *BAD*!: + + CL-DECK-BUILDER2> (with-connection (db) + (mito:retrieve-by-sql + (sxql:select ((:as 0.005 :real))))) + ((:REAL 0.004999999888241291d0)) + +So I'm taking expert's advice and storing money as a fixed point integer with 100 divisions (cents). + +Deflate: Mito -> RDBMS + +Now with test suite! + +|# + +(in-package #:cl-deck-builder2.toolkit.money) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defgeneric currency-deflate (obj) + (:documentation "CURRENCY-DEFLATE takes a NUMBER or a STRING and converts it into a fixed point number. + + ;; One Half Dollar / $0.50 / 50 Cents / These are all the same thing + (CURRENCY-DEFLATE 1/2) => 50 + (CURRENCY-DEFLATE 0.50) => 50 + (CURRENCY-DEFLATE 0.5d0) => 50 + (CURRENCY-DEFLATE \"1/2\") => 50 + (CURRENCY-DEFLATE \"0.50\") => 50 + (CURRENCY-DEFLATE \"0.5d0\") => 50 + + ;; One Dollar / $1.00 / 100 Cents / These are all the same thing + (CURRENCY-DEFLATE 1) => 100 + (CURRENCY-DEFLATE 1.00) => 100 + (CURRENCY-DEFLATE 1.0d0) => 100 + (CURRENCY-DEFLATE \"1\") => 100 + (CURRENCY-DEFLATE \"1.00\") => 100 + (CURRENCY-DEFLATE \"1.0d0\") => 100 + + ;; Ten Dollars / $10.00 / 1000 Cents / These are all the same thing + (CURRENCY-DEFLATE 10) => 1000 + (CURRENCY-DEFLATE 10.00) => 1000 + (CURRENCY-DEFLATE 10.0d0) => 1000 + (CURRENCY-DEFLATE \"10\") => 1000 + (CURRENCY-DEFLATE \"10.00\") => 1000 + (CURRENCY-DEFLATE \"10.0d0\") => 1000") + (:method ((number number)) + ;; CEILING or FLOOR? + (ceiling (* 100 number))) + (:method ((string string)) + (currency-deflate + (read-from-string + (if (eq (char string 0) #\$) + (subseq string 1) + string))))) + +(defgeneric currency-inflate (obj) + (:documentation "CURRENCY-INFLATE takes a NUMBER and converts it into a STRING representation of the fixed point amount of cents (100 cents per dollar) into \"$DD.CC\" format.") + (:method ((number number)) + (format nil "~$" (float (/ number 100))))) diff --git a/src/toolkit/packages.lisp b/src/toolkit/packages.lisp new file mode 100644 index 0000000..362b4fb --- /dev/null +++ b/src/toolkit/packages.lisp @@ -0,0 +1,62 @@ +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.toolkit.paths + (:use #:cl) + (:import-from #:cl-deck-builder2.config + #:*public-directory* + #:*static-directory*) + (:export #:relative-pathname + #:public-pathname + #:static-pathname + #:ygo-pathname + + #:image-files-list + #:make-static-filename-kind + #:probe-file-list + #:probe-image-files-list + #:relative-file-list + #:static-probe-file-list + #:ydk-rename-files + #:ygo-probe-file-list + #:random-pathname-name)) + +;; #:CL-DECK-BUILDER2.TOOLKIT.GIT depends on #:CL-DECK-BUILDER2.TOOLKIT.PATHS +(defpackage #:cl-deck-builder2.toolkit.git + (:use #:cl) + (:import-from #:cl-deck-builder2.toolkit.paths + #:relative-pathname) + (:export #:git-revision + #:parse-git-log + #:git-log)) + +(defpackage #:cl-deck-builder2.toolkit.money + (:use #:cl) + (:export #:currency-inflate + #:currency-deflate)) + +(defpackage #:cl-deck-builder2.toolkit.time + (:use #:cl) + (:export #:generate-status-report)) + +(defpackage #:cl-deck-builder2.toolkit.utils + (:use #:cl) + (:export #:grouped + #:normalize-newlines + #:strip-bom + + #:query-param + #:generate-pages + #:get-opposite-direction + + #:rle-encode + #:rle-encode-plist + #:rle-decode + #:latex-escape)) + +(uiop:define-package #:cl-deck-builder2.toolkit + (:use #:cl) + (:use-reexport #:cl-deck-builder2.toolkit.git + #:cl-deck-builder2.toolkit.money + #:cl-deck-builder2.toolkit.paths + #:cl-deck-builder2.toolkit.time + #:cl-deck-builder2.toolkit.utils)) diff --git a/src/toolkit/paths.lisp b/src/toolkit/paths.lisp new file mode 100644 index 0000000..7a15fe8 --- /dev/null +++ b/src/toolkit/paths.lisp @@ -0,0 +1,149 @@ +#| + +src/toolkit/paths.lisp + +Utilities For Working With Directories, Files, Paths, etc. + +|# + +(in-package #:cl-deck-builder2.toolkit.paths) + +;;;; Code for munging paths and finding files at paths and renaming +;;;; files and that sort of thing. + +(defvar +ygoprodeck-images-root+ + (merge-pathnames #P"ygoprodeck/" *public-directory*) + "The path to where the web server can display YGOProDeck images.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun probe-file-list (seq defaults) + "PROBE-FILE-LIST wille apply PROBE-FILE to every file in SEQ. + +It appears we also do some MERGE-PATHNAMES trickery." + (mapcar (lambda (filename) + (probe-file + (merge-pathnames filename defaults))) + seq)) + +(defun relative-file-list (seq &key (type "jpg")) + "RELATIVE-FILE-LIST takes a LIST of files and transforms them into data that can be presented on the web interface, e.g. for A HREF URLs. + +RELATIVE is in the title because we use PATHNAME-NAME to strip out any directory components on elements of SEQ. + +ARGUMENTS: +SEQ A LIST of files. +DEFAULTS This is passed as the defaults argument to MERGE-PATHNAMES. + +SEE STATIC-RELATIVE-FILE-LIST +SEE YGO-RELATIVE-FILE-LIST" + (mapcar (lambda (file) + (make-pathname :name file :type type)) + (mapcar #'pathname-name seq))) + +(defun static-probe-file-list (seq) + "STATIC-PROBE-FILE-LIST takes a LIST of Yu-Gi-Oh! passcodes and transforms them into data that can be presented on the web interface,e.g. for A HREF URLs. + +STATIC-PROBE-FILE-LIST takes a LIST of files and transforms them into data that can be presented on the web interface, e.g. for A HREF URLs. We use *PUBLIC-DIRECTORY* as the root directory for the output. + +ARGUMENTS: +SEQ A LIST of files. + +SEE RELATIVE-FILE-LIST" + (probe-file-list seq + (relative-pathname *public-directory*))) + +(defun ygo-probe-file-list (seq) + "YGO-RELATIVE-FILE-LIST takes a LIST of Yu-Gi-Oh! passcodes and transforms them into data that can be presented on the web interface,e.g. for A HREF URLs. + +ARGUMENTS: +SEQ A LIST of files. + +SEE RELATIVE-FILE-LIST" + (probe-file-list + (relative-file-list (mapcar #'princ-to-string seq) :type "jpg") + (relative-pathname +ygoprodeck-images-root+))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun make-static-filename-kind (pathname-name maybe-list &key (type "jpg")) + "We use UIOP:ENSURE-LIST so this may be called with one LIST argument. + +TODO That seems kinda hacky to me." + (merge-pathnames + (make-pathname :name (format nil "~A-~{~A~^-~}" + pathname-name + (uiop:ensure-list maybe-list)) + :type type) + *public-directory*)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Get the files from the file system .. so sloppy ... +(defun image-files-list (name) + (mapcar + (lambda (kind) + (make-static-filename-kind name kind)) + '("MAIN" "EXTRA" "SIDE" "MAIN-EXTRA" "MAIN-SIDE" "FINAL"))) + +(defun probe-image-files-list (name &optional (strip t)) + (let* ((pathname *public-directory*) + (len (length (namestring pathname)))) + (remove + NIL + (mapcar + (lambda (path) + (if (probe-file path) + (if strip + (subseq (namestring path) len) + path))) + (image-files-list name))))) + +;; TODO UIOP:ENOUGH-PATHNAME + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This had to be here, in WEB package probably, because YDK depends +;; on TOOLKIT so it can't go in TOOLKIT because it depends on YDK: it +;; uses YDK-NAME-OF. Perhaps I could pass just a name into this and +;; make it deck agnostic. +;; +;; Okay I've done exactly that. +(defun ydk-rename-files (old-name new-name) + "Rename the files from DECK if there are any files according to PROBE-IMAGE-FILES-LIST. + +If there are images, NEW-NAME will replace the PATHNAME-NAME of the old images." + (let* ((old-files (probe-image-files-list old-name nil)) + (new-files + (mapcar (lambda (path) + (merge-pathnames + (make-pathname :name path :type "jpg") *public-directory*)) + (mapcar (lambda (old-path) + (cl-ppcre:regex-replace-all old-name (pathname-name old-path) new-name)) + old-files)))) + ;; (format t "
      ~a
      " (mapcar #'list old-files new-files)))) + (mapcar (lambda (pair) + (when (probe-file (car pair)) + (rename-file (car pair) (cadr pair)))) + (mapcar #'list old-files new-files)))) + +(defun relative-pathname (name) + (asdf:system-relative-pathname :cl-deck-builder2 name)) + +(defun public-pathname (name) + (merge-pathnames name *public-directory*)) + +(defun static-pathname (name) + (merge-pathnames name *static-directory*)) + +(defun ygo-pathname (name) + (merge-pathnames name +ygoprodeck-images-root+)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Was originally from files.lisp + +(defun digest-file-string (digest-spec pathspec &rest args) + (format nil "~{~x~}" + (coerce (apply #'ironclad:digest-file digest-spec pathspec args) + 'list))) + +;; (random-filename (pathname-type pathspec)) => uuid.type +(defun random-pathname-name (&key type) + (let ((uuid (uuid:print-bytes nil (uuid:make-v4-uuid)))) + (format nil "~a~@[.~a~]" uuid type))) diff --git a/src/toolkit/time.lisp b/src/toolkit/time.lisp new file mode 100644 index 0000000..47c4d98 --- /dev/null +++ b/src/toolkit/time.lisp @@ -0,0 +1,89 @@ +;; I noticed I've been doing a lot of work with time. This is time +;; helpers. + +(in-package #:cl-deck-builder2.toolkit.time) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun time-format (when &optional (format local-time:+rfc3339-format+)) + (local-time:format-timestring nil when :format format)) + +(defun time-format/date-only (when) + (time-format when local-time:+rfc3339-format/date-only+)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun day- (when &optional (amount 1)) + "Subtract AMOUNT days from WHEN." + (local-time:timestamp- when amount :day)) + +(defun day+ (when &optional (amount 1)) + "Add AMOUNT days to WHEN." + (local-time:timestamp+ when amount :day)) + +(defun month- (when &optional (amount 1)) + "Subtract AMOUNT months from WHEN." + (local-time:timestamp- + (local-time:timestamp- when amount :month) + local-time:+seconds-per-day+ :sec)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun time-now () + "Today's date, passed through FORMAT-DMY." + (local-time:now)) + +(defun time-yesterday () + "Yesterday's date, passed through FORMAT-DMY." + (day- (time-now))) + +;; TODO Why am I using this? +(defun time-first-of-month () + "The first day of this month." + (local-time:timestamp-minimize-part + (time-now) :day)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun n-month-list (&key (when (time-now)) (start 1) (n 6)) + "Produce a LIST of the last N months, using WHEN as the reference point. +The values iterate numerically from START, that is, the result will be length N - START. +The date will be on the first of each month, in chronological order." + (mapcar (lambda (n) + (month- when n)) + (loop for i from start upto n collect i))) + +(defun n-day-list (&key (when (time-now)) (start 0) (n 5)) + "Produce a LIST of the last N days, using WHEN as a reference point. +The values will iterate numerically from START, that is, the result will be length N - START. +The exact times will not be modified. The results will be in chronological order." + (mapcar (lambda (n) + (day- when n)) + (loop for i from start upto n collect i))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun time-friday (&key (when (time-now))) + (local-time:adjust-timestamp + when + (offset :day-of-week :friday))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun format-status-report (&key (stream *standard-output*) (when (time-friday))) + (let ((date-str (time-format/date-only when)) + (day-map (reverse + (mapcar #'time-format/date-only (n-day-list :when when :n 4))))) + (format stream + "#+TITLE: Status Report: Week of ~A~%~%* Status Report: ~ +Week of ~A~%~%Total hours: 40 h.~%~%~{** ~A~%~%Hours: 0800A-1600P (8h)~%~%~}" + date-str date-str day-map))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *status-report-base-dir* + (probe-file #P"~/code/cl-deck-builder2/doc/status-report/")) + +(defun generate-status-report (&key (when (time-friday))) + (let ((filespec (merge-pathnames + (format nil "~a.org" (time-format/date-only when)) + *status-report-base-dir*))) + (with-open-file (f filespec + :direction :output + :if-exists :error + :if-does-not-exist :create) + (format-status-report :stream f :when when) + (probe-file filespec)))) diff --git a/src/toolkit/utils.lisp b/src/toolkit/utils.lisp new file mode 100644 index 0000000..28a670d --- /dev/null +++ b/src/toolkit/utils.lisp @@ -0,0 +1,141 @@ +;;;; src/toolkit/toolkit.lisp + +(in-package #:cl-deck-builder2.toolkit.utils) + +;; https://stackoverflow.com/a/11965885 +;; def grouped(l, n): +;; for i in xrange(0, len(l), n): +;; yield l[i:i+n] + +(defun grouped (seq &optional (n 1000)) + "Group elements in a list by some number of elements. + +ARGUMENTS +SEQ The sequence to be grouped. +N The number of elements per grouping." + (loop :for i :upto (1- (length seq)) :by n + :collect (subseq seq i (min (length seq) + (+ i n))))) + +;; Not sure how I arrived at this gist. +;; +;; +;; +;; My code is modified slightly. +(defun normalize-newlines (string) + "Remove or replace #\Return(#\Newline)? sequences with just #\Newline. + +The \#Newline following #\Return is optional, so just #\Return also gets turned into #\Newline. + +ARGUMENTS +STRING The target string." + (ppcre:regex-replace-all + (format nil "~C(\\n)?" #\Return) + string + (format nil "~C" #\Newline))) + +;; Is this necessary? I uploaded a file with a BOM and it exploded. +(defun strip-bom (string) + "If STRING is UTF-8 and contains a UTF-8 BOM it will be removed. + +ARGUMENTS +STRING The string to check." + + (when string + (case (type-of (char string 0)) + (extended-char (subseq string 1)) + (t string)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun query-param (name parsed) + "Parse query param values. Get the value of the specified element NAME in the query PARSED. + +ARGUMENTS +NAME The query to search for in the parameter list. +PARSED The parameter list." + ;; TODO STRING=? STRING-EQUAL?? Maybe I should add a &KEY (test #'STRING=)? + (assoc-utils:aget parsed name)) + +;; (defun generate-pages () +;; "Generate pagination" +;; (let ((pages nil)) +;; (dotimes (i 10) +;; (push (list :id (+ 1 i) :start (* 10 i)) pages)) +;; (reverse pages))) + +;; TODO Passing weird floats to this. How can I put the weird math into here? +;; +;; I made it into a helper function for now. +;; No idea how to even explain this. It works! +(defun generate-pages-helper (start &optional (max 10) (multiplier 10)) + "Generate pagination up to MAX pages, with MULTIPLIER items per page." + ;; We always want 10 pages. If we start at page 0, subtract five, + ;; then take the nearest lowest non-negative integer (0), this gets + ;; us a comfy left bound. + (let ((start- (- start 5))) + ;; If START- is less than zero, make it zero. This gives us a safe + ;; left bound. + (when (< start- 0) + (setf start- 0)) + (loop for i from 0 upto (1- (min (min max 8) (- max start -8))) collect + `(:id ,(+ start- i) :offset ,(* multiplier (+ start- i)))))) + +(defun generate-pages (length offset &optional (limit 10)) + "Generate pagination for LENGTH number of pages, with LIMIT number of items per page, offset into the list by OFFSET number of pages. + +ARGUMENTS +LENGTH The length of the array we're generating pages for. +OFFSET The starting index. +LIMIT The number of entries per page. Default is 10." + (generate-pages-helper (floor (/ offset limit)) + (ceiling (/ length limit)) + limit)) + +(defun get-opposite-direction (direction) + "Get the opposite direction" + (if (string= direction "asc") + "desc" + "asc")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Idea for RLE from PCL by Paul Graham +;; This implementation based on +;; +;; Nope it's not I wrote my own implementation! +(defun rle-encode (lst &key (key #'identity) (test #'eq)) + (loop for line in (mapcar key (remove-duplicates lst :key key :test test)) + collect (cons (count line lst :key key :test test) line))) +;; (sort * ;#'> :key #'car)) + +(defun rle-decode (lst) + (loop for line in lst + nconc (make-list 3 :initial-element (second line)))) + +;; XXX Where does this go? +(defun rle-encode-plist (plist &key (key #'identity) (test #'eq)) + (rle-encode + (mapcar (lambda (plist) + (setf (getf plist :name) + (princ-to-string (getf plist :name))) + plist) + plist) + :key key :test test)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; For The Label Maker +;; https://tex.stackexchange.com/a/119383 +(defun latex-escape (s) + (cl-ppcre:regex-replace-all + "(\\\\backslash)" + (cl-ppcre:regex-replace-all + "~" + (cl-ppcre:regex-replace-all + "(\\^)" + (cl-ppcre:regex-replace-all + "([\$\#&%_{}])" + (cl-ppcre:regex-replace-all "\\" s "\\backslash") + "\\\\\\1") + "\\\\\\1{}") + "\\\\texttt{\\~{}}") + "$\\1$")) + diff --git a/src/view.lisp b/src/view.lisp new file mode 100644 index 0000000..d9d9f09 --- /dev/null +++ b/src/view.lisp @@ -0,0 +1,75 @@ +(in-package :cl-user) + +(defpackage #:cl-deck-builder2.view + (:use #:cl) + (:import-from #:cl-deck-builder2.config + #:*template-directory*) + (:import-from #:cl-deck-builder2.i18n + #:with-locale) + (:import-from #:caveman2 + #:*response* + #:response-headers) + (:import-from #:djula + #:add-template-directory + #:compile-template* + #:render-template* + #:*djula-execute-package*) + (:import-from #:datafly + #:encode-json) + (:export #:render + #:render-json) + (:documentation "The VIEW package. + +We just use the defaults generated by Caveman2. + +It provides rendering functions for web view. + +Additionally, we have i18n support with gettext.")) + +(in-package :cl-deck-builder2.view) + +(djula:add-template-directory *template-directory*) + +(defparameter *template-registry* (make-hash-table :test 'equal) + "Hash table of registered templates. Used for caching.") + +(defun render (template-path &optional env (lang "en_US")) + "Use Djula to render a template." + (let ((template (gethash template-path *template-registry*))) + (unless template + (setf template (djula:compile-template* (princ-to-string template-path))) + (setf (gethash template-path *template-registry*) template)) + (with-locale (lang) + (apply #'djula:render-template* template nil env)))) + +(defun render-json (object) + "Use Datafly to render a JSON object." + (setf (getf (response-headers *response*) :content-type) "application/json") + (encode-json object)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Execute package definition + +(defpackage #:cl-deck-builder2.djula + (:use :cl) + (:import-from :cl-deck-builder2.config + :config + :appenv + :developmentp + :productionp) + (:import-from :caveman2 + :url-for) + (:documentation "The package in which template expressions are evaluated. + +SEE ")) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(setf djula:*djula-execute-package* (find-package :cl-deck-builder2.djula)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(in-package #:djula) + +(djula:def-filter :describe (it) + (with-output-to-string (s) (describe it s))) diff --git a/src/web/builder.lisp b/src/web/builder.lisp new file mode 100644 index 0000000..85164b1 --- /dev/null +++ b/src/web/builder.lisp @@ -0,0 +1,351 @@ +#| + +src/web/builder.lisp + +"The Deck Builder" + +I figure we can also put YDK stuff here since they sorta do the +same thing. Since CSV import comes from Crystal Commerce I've left +that in the crystal-commerce file. + +"The old one used to just have a list of cards, and you would +click it, and add it to the deck." + - Yuki, ~July 2023 + +TODO I think some of these modules are going to have conflicting +names, e.g. RENDER-SAVED-DECK-LIST and SAVED-DECK-LIST ... Maybe +I ought to start packaging this up. + +|# + +(in-package #:cl-deck-builder2.web) + +;; TODO Not in use yet. +;; I really should be using https://github.com/40ants/reblocks +(defclass builder-session () + ((current-deck :accessor builder-session-current-deck + :initform (make-instance 'ydk) + :initarg :deck)) + (:documentation "A BUILDER-SESSION encapsulates a deck building session.")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *current-deck* (make-instance 'ydk) + "An instance of YDK that the builder uses as a working region.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-current-deck-list () + "Helper function. Render the current deck list from *CURRENT-DECK*. + +The HTMX JS in the builder is what this gets output to." + + (let ((deck-info (ydk-query *current-deck*))) + ;; No Deck info? Just return an empty list. + (render-with-env #P"builder/current-deck-list.html" + `(:active "/builder" + :deck-id ,(ignore-errors (ydk-id-of *current-deck*)) + :main-deck ,(ignore-errors (getf deck-info :main-deck)) + :extra-deck ,(ignore-errors (getf deck-info :extra-deck)) + :side-deck ,(ignore-errors (getf deck-info :side-deck)))))) + +(defun render-saved-deck-list (&optional id) + "Helper function. Render the saved deck list from SAVED-DECK-LIST." + (render-with-env #P"builder/saved-deck-list.html" + `(:active "/builder" + :current-deck-id ,(ignore-errors + (ydk-id-of *current-deck*)) + :id ,id + :saved-deck-list ,(select-ydk-deck)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Index route +(defroute ("/builder" :method :GET) (&key _parsed) + "The builder home page. The *CURRENT-DECK* is used as a temporary workspace to create a YDK style deck listing." + (v:info :builder "GET /builder => ~a" _parsed) + + (with-logged-in-user + (render-results :active "/builder" + :class 'ygo-info + :params _parsed + :tpl #P"builder/index.html"))) + +(defroute ("/builder/current-deck-list" :method :GET) () + "The endpoint for RENDER-CURRENT-DECK-LIST. + +TODO This should be re-written as a Redblocks Widget." + + ;; Haha, I moved the hack somewhere else. Now it's somebody elses + ;; problem. I actually think I fixed the hack. This comment stays + ;; for now. + (with-logged-in-user + (render-current-deck-list))) + +(defroute ("/builder/saved-deck-list" :method :GET) () + "The endpoint for RENDER-SAVED-DECK-LIST. + +TODO This should be re-written as a Redblocks Widget." + (with-logged-in-user + (render-saved-deck-list))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Search route +(defroute ("/builder/search" :method :POST) (&key _parsed) + "Main deck builder card search route. Will return a list of data from YGOProDeck. Now souped up and using an ALIST to pass parameters around." + (v:info :builder "POST /builder/search => ~a~%" _parsed) + + (with-logged-in-user + (render-results :active "/builder" + :class 'ygo-info + :params _parsed + :tpl #P"builder/search-results.html"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; POST ROUTES +;; +;; TODO Review Logic +;; +;; Always Extra, then Side +;; "fusion" +;; "link" +;; "ritual" +;; "synchro" +;; "xyz" +;; "fusion_pendulum" +;; "ritual_pendulum" +;; "synchro_pendulum" +;; "xyz_pendulum" +;; +;; TODO Pretty Gnarly! +(defroute ("/builder/add/:passcode" :method :POST) (&key passcode _parsed) + "Try to add PASSCODE to *CURRENT-DECK*. + +TODO Parameters: PASSCODE DECK" + (v:info :builder "POST /builder/add/~d => ~a" passcode _parsed) + + ;; Clumsily enforce login + (unless (logged-in-p) + (_ "Oops")) + + (let ((passcode (ignore-errors + (parse-integer passcode))) + (target-deck (alexandria:make-keyword + (string-upcase + (or + (query-param "deck" _parsed) + "MAIN"))))) + (when passcode + (let* ((card-info (ygo-select-info-by-passcode passcode)) + (frame-type (ygo-frame-type-of card-info))) + (labels ((is-special (frame-type) + (or + ;; First check what kind of monster it is + (string= "fusion" frame-type) + (string= "link" frame-type) + ;; Ritual cards go in the main deck... + ;; (string= "ritual" frame-type) + (string= "synchro" frame-type) + (string= "xyz" frame-type) + (string= "fusion_pendulum" frame-type) + ;; (string= "ritual_pendulum" frame-type) + (string= "synchro_pendulum" frame-type) + (string= "xyz_pendulum" frame-type))) + (try-add (frame-type passcode target-deck) + (v:info :builder "TRY-ADD ~a ~a ~a" frame-type passcode target-deck) + (cond ((is-special frame-type) + ;; It's a Special Summon - try to add it to the Extra Deck, then Side Deck. + (cond ((< (length (ydk-extra-deck-of *current-deck*)) +extra-deck-card-limit+) + ;; Add to extra deck + (ydk-add *current-deck* :extra passcode)) + ((< (length (ydk-side-deck-of *current-deck*)) +extra-deck-card-limit+) + ;; Add to side deck + (ydk-add *current-deck* :side passcode)) + (t (v:info :builder (_ "Deck ~a has too many cards~%") (ydk-name-of *current-deck*))))) + ;; So if we get here theoretically the card shouldn't be a "Special Summon" + ((and (eq target-deck :main) + (< (length (ydk-main-deck-of *current-deck*)) +main-deck-card-limit+)) + ;; Add to main deck + (ydk-add *current-deck* target-deck passcode)) + ((and (eq target-deck :extra) + (< (length (ydk-extra-deck-of *current-deck*)) +extra-deck-card-limit+) + (not (equal frame-type "ritual"))) + ;; Add to extra deck + (ydk-add *current-deck* target-deck passcode)) + ((and (eq target-deck :side) + (< (length (ydk-side-deck-of *current-deck*)) +extra-deck-card-limit+) + (not (equal frame-type "ritual"))) + ;; Add to side deck + (ydk-add *current-deck* target-deck passcode))))) + ;; TODO Where should we validate deck constrains? Here or ADD? + ;; For now we'll do it here. + (if (< (count passcode (ydk-concatenate *current-deck*)) 3) + ;; There *are* fewer than three of this card in the deck... + (try-add frame-type passcode target-deck) + (flash-error (format nil (_ "Deck ~a has too many cards~%") (ydk-name-of *current-deck*)))))) + (setf (getf (response-headers *response*) :HX-Trigger) "deck-list-changed") + (render-current-deck-list)))) + +(defroute ("/builder/remove" :method :POST) (&key _parsed) + "Try to remove PASSCODE from *CURRENT-DECK*. + +TODO Parameters: PASSCODE DECK" + (v:info :builder "POST /builder/remove => ~a" _parsed) + + (let ((index (query-param "index" _parsed)) + (deck (query-param "deck" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer index) + (:string deck)) + (ydk-delete-index *current-deck* (alexandria:make-keyword (string-upcase deck)) index) + (setf (getf (response-headers *response*) :HX-Trigger) "deck-list-changed")) + (ratify:combined-error (e) + (flash-error (princ-to-string e)) + (redirect "/builder" 302)))) + + (render-current-deck-list)) + +(defroute ("/builder/clear" :method :POST) () + "Clear the *CURRENT-DECK* of any cards using YDK-CLEAR." + (ydk-clear *current-deck*) + (render-current-deck-list)) + +(defroute ("/builder/create" :method :POST) (&key _parsed) + "Create a new deck. This will preserve some information from *CURRENT-DECK*, giving it a new name, and immediately synchronising it to the database. You may use the CLEAR route to erase the deck." + (v:info :builder "POST /builder/create => ~a" _parsed) + + (let ((name (query-param "deck-create-name" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:string name)) + (let ((found (ydk-deck-by-name name))) + (if found + ;; The deck already exists + (progn + (flash-message + (format nil (_ "Deck ~a already exists in database; loading...") name)) + (setf *current-deck* (ydk-sync found))) + (progn + (flash-message + (format nil (_ "Creating deck ~a into database.") name)) + (setf *current-deck* + (make-instance 'ydk + :name name + :created-by (user-name) + :main-deck (ydk-main-deck-of *current-deck*) + :extra-deck (ydk-extra-deck-of *current-deck*) + :side-deck (ydk-side-deck-of *current-deck*))) + ;; Sync it to the database to save it to the + ;; names list - doesn't matter if it has no + ;; cards in it. + (let ((new (ydk-sync *current-deck*))) + (when new + (setf (ydk-id-of *current-deck*) + (mito:object-id new)))))))) + (ratify:combined-error (e) + (flash-error e)))) + + (render-saved-deck-list)) + +(defroute ("/builder/delete" :method :DELETE) (&key _parsed) + "Delete the deck specified by DECK-CREATE-NAME in the HTML. + +TODO Parameters: NAME The name of the deck to use YDK-DECK-DELETE-BY-NAME on." + (v:info :builder "DELETE /builder/delete => ~a" _parsed) + + (let ((name (query-param "deck-create-name" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:string name)) + (ydk-deck-delete-by-name name)) + (ratify:combined-error (e) + (flash-error e)))) + + (render-saved-deck-list)) + +(defroute ("/builder/save" :method :POST) () + "Save *CURRENT-DECK* to the database using YDK-SYNC. Return an updated deck listing with RENDER-CURRENT-DECK-LIST." + (v:info :builder "POST /builder/save") + + (v:info :builder "~a" (ydk-sync *current-deck*)) + + (render-current-deck-list)) + +(defroute ("/builder/load" :method :POST) (&key _parsed) + "Load Deck POST Route. Load ID into *CURRENT-DECK*." + (v:info :builder "POST /builder/load => ~a" _parsed) + + (let ((id (query-param "deck-load-id" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (ydk-deck-by-id id))) + (when found + (setf *current-deck* (ydk-sorted (ydk-sync found))) + (flash-message + (format nil (_ "Found entry ~A (~d); loading...~%") + (ydk-name-of found) (mito:object-id found)))))) + (ratify:combined-error (e) + (flash-error e)))) + + (render-current-deck-list)) + +(defroute ("/builder/rename" :method :POST) (&key _parsed) + "Try to rename a deck. Rename Deck ID to DECK-CREATE-NAME. + +TODO There is other code to rename, isn't there? It looks like that code is inteded to be used from the Deck Overview page. This is just in the builder." + (v:info :builder "POST /builder/rename => ~a" _parsed) + + (let ((create-name (query-param "deck-create-name" _parsed)) + (id (query-param "deck-load-id" _parsed))) + ;; We want to change the name of LOAD-NAME to CREATE-NAME. + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (ydk-deck-by-id id))) + (ydk-rename-deck found create-name) + (flash-message + (format nil (_ "Found entry ~A (~d); renaming to ~a.~%") + (ydk-name-of found) id create-name)))) + (ratify:combined-error (e) + (flash-error e)))) + + (render-saved-deck-list)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Since we're doing this in a builder context I thought it would be +;; better to do this here than at /category/saved-category-list as +;; that seemed a bit redundant. Surely there's a better way to +;; organize all this stuff! +(defroute ("/builder/saved-category-list" :method :GET) (&key _parsed) + "Route for rendering the saved category list. + +This is a \"widget\" that we include with Djula, and render separately via HTMX AJAX." + (let ((id (query-param "id" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (find-dao 'category :id id))) + (if found + (render-category found + :tpl #P"builder/saved-category-list.html")))) + (ratify:combined-error (e) + (flash-error e) + (render-category (find-dao 'category :id (max-dao 'category)) + :tpl #P"builder/saved-category-list.html"))))) + +;; TODO This has got to be better! +(defroute ("/builder/decks-by-cat-id" :method :GET) (&key |id|) + "Saved Deck List helper. Display the saved deck list, and if an |ID| is provided, send that as the current ID." + (with-logged-in-user + (render-with-env #P"builder/saved-deck-list.html" + `(:active "/builder" + :current-deck-id ,(ignore-errors + (ydk-id-of *current-deck*)) + :id ,|id| + :saved-deck-list ,(select-ydk-deck + (if |id| + (sxql:where (:= :category-id |id|)))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/builder/move-to-category" :method :POST) (&key _parsed) + "Move *CURRENT-DECK* into CATEGORY ID. TODO Implement this?" + (v:info :builder "GET /builder/move-to-category => ~a" _parsed)) diff --git a/src/web/cards.lisp b/src/web/cards.lisp new file mode 100644 index 0000000..7f80a71 --- /dev/null +++ b/src/web/cards.lisp @@ -0,0 +1,92 @@ +#| + +src/web/cards.lisp + +Card Browser + +TODO Link To Inventory Item +TODO This needs to be rewritten using YGOProDeck 2 still. + +|# + +(in-package #:cl-deck-builder2.web) + +(defroute ("/cards" :method :GET) (&key _parsed) + "Card Browser Main Route. We use RENDER-RESULTS from `/inventory` URL. + +TODO Cards should probably have their own RENDER-RESULTS. RENDER-CARD?" + (v:info :cards "GET /cards => ~a" _parsed) + + (with-logged-in-user + (render-results :active "/cards" + :class 'ygo-info + :params _parsed + :tpl #P"cards/index.html"))) + +(defroute ("/cards/search" :method :POST) (&key _parsed) + "Card Browser Search POST Route. We also user RENDER-RESULTS here." + (v:info :cards "POST /cards/search => ~a" _parsed) + + (with-logged-in-user + (render-results :active "/cards" + :class 'ygo-info + :params _parsed + :tpl #P"cards/search-results.html"))) + +(defroute ("/cards/:id/view" :method :GET) (&key id) + "View Route for Card ID. We use YGO-SELECT-INFO-BY-ID to get the PASSCODE to get the YGO-SET-BY-PASSCODE." + (v:info :cards "GET /cards/~d/view" id) + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (with-connection (db) + (with-transaction + (let* ((variants (select-variant-condition)) + (ygo-info (ygo-select-info-by-id id)) + (passcode (ygo-passcode-of ygo-info)) + (found (ygo-card-sets passcode))) + (if found + (render-with-env #P"cards/view.html" + `(:ygo-card-sets ,found + :variants ,variants))))))) + (ratify:combined-error (e) + (flash-error (format nil "/cards/~d/view.error => ~a~%" id e)))))) + +(defroute ("/cards/by-passcode/:id" :method :GET) (&key id) + "View YGO-INFO by PASSCODE ID." + (alexandria:if-let ((info (find-dao 'ygo-info :passcode id))) + (redirect (format nil "/cards/~d/view" (mito:object-id info))) + (redirect "/cards"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This is bad, but could be better for other things +(defroute ("/cards/:id/image" :method :GET) (&key id) + "Get the card image for card ID. + +TODO Rewrite with templates +TODO This is bad, the way that it's written right now it will take one ID, ideally, this should wrap a list of IDs to save on database bandwidth. Each request triggers a FIND-DAO." + (v:debug :cards "GET /cards/~d/image" id) + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (find-dao 'ygo-info :id id))) + (if found + (format nil + "\"Card" + (ygo-passcode-of found) + (ygo-passcode-of found) + (name-of found)))))))) + +(defroute ("/cards/by-passcode/:id/price" :method :GET) (&key id) + "Get the YGO-PRICE info for this PASSCODE ID. The pricing info comes from YGOProDeck." + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (find-dao 'ygo-price :passcode-id id))) + (render-with-env #P"cards/ygo-price.html" + (list :price found))))))) diff --git a/src/web/category.lisp b/src/web/category.lisp new file mode 100644 index 0000000..583640b --- /dev/null +++ b/src/web/category.lisp @@ -0,0 +1,138 @@ +(in-package #:cl-deck-builder2.web) + +(defparameter *current-category* nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-category (id &key (tpl #P"category/index.html") env) + "Helper function. Use RENDER-WITH-ENV to display template TPL with environment ENV. + +ID is the CATEGORY we wish to render. + +TODO etypecase? defmethod?" + (let ((found (ignore-errors + (find-dao 'category :id id)))) + (render-with-env tpl + (append env + `(:active "/category" + :category ,found + :categories ,(select-category) + :children ,(ignore-errors (category-full-tree :node.name (category-name-of found))) + :id ,(ignore-errors (mito:object-id found))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/category" :method :GET) (&key _parsed) + "Category Viewer Main Route. + +TODO Parameters: ID The desired CATEGORY ID." + (with-logged-in-user + (render-category (query-param "id" _parsed)))) + +(defroute ("/category/search" :method :POST) (&key _parsed) + "POST Category Search Route. + +TODO Implement this?" + (with-logged-in-user + (render-category (query-param "id" _parsed)))) + +(defroute ("/category/:id/view" :method :GET) (&key id) + "Category Viewer Single Route. + +TODO Parameters: ID The desired CATEGORY ID." + (with-logged-in-user + (render-category id))) + +(defroute ("/category/list" :method :GET) (&key _parsed) + "GET route for the list of categories." + (v:info :web "GET /category/list => ~a" _parsed) + + (with-logged-in-user + (render-category (query-param "id" _parsed)))) + +(defroute ("/category/:id/delete" :method :DELETE) (&key id) + "DELETE a CATEGORY. + +TODO Parameters: ID The ID of the CATEGORY to remove. +TODO Also delete associated deck metadata?" + (v:info :web "DELETE /category/:id/delete" id) + + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (find-dao 'category :id id))) + (when found + (category-delete-tree (category-name-of found)) + (v:info :web "DELETE /category/:id/delete OK" id) + (flash-message (_ "Success!"))))) + (ratify:combined-error (e) + (flash-error e)))) + +(defroute ("/category/:id/rename" :method :POST) (&key id _parsed) + "POST route to RENAME a CATEGORY. + +TODO Parameters: NAME The new name of the CATEGORY. ID of the CATEGORY to be RENAMEd." + (v:info :web "POST /category/~d/rename => ~a" id _parsed) + + (let ((name (query-param "name" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer id) + (:string name)) + (if (category-rename (find-dao 'category :id id) name) + (format nil "OK!") + (format nil "Error!"))) + (ratify:combined-error (e) + (flash-error e))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defroute ("/category/fake-data" :method :GET) () + "Route to generate new fake CATEGORY data. + +TODO Parameters: NAME The name of the new child CATEGORY. ID The parent of the new child CATEGORY." + + (v:info :web "GET /category/fake-data") + (category-insert-fake-data) + (v:info :web "GET /category/fake-data OK")) + +(defroute ("/category/new-parent" :method :POST) (&key _parsed) + "Create a new parent by NAME. We use CATEGORY-INSERT-RIGHT-OF." + (v:info :web "POST /category/new-parent => ~a" _parsed) + + (let ((id (or (query-param "id" _parsed) 0)) + (name (query-param "name" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer id) + (:string name)) + (let ((left-of (ignore-errors + (category-name-of (find-dao 'category :id id))))) + (category-insert-right-of name left-of))) + (ratify:combined-error (e) + (flash-error e))))) + +(defroute ("/category/new-child" :method :POST) (&key _parsed) + "POST route for a new child CATEGORY. + +TODO Parameters: NAME The name of the new child CATEGORY. ID The parent of the new child CATEGORY." + (v:info :web "POST /category/new-child => ~a" _parsed) + + (let ((id (query-param "id" _parsed)) + (name (query-param "name" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer id) + (:string name)) + (let ((left-of (ignore-errors + (category-name-of (find-dao 'category :id id))))) + (category-insert-new-child name left-of) + (format nil "OK!"))) + (ratify:combined-error (e) + (flash-error e))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/category/explain" :method :GET) () + "TODO This needs to be incorporated into the documentation." + (with-logged-in-user + (render-with-env #P"category/explain.html" + `(:active "/category" + :categories ,(select-category))))) diff --git a/src/web/constructed-decks.lisp b/src/web/constructed-decks.lisp new file mode 100644 index 0000000..72c0515 --- /dev/null +++ b/src/web/constructed-decks.lisp @@ -0,0 +1,239 @@ +#| + +src/web/construct-decks.lisp + +Web Routes For Construct Decks + +TODO Pick one: /construct/ or /construct/ ? + +Features Requsted: + +- DONE Card Names +- DONE Card Prices - I think we'll need to wire this up to the + YGO-CC-ITEM. That requires rewriting that component to use the new + YGO-SET instead of the CSV. +- DONE Sort by Price - Honestly having trouble sorting this junk lmao! +- Show only YGO-SET-ITEMS with inventory - see constructed-decks.lisp + + +|# + +(in-package #:cl-deck-builder2.web) + +(defparameter *current-constructed-deck* nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-construct-deck (id tpl) + (with-logged-in-user + (let ((deck (first + (select-constructed-decks + (sxql:where (:= :id id)))))) + (when deck + (render-with-env tpl + (list :active "/construct" + :deck deck + :id (mito:object-id deck) + :name (cl-deck-builder2.models.constructed-decks::deck-name-of deck) + :saved-deck-list (select-constructed-decks))))))) + +;; I think this takes a CAR and a CDR pair of YGO-SET ID and VARIANT ID +;; I'm pretty sure it's supposed to be finding YGO-SET-ITEMs though +(defun selected-sets-to-deck-listing (cards) + (let ((lst '())) + (with-connection (db) + (with-transaction + (dolist (card cards (reverse lst)) + (destructuring-bind (set variant) + card + (let* ((set-id (cdr set)) + (variant-id (cdr variant)) + (set-item (first (ygo-select-set-item set-id variant-id))) + (set (first (ygo-set (sxql:where (:= :id set-id))))) + (variant (mito:find-dao 'variant-condition :id variant-id))) + (with-slots ((passcode-id cl-deck-builder2.models.ygoprodeck.classes::passcode-id)) + set + (push (list :deck-set set + :condition variant + :set-item set-item + :card (ygo-card-by-passcode passcode-id)) + lst))))))))) + +;; XXX Where does this go? What does this accomplish? +(defun select-deck-as-plist (id) + (let ((lst '())) + (dolist (itm (retrieve-dao 'deck-item :deck-id id) (reverse lst)) + (push (list :deck-item itm) lst)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun default-constructed-deck-is-valid-to-pull () + (valid-pull-p + (deck-to-pull-set-items-qty-as-alist *current-constructed-deck*) + (deck-to-pull-desired-qty-as-alist *current-constructed-deck*))) + +(defun default-constructed-deck-invalid-qtys () + (find-any-invalid-qtys + (deck-to-pull-set-items-qty-as-alist *current-constructed-deck*) + (deck-to-pull-desired-qty-as-alist *current-constructed-deck*))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun store-combined-deck-as-current (original-deck selected-listing) + (setf *current-constructed-deck* + (loop for original in (reverse original-deck) + for selected in selected-listing + collect (apply #'make-instance 'constructed-deck-intermediate (append original selected))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; We have to actually pull the inventory. +;; This is the old code but it's written for YDK-DECK object so maybe I'll write one for CONSTRUCTED-DECK-INTERMEDIATE. +(defun pull-from-inventory (cdi-items) + "Attempt to find inventory stock for every card in this deck, and if we are able to secure it, construct and insert a new PULLED-DECK into the database." + (let ((counts (deck-to-pull-desired-qty-as-alist cdi-items))) + (dolist (row counts) + (decf (qty-of (car row)) (cdr row)) + ;; Update each one and save - wrapping this in a transaction causes issues with pulling multiple items. + (save-dao (car row))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/construct" :method :GET) () + "Constructed Deck View Main Route. Login Required." + (v:info :construct "GET /construct") + + (with-logged-in-user + (render-with-env #P"construct/index.html" + (list :active "/construct" + :saved-deck-list (select-constructed-decks))))) + +(defroute ("/construct/:id/view" :method :GET) (&key id) + "Constructed Deck View deck by ID Route. Login Required." + (v:info :construct "GET /construct/~d" id) + + (with-logged-in-user + (render-construct-deck id #P"construct/index.html"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/construct/ygo-set-item" :method :GET) (&key _parsed) + "Find the YGO-SET-ITEM associated with the |SET-ID| and |VARIANT-ID| + +TODO This needs a rewrite as it currently actually accepts multiple +CARDs and operates on their component |SET-ID| and |VARIANT-ID| which +is really not optimal. Ideally we should be handling this all in bulk +with better routes." + (v:info :cards "GET /cards/ygo-set-items ~a" _parsed) + + (with-logged-in-user + (alexandria:if-let ((cards (query-param "cards" _parsed))) + (dolist (card cards) + (let ((|set-id| (query-param "set-id" card)) + (|variant-id| (query-param "variant-id" card))) + (handler-case + (ratify:with-parsed-forms + ((:integer |set-id|) + (:integer |variant-id|)) + (return + (render-with-env #P"cards/variant-results.html" + (list :id (gensym) + :item (find |variant-id| + (select-ygo-cc-item-variants |set-id|) + :key (alexandria:compose #'mito:object-id #'variant-of)))))) + (ratify:combined-error (e) + (flash-error (format nil "/cards/ygo-set-items => ~d:~d ~a~%" |set-id| |variant-id| e))))))))) + + +(defroute ("/construct/:id/select-sets" :method :GET) (&key id) + "Select the YDK-SET for the corresponding PASSCODEs. Use Deck ID from the deck builder." + (v:info :construct "GET /construct/~d/select-sets" id) + + (with-logged-in-user + (with-connection (db) + (let* ((cards (mito:retrieve-dao 'deck-item :deck-id id)) + (sets (mapcar (alexandria:compose #'ygo-card-sets #'deck-passcode-of) cards)) + (variants (select-variant-condition))) + (render-with-env #P"construct/select-sets.html" + (list :sets (reverse sets) + :deck-id id + :variants variants)))))) + +(defroute ("/construct/:id/select-sets" :method :POST) (&key id _parsed) + "Constructed Decks Select Sets POST Route - Attempt to SUBTRACT-DESIRED-FROM-SET-ITEM-QTY-AS-ALIST, and if VALID-PULL-P, call PULL-FROM-INVENTORY. Otherwise, FLASH-ERROR to the user about Insufficient Inventory." + (v:info :construct "POST /construct/~d/select-sets ~a" id _parsed) + + (with-logged-in-user + (alexandria:if-let ((cards (query-param "cards" _parsed))) + (let ((original-deck (select-deck-as-plist id)) + (selected-listing (selected-sets-to-deck-listing cards))) + + ;; Store the current constructed deck template + (store-combined-deck-as-current original-deck selected-listing) + + ;; See if there are any issues with the current inventory + (if (default-constructed-deck-is-valid-to-pull) + (progn + ;; Decrement the stock from the YGO-SET-ITEM + (pull-from-inventory *current-constructed-deck*) + ;; Create a new CONSTRUCTED-DECK based on *CURRENT-CONSTRUCTED-DECK* + (cdi-pull-from-inventory *current-constructed-deck*) + (flash-message (_ "Deck constructed!")) + (render-with-env #P"construct/constructed-deck-list.html" + (list :original-deck original-deck + :deck-id id + :total-price nil + :table *current-constructed-deck*))) + (let ((err (default-constructed-deck-invalid-qtys))) + (with-connection (db) + (flash-error (format nil (_ "Insufficient Inventory:
      ~a") + ;; TODO all this nesting again... + (ygo-passcode-of err) + (ygo-passcode-of err)))) + (redirect (format nil "/construct/~d/select-sets" id)))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/construct/:id/deconstruct" :method :POST) (&key id _parsed) + "Deconstruct deck. That is, increment the QTY of the the contents to of the CONSTRUCTED-DECK-AS-CDI-LIST." + (v:info :construct "POST /construct/~d/deconstruct ~a" id _parsed) + + (with-logged-in-user + (return-to-inventory + (constructed-deck-as-cdi-list id))) + + (_ "Ok!")) + +(defroute ("/construct/:id/selected-sets" :method :GET) (&key id _parsed) + "Query CONSTRUCTED-DECK ID for the YGO-SET-ITEMS it contains. We use CONSTRUCTED-DECK-AS-CDI-LIST again." + (v:info :construct "GET /construct/~d/selected-sets ~a" id _parsed) + + (with-logged-in-user + (let ((table (constructed-deck-as-cdi-list id))) + (render-with-env #P"construct/constructed-deck-list.html" + (list :table table))))) + +(defroute ("/construct/:id/update" :method :POST) (&key id |name| |sell-price| |sold| _parsed) + "Update a CONSTRUCTED-DECK by ID" + (v:info :construct "POST /construct/~d/update ~a" id _parsed) + + (with-logged-in-user + (let ((found (find-dao 'constructed-deck :id id))) + (when found + (when |name| (setf (deck-name-of found) |name|)) + (when |sell-price| (setf (deck-sell-price-of found) |sell-price|)) + (when (string= |sold| "on") (setf (deck-sold found) 1)) + (update-dao found) + (flash-message (_ "Deck Updated")) + (redirect (format nil "/construct/~d/view" id)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; This one looks like: Get all the cards in a deck id 13 and query all the sets using YGO-CARD-SETS + +;; (let* ((cards (retrieve-dao 'deck-item :deck-id 13)) +;; (all-sets (mapcar #'deck-passcode-of cards))) +;; (mapcar #'cl-deck-builder2.models.ygoprodeck.methods::ygo-card-sets +;; (reverse all-sets))) + +;; (with-connection (db) +;; (with-transaction +;; (let ((v (mito:find-dao 'cl-deck-builder2.models.ygoprodeck.fields::variant-condition :name "Near Mint"))) +;; (mapcar (lambda (set) +;; (mito:find-dao 'cl-deck-builder2.models.ygoprodeck.classes::ygo-set-item +;; :item set +;; :variant v)) +;; *)))) diff --git a/src/web/contact.lisp b/src/web/contact.lisp new file mode 100644 index 0000000..2b0c940 --- /dev/null +++ b/src/web/contact.lisp @@ -0,0 +1,76 @@ +#| + +# Contact Form + +GET /contact Index +GET /contact/admin Admin Panel +GET /contact/:id View Submission TODO + +DELETE /contact Clear All +DELETE /contact/:id Delete By ID + +POST /contact New Submission + +|# + +(in-package #:cl-deck-builder2.web) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-feedback (&optional (tpl #P"contact/index.html")) + (render-with-env tpl (append + (list :active "/contact") + (auth (:admin) + (list :feedback (select-feedback)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/contact" :method :GET) () + "Contact Form Index Page" + (v:info :contact "GET /contact") + + (with-logged-in-user + (render-feedback))) + +(defroute ("/contact/admin" :method :GET) () + "Contact Form Admin Panel" + (with-logged-in-user + (render-feedback #P"contact/admin.html"))) + +(defroute ("/contact" :method :DELETE) () + "Delete *ALL* FEEDBACK." + (v:info :contact "DELETE /contact") + + (auth (:admin) + (progn + (delete-from 'feedback) + (flash-message (_ "Feedback cleared.")) + (render-feedback #P"contact/list.html")) + (flash-error (_ "Something went wrong. Try again?")))) + +(defroute ("/contact/:id/delete" :method :DELETE) (&key id) + "Delete FEEDBACK with matching ID." + + (auth (:admin) + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (delete-by-values 'feedback :id id)) + (ratify:combined-error (e) + (flash-error e))))) + +(defroute ("/contact" :method :POST) (&key _parsed) + "User Submit Feedback POST Route. Login Required." + (v:info :contact "POST /contact => ~a" _parsed) + + (with-logged-in-user + (let ((user (find-dao 'user :email (user-name))) + (body (query-param "query" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:string body)) + (if (ignore-errors + (create-feedback user body)) + (flash-message (_ "Thanks! We got your message.")) + (flash-error (_ "Something went wrong. Try again?")))) + (ratify:combined-error (e) + (flash-error e)))) + (redirect "/contact"))) diff --git a/src/web/decks.lisp b/src/web/decks.lisp new file mode 100644 index 0000000..4bf9bf6 --- /dev/null +++ b/src/web/decks.lisp @@ -0,0 +1,376 @@ +#| + +src/web/decks.lisp + +Deck Overview Web Interface + +Here we see the cards in the deck, we have the option to generate +deck images, and we have the option to pull the deck from +inventory. + +|# + +(in-package #:cl-deck-builder2.web) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO both YDK-DATA-TO-TEMPLATE and RENDER-YDK desperately need an overhaul. +(defun ydk-to-template-data (ydk) + "Transform information from YDK-QUERY on YDK into: main, side, extra decks, with RLE encodings of all three, plus metadata." + (let ((data (ydk-query ydk))) + (when data + (labels ((filter-kind (lst kind) + (remove-if + (lambda (card) + (if (eq (getf card :kind) kind) + nil + t)) + (reverse lst)))) + (let ((main-deck (filter-kind data 0)) + (extra-deck (filter-kind data 1)) + (side-deck (filter-kind data 2))) + (list + :name (ydk-name-of ydk) + :created-by (ydk-created-by ydk) + + :main-deck main-deck + :extra-deck extra-deck + :side-deck side-deck + + :main-deck-rle (rle-encode-plist main-deck :key (lambda (plist) (getf plist :name)) :test #'string=) + :extra-deck-rle (rle-encode-plist extra-deck :key (lambda (plist) (getf plist :name)) :test #'string=) + :side-deck-rle (rle-encode-plist side-deck :key (lambda (plist) (getf plist :name)) :test #'string=))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO This is ugly still. Fix it. +(defun draw-deck-image-by-id (id) + (let ((found (find-dao 'ydk-deck :id id))) + (when found + (let ((ydk (ydk-sync found))) + (draw-deck-image ydk (ydk-name-of ydk)))))) + +;; TODO this only works with decks from the database as it uses +;; YDK-DECK-INFO-BY-ID, making the name of this function a +;; misnomer. It should be RENDER-YDK-BY-DECK-ID. I'd use YDK-QUERY for +;; now beacuse that nets you INNER-JOIN with fields coming from +;; datafly as an associated list... sloppy work. +(defun render-ydk (id tpl) + "Render a YDK-DECK from the databse with ID using template TPL. We also use YDK-TO-TEMPLATE-DATA to query additional deck information." + (let ((found (ydk-deck-by-id id))) + (if found + (render-with-env + tpl + (append + (list :active "/decks" + :id id + :category (with-connection (db) + (ydk-category-of found)) + :saved-deck-list (select-ydk-deck) + :saved-category-list (select-category) + :files (probe-image-files-list + (ydk-name-of found))) + (ydk-to-template-data found))) + (_ "Nothing to see here...")))) + +(defgeneric ydk-listing-as (id tpl) + (:documentation "Looks like this is a kludge to accept ID as a string or integer. TODO Move to generics.lisp") + (:method ((id string) (tpl pathname)) + (ydk-listing-as (parse-integer id) tpl)) + (:method ((id integer) (tpl pathname)) + (render-ydk id tpl))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/decks" :method :GET) (&key _parsed) + "Deck List Main Route. Login Required." + (with-logged-in-user + (let ((id (query-param "deck-load-id" _parsed))) + (if id + (let ((found (ydk-deck-by-id id))) + (render-with-env #P"decks/index.html" + `(:active "/decks" + :files ,(probe-image-files-list (ydk-name-of found)) + :id ,id + :category ,(ydk-category-of found) + :saved-deck-list ,(select-ydk-deck) + :saved-category-list ,(select-category)))) + (render-with-env #P"decks/index.html" + `(:active "/decks" + :saved-deck-list ,(select-ydk-deck) + :saved-category-list ,(select-category))))))) + +(defroute ("/decks/:id/view" :method :GET) (&key id) + "Deck Viewer Main Route. Login Required. View YDK-DECK with YDK-DECK-BY-ID. Uses RENDER-YDK." + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (ydk-deck-by-id id))) + (if found + (render-ydk id #P"decks/index.html") + (progn + (flash-error (format nil (_ "No Deck ID ~d") id)) + (render-with-env #P"decks/index.html" + `(:active "/decks" + :saved-deck-list ,(select-ydk-deck) + :saved-category-list ,(select-category))))))) + (ratify:combined-error (e) + (flash-error (format nil "~a~%" e)) + (render-with-env #P"decks/index.html" + `(:active "/decks" + :saved-deck-list ,(select-ydk-deck) + :saved-category-list ,(select-category))))))) + +(defroute ("/decks/:id/generate-cover-images" :method :GET) (&key id) + "Deck Viewer - Generate Cover Image. Login Required. Render a static image and display it with DECK-IMAGE-LISTING template." + (v:info :decks "GET /decks/~d/generate-cover-images" id) + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (if (draw-deck-image-by-id id) + (ydk-listing-as id #P"decks/deck-image-listing.html") + (_ "Something went wrong. Try again?"))) + (ratify:combined-error (e) + (flash-error e))))) + +(defroute ("/decks/:id/delete-generated-images" :method :DELETE) (&key id) + "Deck Viewer - Delete Generate Cover Image. Login Required." + (v:info :decks "DELETE /decks/~d/delete-generated-images" id) + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (ydk-deck-by-id id))) + (when found + (if (notany #'null + (mapcar #'uiop:delete-file-if-exists + (probe-image-files-list (ydk-name-of found) nil))) + (flash-message (_ "Success!")) + (flash-error (_ "Something went wrong. Try again?")))))) + (ratify:combined-error (e) + (flash-error e))))) + +(defroute ("/decks/:id/to-category" :method :POST) (&key id _parsed) + "POST route to send deck ID to category ID." + (v:info :decks "POST /decks/~d/to-category._parsed = ~a~%" id _parsed) + + (with-logged-in-user + (let ((cat-id (query-param "category-id" _parsed))) + (handler-case + (ratify:with-parsed-forms + ((:integer id) + (:integer cat-id)) + (let ((found (ydk-deck-by-id id))) + (when found + (setf (ydk-category-of found) + (find-dao 'category :id cat-id)) + (with-connection (db) + (mito:save-dao found)) + (render-with-env #P"decks/category-select.html" + `(:category-id ,(mito:object-id + (ydk-category-of found)) + :deck-id ,(mito:object-id found) + :categories ,(select-category)))))) + (ratify:combined-error (e) + (flash-error e)))))) + +(defroute ("/decks/:id/rename" :method :POST) (&key id _parsed) + "Deck Viewer - Rename deck ID with new name. Will also rename the generated image files, if they exist." + (v:info :decks "GET /decks/~d/rename._parsed = ~a~%" id _parsed) + + (with-logged-in-user + (let ((name (query-param "name" _parsed))) + ;; We want to change the name of LOAD-NAME to CREATE-NAME. + (handler-case + (ratify:with-parsed-forms + ((:integer id) + (:string name)) + (let ((found (ydk-deck-by-id id))) + ;; Ahaha that was a fun hunt. Using YDK-DECK-RENAME + ;; before RENAME-FILES caused the OLD-NAME in + ;; RENAME-FILES to be NEW-NAME... Rename the files + ;; before renaming the database entry. + (v:info :decks "RENAME ~a => ~a" (ydk-name-of found) name) + (ydk-rename-files (ydk-name-of found) name) + (ydk-rename-deck found name) + (flash-message + (format nil "Found entry ~A (~d); renaming to ~a.~%" + (ydk-name-of found) id name)) + (redirect (format nil "/decks/~d/view" id) 302))) + (ratify:combined-error (e) + (flash-error e)))))) + +(defroute ("/decks/:id/pull" :method :GET) (&key id) + "Pull deck ID using YDK-DECK-PULL-FROM-INVENTORY. + +TODO I think This is outdated." + (v:info :decks "GET /decks/~d/pull" id) + + ;; We want to "pull" the deck from inventory. That is, take the list + ;; of cards in the deck and remove them from the inventory count. + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (ydk-deck-by-id id))) + ;; The deck exists by ID + (when found + (let ((pulled (ydk-deck-pull-from-inventory found))) + (if pulled + (progn + (flash-message + (format nil "Found entry ~A (~d); pulling cards.~%" + (ydk-name-of found) id)) + (redirect (format nil "/construct/~d/view" + (mito:object-id pulled)) + 302)) + (flash-message + (format nil "Unable to pull deck ~d?~%" id))))))) + (ratify:combined-error (e) + (flash-error e))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/decks/cards-in-decks" :method :GET) (&key _parsed) + "Route to display all the unique cards in all decks." + (v:info :decks "GET /decks/cards-in-decks => ~a" _parsed) + + (render #P"decks/cards-in-decks.html" + (list :active "/decks" + :table (cards-in-all-decks)))) + +(defroute ("/decks/:id/name" :method :GET) (&key id) + "Route to display the name of the YDK-DECK by this ID. + +TODO This should be a batch request." + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (ydk-deck-by-id id))) + (if found + ;; Deck names like "29036" are coming back as INTEGER + ;; which is causing issues with Caveman2. It seems to + ;; use REDUCE #'LENGTH to measure the content length. + ;; It didn't like: (LENGTH 29036) + (princ-to-string (ydk-name-of found)) + (_ "No Name"))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/decks/by-category/:id" :method :GET) (&key id _parsed) + "Query a list of decks by category." + (v:info :decks "GET /decks/by-category/~d => ~a" id _parsed) + + (with-logged-in-user + (render #P"decks/decks-by-category.html" + (list :active "/decks" + :table (by-category 'ydk-deck id))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; All the various kinds of deck representations: HTML, HTML as Text, +;; Text as Text Area, Resulting Deck File Images +(defroute ("/decks/:id/html-listing" :method :GET) (&key id _parsed) + "Helper route. Get the builder HTML listing using YDK-LISTING-AS and DECK-HTML-RESULTS template." + (v:info :decks "GET /decks/~d/html-listing => ~a" id _parsed) + + (with-logged-in-user + (ydk-listing-as id #P"decks/deck-html-results.html"))) + +(defroute ("/decks/:id/html-text-listing" :method :GET) (&key id _parsed) + "Helper route. Get the HTML listing using YDK-LISTING-AS and DECK-TEXT-THREE-COLUMN template." + (v:info :decks "GET /decks/~d/html-text-listing => ~a" id _parsed) + + (with-logged-in-user + (ydk-listing-as id #P"decks/deck-text-three-column.html"))) + +(defroute ("/decks/:id/text-listing" :method :GET) (&key id _parsed) + "Helper route. Get the text listing using YDK-LISTING-AS and DECK-TEXT-TEXTAREA template." + (v:info :decks "GET /decks/~d/text-listing => ~a" id _parsed) + + (with-logged-in-user + (ydk-listing-as id #P"decks/deck-text-textarea.html"))) + +(defroute ("/decks/:id/text-listing-with-brs" :method :GET) (&key id _parsed) + "Helper route. Get the text listing using YDK-LISTING-AS and DECK-TEXT-TEXTAREA template with ." + (v:info :decks "GET /decks/~d/text-listing-with-brs => ~a" id _parsed) + + (with-logged-in-user + (ydk-listing-as id #P"decks/deck-text-textarea-with-brs.html"))) + +(defroute ("/decks/:id/image-listing" :method :GET) (&key id _parsed) + "Helper route. Get the image listing using YDK-LISTING-AS and DECK-IMAGE-LISTING template." + (v:info :decks "GET /decks/~d/text-listing => ~a" id _parsed) + + (with-logged-in-user + (ydk-listing-as id #P"decks/deck-image-listing.html"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/decks/:id/delete" :method :DELETE) (&key id) + "Delete Deck by ID." + (v:info :builder "DELETE /decks/:id/delete => ~a" id) + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (ydk-deck-delete-by-id id) + (flash-message (format nil (_ "Deck ~a deleted success!" id)))) + (ratify:combined-error (e) + (flash-error e))) + + (redirect "/decks"))) + +(defroute ("/decks/search" :method :GET) (&key _parsed) + "Deck Viewer Search Main Route." + (v:info :builder "GET /decks/search => ~a" _parsed) + + (with-logged-in-user + (render-with-env #P"decks/search.html" + (list :active "/decks")))) + +(defroute ("/decks/search" :method :POST) (&key _parsed) + "Deck Viewer Search POST route. + +TODO Ensure this is functional." + (v:info :builder "GET /decks/search => ~a" _parsed) + + (with-logged-in-user + (let* ((name (query-param "name" _parsed)) + (decks (select-ydk-deck + (sxql:where (%sxql-like :name name))))) + + (render-with-env #P"decks/search.html" + (list :active "/decks" + :decks decks))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; XXX Duplicate code from /builder/saved-deck-list +(defroute ("/decks/deck-select" :method :GET) (&key |id|) + "Deck Select Route Helper." + (with-logged-in-user + (render-with-env #P"decks/deck-select.html" + (list :active "/decks" + :id (ignore-errors + (parse-integer |id|)) + :decks (select-ydk-deck))))) + +(defroute ("/decks/category-select" :method :GET) (&key |deck-id| |category-id|) + "Category Select Route Helper. +TODO Return Sub-categories." + (with-logged-in-user + (render-with-env #P"decks/category-select.html" + (list :active "/decks" + :category-id (ignore-errors + (parse-integer |category-id|)) + :deck-id (ignore-errors + (parse-integer |deck-id|)) + :categories (select-category))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/decks/kde/:id" :method :GET) (&key id) + "KDE Team Deck List Viewer. View Deck by ID" + (render-with-env #P"kde-team.html" + (ydk-to-kde + (ydk-sync + (ydk-deck-by-id id))))) diff --git a/src/web/exception.lisp b/src/web/exception.lisp new file mode 100644 index 0000000..4b33632 --- /dev/null +++ b/src/web/exception.lisp @@ -0,0 +1,15 @@ +#| + +src/web/exception.lisp + +Error Pages + +|# + +(in-package #:cl-deck-builder2.web) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod on-exception ((app ) (code (eql 404))) + (declare (ignore app)) + (merge-pathnames #P"_errors/404.html" + *template-directory*)) diff --git a/src/web/flash-message.lisp b/src/web/flash-message.lisp new file mode 100644 index 0000000..f22b8bd --- /dev/null +++ b/src/web/flash-message.lisp @@ -0,0 +1,49 @@ +(in-package #:cl-deck-builder2.web.flash-message) + +(defvar *flash-messages* '()) + +(defclass flash-message () + ((body :accessor flash-body-of + :initarg :body) + (class :accessor flash-class-of + :initarg :class) + (title :accessor flash-title-of + :initarg :title))) + +(defun flash (class title body) + "Essentially HASH-TABLE-PUSH: helper function to PUSHNEW a VALUE associated with KEY to *SESSION* HASH-TABLE." + (let ((obj (make-instance 'flash-message + :class class + :title title + :body body))) + (push obj *flash-messages*) + (flash-body-of obj))) + +(defun flash-gethash () + (let ((messages *flash-messages*)) + (setf *flash-messages* '()) + (reverse messages))) + +;; error -> is-danger +(defun flash-error (msg) + (flash "is-danger" "Error" (princ-to-string msg))) + +;; message -> is-success +(defun flash-message (msg) + (flash "is-success" "Success" (princ-to-string msg))) + +;; info -> is-info +(defun flash-info (title msg) + (flash "is-info" title (princ-to-string msg))) + +;; link -> is-link +(defun flash-link (title msg) + (flash "is-link" title (princ-to-string msg))) + +;; primary -> is-primary +(defun flash-primary (title msg) + (flash "is-primary" title (princ-to-string msg))) + +;; warning -> is-warning +(defun flash-warning (msg) + (flash "is-warning" "warning" (princ-to-string msg))) diff --git a/src/web/index.lisp b/src/web/index.lisp new file mode 100644 index 0000000..9072c9c --- /dev/null +++ b/src/web/index.lisp @@ -0,0 +1,81 @@ +#| + +src/web/index.lisp + +Some static top-level routes. + +If there isn't much to the page it probably appears here e.g. / /about /news + +|# + +(in-package :cl-deck-builder2.web) + +(defroute index "/" () + "Main HTML index." + (with-logged-in-user + (render-with-env #P"index.html" `(:active "/")))) + +;; (render-markdown +;; (relative-pathname "doc/index.md") +;; :title "Index" :env `(:active "/index")) + +(defroute "/about" () + "About page." + (with-logged-in-user + (render-markdown + (relative-pathname "doc/about.md") + :title "About" :env `(:active "/about")))) + +(defroute "/news" () + "News page. We use parse git log in toolkit/git.lisp." + (with-logged-in-user + (render-with-env #P"news.html" + `(:active "/news" + :git-log ,(mapcar #'parse-git-log (git-log)))))) + +(defroute ("/todo" :method :GET) () + "To-Do list page. This is my personal professional todo list!" + (with-logged-in-user + (render-markdown + (asdf:system-relative-pathname :cl-deck-builder2 + "doc/todo.md") + :title "To-Do List" + :env `(:active "/todo")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/markdown" :method :GET) (&key _parsed) + "A utility URL to parse markdown files. + +In a production environment you would probably want to disable this URL, as it could potentially allow an attacker to read arbitrary source files. That isn't a concern since we're running in a Docker container." + (with-logged-in-user + (let* ((path (query-param "path" _parsed)) + (pathspec (asdf:system-relative-pathname :cl-deck-builder2 path))) + (alexandria:if-let ((extracted-title (ignore-errors + (car (uiop:read-file-lines pathspec))))) + (render-markdown pathspec :title extracted-title) + (render-with-env #P"markdown.html" (list :title "Oops!" :html "Nothing to see here...")))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun extract-router-docs (app) + "Courtesy of knobo: https://github.com/fukamachi/caveman/pull/108" + (sort + (mapcar (lambda (route) + (list + (myway.rule::rule-url (myway.route:route-rule route)) + (documentation (ningle.route:route-controller route) 'function) + (car (map-set:ms-map 'list 'identity (myway.rule::rule-methods (myway.route:route-rule route)))) + (myway.rule::rule-param-keys (myway.route:route-rule route)))) + (myway:mapper-routes (ningle.app::mapper app))) + #'string< + :key #'car)) + +(defroute doc "/doc" (&key |org|) + "This url shows this the documentation." + (with-logged-in-user + (let ((docs (extract-router-docs *web*)) + (tpl (if |org| + #P"docs.md" + #P"docs.html"))) + (render-with-env tpl + `(:active "/doc" + :docs ,docs))))) diff --git a/src/web/inventory.lisp b/src/web/inventory.lisp new file mode 100644 index 0000000..abb6fd9 --- /dev/null +++ b/src/web/inventory.lisp @@ -0,0 +1,244 @@ +#| + +src/web/inventory.lisp + +Crystal Commerce-style Inventory Management User Interface and helpers. + +TODO pass over the results of (make-search-query) such that +:total-qty is the aggregate of: + +(select 'ygo-cc-item (where (:= :item_id xxx))) + +(reduce #'+ + (mapcar (alexandria:compose #'read-from-string #'cc-sell-price-of) + (retrieve-dao 'ygo-cc-item :item-id 48883))) + +(reduce #'+ (mapcar #'cc-qty-of (retrieve-dao 'ygo-cc-item :item-id 48883))) + + +|# + +(in-package #:cl-deck-builder2.web) + +(defun render-results (&key (active "/inventory") (class 'cc-item) (params nil) (tpl #P"inventory/search-results.html")) + "Mega helper function. Render function for anything that currently queries the databse. We search both PRODUCT-NAME and NAME, as well as querying a list of VARIANTs, with a LIMIT on the number of results and an OFFSET into those search results. You may also specify a DIRECTION, and SORT-BY options. + +TODO Major rewrite candidate right here. +TODO Integrate new SEARCH-SESSION object after that's done." + (let ((direction (or (query-param "direction" params) "desc")) + (variant (or (query-param "variant" params) + (find-dao 'variant-condition :name "Near Mint"))) + (variants (select-variant-condition)) + (limit (or (query-param "limit" params) "10")) + (name (or + (query-param "name" params) + (query-param "product-name" params))) + (offset (or (query-param "offset" params) "0")) + (sort-by (or (query-param "sort-by" params) "id"))) + (handler-case + (ratify:with-parsed-forms + ((:integer limit) + (:integer offset) + (:string direction) + (:string sort-by)) + ;; NAME can be blank, so... + (let* ((filtered-cards (make-search-query class params)) + (length (make-count-query class params)) + (pages (generate-pages length offset limit))) + (render-with-env tpl + `(:active ,active + :cards ,filtered-cards + :direction ,direction + :last-page ,(car (last pages)) + :length ,length + :limit ,limit + :name ,name + :offset ,offset + :opposite-direction ,(get-opposite-direction direction) + :pages ,pages + :search-params ,+search-params+ + :sort-by ,sort-by + :total ,(count-dao class) + :variant ,variant + :variants ,variants)))) + (ratify:combined-error (e) + (flash-error (format nil "~a/search.error => ~a~%" active e)))))) + +;; USE REINITIALIZE-INSTANCE to update the existing YGO-SET-ITEM from _PARSED +(defun patch-card (card _parsed) + (let ((set-id (query-param "set-id" card)) + (variant-id (query-param "variant-id" card))) + (handler-case + (ratify-parsing:with-parsed-forms + ((:integer set-id) + (:integer variant-id)) + (let ((found (find-or-create-instance 'ygo-set-item :item-id set-id :variant-id variant-id)) + (clean (filter-alist _parsed))) + (v:info :inventory "PATCH-CARD: ~a" found) + (if found + ;; TODO redraw item based on context? Like patching from the inventory list vs patching from the editor? + ;; (redirect (format nil "/inventory/~d/edit" (mito:object-id found)) 303) + (progn + (apply #'reinitialize-instance found (assoc-utils:alist-plist clean)) + (update-dao found) + (_ "Save")) + (progn + (flash-error (format nil "Error udpating id ~d:~d" set-id variant-id)))))) + (ratify:combined-error (e) + (flash-error e))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Inventory Viewer +(defroute ("/inventory" :method :GET) (&key _parsed) + "Inventory Viewer GET route. Will display index with search results." + (v:info :inventory "GET /inventory ~a" _parsed) + + ;; (with-logged-in-user + ;; (render-results :params _parsed :tpl #P"inventory/index.html")) + (redirect "/cards")) + +#| +(defroute ("/inventory" :method :POST) (&key _parsed) + "Inventory Viewer search POST route. Will display index with search results." + (v:info :inventory "POST /inventory ~a" _parsed) + + (with-logged-in-user + (render-results :params _parsed :tpl #P"inventory/index.html"))) + +(defroute ("/inventory/search" :method :POST) (&key _parsed) + "Inventory Viewer search POST route. Will display index with search results." + (v:info :inventory "POST /inventory/search ~a" _parsed) + + (with-logged-in-user + (render-results :params _parsed))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Inventory Item Editor +;; +;; TODO PUT?? POST?? GET?? + +;; New Item +(defroute ("/inventory/new" :method :GET) () + "Display the page for creation of a new inventory item" + (v:info :inventory "GET /inventory/new") + + (with-logged-in-user + (render-with-env #P"inventory/new.html" + `(:active "/inventory" + :card ,(make-instance 'cc-item))))) + +(defroute ("/inventory/new" :method :POST) (&key _parsed) + "POST method for the processing the information from page for creation of a new inventory item." + (v:info :inventory "POST /inventory/new => ~a" _parsed) + + (with-logged-in-user + (let ((clean (filter-alist _parsed))) + (let ((new (apply #'cc-create (assoc-utils:alist-plist clean)))) + (when (mito:object-id new) + (redirect (format nil "/inventory/~d/edit" (mito:object-id new)))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Inventory Item Importer +(defroute ("/inventory/import" :method :GET) () + "Inventory Item Importer. TODO" + (v:info :inventory "GET /inventory/import") + + (with-logged-in-user + (render-with-env #P"inventory/import.html" + `(:active "/inventory")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Seems like this one needs to be last. It has something to do with +;; the wildcard :ID parameter. Maybe we ought to add the /edit back? + +(defroute ("/inventory/:id/edit" :method :GET) (&key id) + "Edit Route for Inventory Item. + +ARGUMENTS +ID The ID of the CC-ITEM inventory item you wish to edit." + (v:info :inventory "GET /inventory/~d/edit" id) + + (with-logged-in-user + (handler-case + (ratify-parsing:with-parsed-forms + ((:integer id)) + (render-with-env #P"inventory/edit.html" + `(:active "/inventory" + :errors ,(flash-gethash :errors) + :messages ,(flash-gethash :messages) + :card ,(cc-select-by-id id)))) + (ratify:combined-error (e) + (flash-error e))))) + +(defroute ("/inventory/:id/edit" :method :POST) (&key id _parsed) + "CC-ITEM Update POST route. If the item exists, update it using REINITIALIZE-INSTANCE, after filtering _PARSED through FILTER-ALIST. + +TODO Differentiate between CC-ITEM and YGO-CC-ITEM?" + (v:info :inventory "POST /inventory/~d/edit => ~a" id _parsed) + + (with-logged-in-user + (handler-case + (ratify-parsing:with-parsed-forms + ((:integer id)) + (with-connection (db) + (let ((found (mito:find-dao 'cc-item :id id)) + (clean (filter-alist _parsed))) + (when found + (apply #'reinitialize-instance found (assoc-utils:alist-plist clean)) + (mito:save-dao found) + (redirect (format nil "/inventory/~d/edit" (mito:object-id found)) 303))))) + (ratify:combined-error (e) +(flash-error e))))) + +(defroute ("/inventory/:id/delete" :method :DELETE) (&key id) + "DELETE an Inventory Item specified by ID." + (v:info :inventory "DELETE /inventory/~d/delete" id) + + (with-logged-in-user + (handler-case + (ratify-parsing:with-parsed-forms + ((:integer id)) + (cc-delete-by-id id)) + (ratify:combined-error (e) + (flash-error e))))) +|# + +(defroute ("/inventory/patch" :method :PATCH) (&key _parsed) + "YGO-CC-ITEM PATCH route. This appears to be functionally identical to EDIT route for ID, but this is using YGO-CC-ITEM as class. The POST route appears to be used for CC-ITEM object." + (v:info :inventory "PATCH /inventory/patch => ~a" _parsed) + + (with-logged-in-user + (alexandria:if-let ((cards (query-param "cards" _parsed))) + (dolist (card cards) + (patch-card card _parsed)) + (patch-card _parsed _parsed)))) + +;; Huh? It works?? +(defroute ("/inventory/variants/:id" :method :GET) (&key id) + "Display the VARIANTs for this inventory item using SELECT-YGO-CC-ITEM-VARIANTS." + (v:info :inventory "GET /inventory/variants/~d" id) + + (with-logged-in-user + (handler-case + (ratify-parsing:with-parsed-forms + ((:integer id)) + (render-with-env #P"inventory/variant-results.html" + (list :active "/inventory" + :id id + :cards (select-ygo-cc-item-variants id)))) + ;; :cards ,filtered-cards + ;; :variant ,variant + ;; :variants ,variants + ;; :direction ,direction + ;; :length ,length + ;; :total ,(count-dao class) + ;; :limit ,limit + ;; :name ,name + ;; :offset ,offset + ;; :opposite-direction ,(get-opposite-direction direction) + ;; :pages ,(generate-pages length offset limit) + ;; :search-params ,+search-params+ + ;; :sort-by ,sort-by) + + (ratify:combined-error (e) + (flash-error e))))) diff --git a/src/web/label-maker.lisp b/src/web/label-maker.lisp new file mode 100644 index 0000000..7e0323b --- /dev/null +++ b/src/web/label-maker.lisp @@ -0,0 +1,171 @@ +#| + +src/web/label-maker.lisp + +Label Maker Front-End + +https://www.uline.com/product/detail/s-20247/store-fixtures/price-tag-insert-cards-1-1-4-x-2 + +Supports arbitrary number of labels. +Currently tested with LABELS-LENGTH set to 32 (ULINE S-20247) or 30 (Avery 5160). + +We generate a LaTeX file then use PDFLaTeX to produce a pdf in +GENERATE-LABELS-FOR-ATTACHMENT-ID. + +The barcodes are generated by ZXingWriter. + +TODO A lot of functionality that resides in the web interface needs to be moved to the model interface: +- public-relative-pathname +- generate-labels +- generate-labels-for-attachment +- generate-labels-for-attachment-id +- *current-label-class* + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.web.label-maker + (:use #:cl + #:cl-deck-builder2.models.label-maker) + (:import-from #:caveman2 + #: + #:defroute + #:redirect + #:clear-routing-rules) + (:import-from #:cl-deck-builder2.i18n + #:_) + (:import-from #:cl-deck-builder2.toolkit + #:query-param + #:random-pathname-name) + (:import-from #:cl-deck-builder2.web + #:flash-error + #:flash-message + #:render-with-env + #:render-markdown-file) + (:import-from #:cl-deck-builder2.web.query + #:select-attachment-by-id) + (:import-from #:cl-deck-builder2.models.attachment + #:attachment-valid-p + #:create-attachment) + (:import-from #:cl-deck-builder2.config + #:*public-directory* + #:*static-directory* + #:*template-directory*) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:*web*)) + +(in-package #:cl-deck-builder2.web.label-maker) + +(defclass () ()) +(defvar *web* (make-instance ')) +(clear-routing-rules *web*) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *base-pathspec* #P"/tmp/labels/") + +(defparameter *current-label-class* 'latex-label-uline-s-20247 + "The most recently used class for making labels. + +The idea was you could select the types of labels from a select box on the Label Maker page.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun public-relative-pathname (pathspec) + "PROBE-FILE on PATHSPEC in *PUBLIC-DIRECTORY*. + +TODO I think this is duplicate code from TOOLKIT.PATHS." + (probe-file + (merge-pathnames + pathspec + cl-deck-builder2.config:*public-directory*))) + +;; TODO All of this needs to be rewritten in some kind of pipeline form. +(defun generate-labels (output &key (class 'latex-label-uline-s-20247) csv pdf tex) + "Create output with RENDER-PAGE." + (ensure-directories-exist *base-pathspec*) + (let ((page (make-instance class :csv csv))) + (with-open-file (s tex + :if-exists :supersede + :direction :output) + (format s "~a" (render-page page)))) + + (uiop:delete-file-if-exists (probe-file #P"~/public/labels.pdf")) + (if (inferior-shell:run + `(and + (cd "/tmp/labels/") + (pdflatex ,tex) + (mv ,pdf ,output))) + output)) + +;; TODO ENOUGH-PATHNAME +;; TODO rewrite this using defclass like we did for CARDINFO +(defun generate-labels-for-attachment (path file-key) + (generate-labels (make-pathname :name file-key + :type "pdf" + :directory (namestring *public-directory*)) + :class *current-label-class* + :csv path + :tex #P"/tmp/labels/labels.tex" + :pdf #P"/tmp/labels/labels.pdf")) + +(defun generate-labels-for-attachment-id (id) + (let ((found (select-attachment-by-id id))) + (when found + (generate-labels-for-attachment + (public-relative-pathname + (;; Trim leading "/" + subseq + (namestring (mito-attachment:file-url found)) 1)) + (pathname-name + (mito-attachment:file-key found)))))) + +(defun label-maker-process-files (files) + "Process FILES with ATTACHMENT-VALID-P, CREATE-ATTACHMENT, then GENERATE-LABELS-FOR-ATTACHMENT-ID." + (dolist (file files) + (if (attachment-valid-p file) + (destructuring-bind (content filename content-type) + file + + (setf filename (random-pathname-name :type (pathname-type filename))) + + (let ((success (create-attachment content filename content-type))) + (if success + (progn + ;; Create ATTACHMENT was successful + (generate-labels-for-attachment-id (mito:object-id success)) + (flash-message (format nil (_ "Success! Download link: labels.pdf") + (pathname-name + (mito-attachment:file-key success))))) + (flash-error (_ "Something went wrong. Try again?"))))) + (flash-error (_ "Invalid upload."))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/" :method :GET) () + "Label Maker Main Route." + (render-with-env #P"label-maker.html" + `(:active "/label-maker"))) + +(defroute ("/instructions" :method :GET) () + "Display Label Maker Instructions using RENDER-MARKDOWN-FILE." + (render-markdown-file + (asdf:system-relative-pathname :cl-deck-builder2 "doc/label-maker.md"))) + +(defroute ("/" :method :POST) (&key _parsed) + "Label Maker POST route. + +TODO Parameters: FILES A list of CSV files to process. CLASS the class of label you wish to generate. May be one of LATEX-LABEL-ULINE-S-20247 or LATEX-LABEL-AVERY-5160." + (v:info :label-maker "POST /label-maker => ~a" _parsed) + + (let ((files (query-param "files" _parsed)) + (class (query-param "class" _parsed))) + + ;; Check the CLASS parameter + (setf *current-label-class* + (find-class + (cond ((string= class "latex-label-uline-s-20247") 'latex-label-uline-s-20247) + (t 'latex-label-avery-5160)))) + + (if files + (label-maker-process-files files) + (flash-error (_ "No files."))) + (redirect "/label-maker"))) diff --git a/src/web/packages.lisp b/src/web/packages.lisp new file mode 100644 index 0000000..e5b7270 --- /dev/null +++ b/src/web/packages.lisp @@ -0,0 +1,64 @@ +;;;; src/web/package.lisp + +(in-package #:cl-user) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.web.flash-message + (:use #:cl) + (:export #:flash + #:flash-gethash + #:flash-error + #:flash-message)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.web.query + (:use #:cl + #:cl-deck-builder2.db + #:cl-deck-builder2.models) + (:export #:%sxql-like + + #:select-attachment + #:select-category + #:select-constructed-deck-items + #:select-constructed-decks + #:select-deck-item + #:select-feedback + #:select-ydk-deck + + #:cards-in-all-decks + #:select-attachment-by-id + #:select-variant-condition + #:select-ygo-cc-item-variants + #:ygo-select-info-by-id + #:ygo-select-info-by-passcode + #:ygo-set-item-by-id + #:ygo-set-item-by-item-id)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defpackage #:cl-deck-builder2.web + (:use #:cl + #:caveman2 + #:cl-deck-builder2.db + #:cl-deck-builder2.models + #:cl-deck-builder2.i18n + #:cl-deck-builder2.toolkit + #:cl-deck-builder2.web.query + #:cl-deck-builder2.web.flash-message) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:import-from #:cl-deck-builder2.config + #:*public-directory* + #:*static-directory* + #:*template-directory*) + (:import-from #:cl-deck-builder2.draw + #:draw-deck-image) + (:import-from #:cl-deck-builder2.view #:render) + (:import-from #:hermetic + #:setup + #:login + #:logged-in-p + #:user-name + #:logout + #:auth) + (:export #:*web* + #:make-chat-client + #:make-chat-server)) diff --git a/src/web/params.lisp b/src/web/params.lisp new file mode 100644 index 0000000..ddc13c9 --- /dev/null +++ b/src/web/params.lisp @@ -0,0 +1,146 @@ +#| + +Various constants or parameters used around the web interface + +|# + +(in-package #:cl-deck-builder2.web) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; builder.lisp + +(defparameter +main-deck-card-limit+ 60) + +(defparameter +extra-deck-card-limit+ 15) + +(defparameter +search-params+ + '(:ATTRIBUTE ("DARK" "DIVINE" "EARTH" "FIRE" "LIGHT" "WATER" "WIND") + :TYPE + ("Effect Monster" "Flip Effect Monster" "Fusion Monster" + "Gemini Monster" "Link Monster" "Normal Monster" + "Normal Tuner Monster" "Pendulum Effect Fusion Monster" + "Pendulum Effect Monster" "Pendulum Effect Ritual Monster" + "Pendulum Flip Effect Monster" "Pendulum Normal Monster" + "Pendulum Tuner Effect Monster" "Ritual Effect Monster" + "Ritual Monster" "Skill Card" "Spell Card" "Spirit Monster" + "Synchro Monster" "Synchro Pendulum Effect Monster" + "Synchro Tuner Monster" "Token" "Toon Monster" "Trap Card" + "Tuner Monster" "Union Effect Monster" "XYZ Monster" + "XYZ Pendulum Effect Monster") + :RACE + ("Abidos the Third" "Adrian Gecko" "Alexis Rhodes" "Amnael" "Andrew" + "Aqua" "Arkana" "Aster Phoenix" "Axel Brodie" "Bastion Misaw" "Beast" + "Beast-Warrior" "Bonz" "Camula" "Chazz Princet" "Christine" "Chumley Huffi" + "Continuous" "Counter" "Creator-God" "Cyberse" "David" + "Dinosaur" "Divine-Beast" "Don Zaloog" "Dr. Vellian C" "Dragon" "Emma" + "Equip" "Espa Roba" "Fairy" "Field" "Fiend" "Fish" "Illusionist" + "Insect" "Ishizu" "Ishizu Ishtar" "Jaden Yuki" "Jesse Anderso" "Joey" + "Joey Wheeler" "Kagemaru" "Kaiba" "Keith" "Lumis and Umb" "Lumis Umbra" + "Machine" "Mai" "Mai Valentine" "Mako" "Nightshroud" "Normal" + "Odion" "Paradox Broth" "Pegasus" "Plant" "Psychic" "Pyro" + "Quick-Play" "Reptile" "Rex" "Ritual" "Rock" "Sea Serpent" "Seto Kaiba" + "Spellcaster" "Syrus Truesda" "Tania" "Tea Gardner" "The Supreme K" + "Thelonious Vi" "Thunder" "Titan" "Tyranno Hassl" "Warrior" + "Weevil" "Winged Beast" "Wyrm" "Yami Bakura" "Yami Marik" "Yami Yugi" + "Yubel" "Yugi" "Zane Truesdal" "Zombie") + :FRAME-TYPE + ("effect" "effect_pendulum" "fusion" "fusion_pendulum" "link" + "normal" "normal_pendulum" "ritual" "ritual_pendulum" "skill" "spell" + "synchro" "synchro_pendulum" "token" "trap" "xyz" "xyz_pendulum") + :ARCHETYPE + ("\"C\"" "-Eyes Dragon" "@Ignister" "A.I." "ABC" "Abyss Actor" "Abyss Script" + "Adamancipator" "Advanced Crystal Beast" "Adventurer Token" "Aesir" "Aether" + "Albaz Dragon" "Alien" "Alligator" "Allure Queen" "Ally of Justice" + "Altergeist" "Amazement" "Amazoness" "Amorphage" "Ancient Gear" + "Ancient Treasure" "Ancient Warriors" "Anti" "Apoqliphort" "Appliancer" + "Aquaactress" "Aquamirror" "Arcana Force" "Archfiend" "Armed Dragon" "Aroma" + "Artifact" "Assault Mode" "Atlantean" "Attraction" "Attribute Summoner" + "B.E.S." "Bamboo Sword" "Barbaros" "Barian's" "Batteryman" "Battleguard" + "Battlewasp" "Battlin' Boxer" "Battlin' Boxing" "Beetrooper" + "Black Luster Soldier" "Blackwing" "Blaze Accelerator" "Blue-Eyes" "Bonding" + "Book of" "Boot-Up" "Borrel" "Bounzer" "Branded" "Bridge" "Bugroth" "Bujin" + "Burning Abyss" "Butterfly" "Butterspy" "Bystial" "Cataclysmic" + "Celtic Guard" "Chaos" "Chaos Phantom" "Charmer" "Chemicritter" "Chronomaly" + "Chrysalis" "Cipher" "Clear Wing" "Cloudian" "Code Talker" "Codebreaker" + "Constellar" "Contact" "Cosmic Synchro Monster" "Counter Fairy" "Crusadia" + "Crystal" "Crystal Beast" "Crystron" "Cubic" "Cupid" "CXyz" "Cyber" + "Cyber Angel" "Cyber Dragon" "Cyberdark" "Cynet" "D.D." "D/D" "D/D/D" + "Danger!" "Dark Contract" "Dark Magician" "Dark Scorpion" "Dark World" + "Darklord" "Deep Sea" "Demise" "Deskbot" "Despia" "Destiny HERO" + "Destruction Sword" "Dice" "Digital Bug" "Dinomist" "Dinomorphia" + "Dinowrestler" "Divine Dragon" "Djinn" "Dododo" "Dogmatika" "Doll" + "Doodle Beast" "Doodlebook" "Doriado" "Dracoslayer" "Dracoverlord" + "Dragonmaid" "Dragunity" "Dream Mirror" "Drytron" "Dual Avatar" "Duston" + "Earthbound" "Edge Imp" "Egyptian God" "Eldlich" "Elemental HERO" + "Elemental Lord" "Elementsaber" "Empowered Warrior" "Endymion" "Evil Eye" + "Evil HERO" "Evil★Twin" "Evolsaur" "Evoltile" "Evolzar" + "Exchange of the Spirit" "Exodia" "Exosister" "Eyes Restrict" "F.A." "Fabled" + "Fairy" "Fairy Tail" "Fire Fist" "Fire Formation" "Fire King" "Firewall" + "Fishborg" "Flamvell" "Fleur" "Floowandereeze" "Flower Cardian" "Fluffal" + "Forbidden" "Fortune Fairy" "Fortune Lady" "Fossil" "Frightfur" "Frog" + "From the Underworld" "Fur Hire" "Fusion" "G Golem" "Gadget" "Gagaga" + "Gaia Knight" "Gaia The Fierce Knight" "Galaxy" "Galaxy-Eyes" "Gandora" + "Gate Guardian" "Geargia" "Gem-" "Generaider" "Genex" "Ghostrick" "Ghoti" + "Gimmick Puppet" "Gishki" "Gizmek" "Glacial Beast" "Gladiator Beast" "Gogogo" + "Gold Pride" "Golden Castle of Stromberg" "Golden Land" "Gorgonic" "Gouki" + "Gravekeeper's" "Graydle" "Greed" "Grepher" "Guardian" "Guardragon" "Gunkan" + "Gusto" "Harpie" "Hazy" "Hellfire" "Herald" "Heraldic" "Heraldry" "HERO" + "Heroic" "Hi-Speedroid" "Hieratic" "Hole" "Holy Knight" + "Horus the Black Flame Dragon" "Hyperion" "Ice Barrier" "Icejade" "Igknight" + "Impcantation" "Infernity" "Infernoble Knight" "Infernoid" "Infestation" + "Infinitrack" "Invoked" "Inzektor" "Iron Chain" "Jar" "Jester" "Jinzo" "Junk" + "Jurrac" "Kaiju" "Kairyu-Shin" "Karakuri" "Kashtira" "Knight" "Knightmare" + "Koa'ki Meiru" "Koala" "Kozmo" "Krawler" "Kuriboh" "Labrynth" + "Labyrinth Wall" "Lady of Lament" "Laval" "Legendary Knight" "Libromancer" + "Lightsworn" "Lil-la" "Live☆Twin" "Lswarm" "Lunalight" "Lyrilusc" "Machina" + "Madolche" "Madoor" "Magical Musket" "Magician" "Magician Girl" "Magikey" + "Magistus" "Magnet Warrior" "Majespecter" "Majestic" "Maju" "Malefic" + "Malicevorous" "Man-Eater Bug" "Manadome" "Marincess" "Martial Art Spirit" + "Mask" "Masked HERO" "Materiactor" "Mathmech" "Mayakashi" "Mayakashi Shiranui" + "Mecha Phantom Beast" "Megalith" "Mekk-Knight" "Meklord" "Melffy" "Melodious" + "Mermail" "Metalfoes" "Metaphys" "Mikanko" "Mist Valley" "Mokey Mokey" + "Monarch" "Morphtronic" "Mystical Beast of the Forest" "Myutant" "Naturia" + "Nekroz" "Nemeses" "Nemleria" "Nemurelia" "Neo Space" "Neo-Spacian" "Neos" + "Nephthys" "Nimble" "Ninja" "Ninjitsu Art" "Noble Knight" "Nordic" + "Nouvellez" "Number" "Numeron" "Odd-Eyes" "Ogdoadic" "Ojama" "Onomat" + "Orcust" "Overlay" "P.U.N.K." "Paladins of Dragons" "Paleozoic" "Parasite" + "Parshath" "Pendulum" "Pendulum Dragon" "Penguin" "Performage" "Performapal" + "Phantasm Spiral" "Phantom Beast" "Phantom Knights" "Photon" + "Plunder Patroll" "Polymerization" "Possessed" "Potan" "Power Tool" + "Prank-Kids" "Predaplant" "Prediction Princess" "Priestess" "PSY-Frame" + "Puppet" "Purrely" "Qli" "Raidraptor" "Rank-Up-Magic" "Recipe" "Red-Eyes" + "Reptilianne" "Rescue-ACE" "Resonator" "Rikka" "Ritual Beast" "Roid" "Rokket" + "Roland" "Rose" "Rose Dragon" "Runick" "S-Force" "Sacred Beast" + "Salamangreat" "Scareclaw" "Scrap" "Scrap-Iron" "Secret Six Samurai" + "Seven Emperors" "Seventh" "Shaddoll" "Shark" "Shinobird" "Shiranui" + "Silent Magician" "Silent Swordsman" "Simorgh" "Six Samurai" "Skull Servant" + "Sky Scourge" "Sky Striker" "Slime" "Solemn" "Solfachord" "Speedroid" + "Spellbook" "Sphinx" "Spider" "Spirit Message" "Spiritual Art" "Spright" + "Springans" "SPYRAL" "Star" "Star Seraph" "Stardust" "Starliege" + "Starry Knight" "Stealth Kragen" "Steelswarm" "Stellarknight" "Subterror" + "Sunavalon" "Sunseed" "Sunvine" "Super Defense Robot" "Super Quant" + "Superheavy" "Superheavy Samurai" "Supreme King" "Swordsoul" "Sylvan" + "Symphonic" "Symphonic Warrior" "Synchro" "Synchron" "T.G." "Tearlaments" + "Tellarknight" "Tenyi" "The Agent" "The Sanctuary in the Sky" "The Weather" + "Therion" "Thunder Dragon" "Time Thief" "Timelord" "Tindangle" "Toon" "Train" + "Transcendrake" "Trap Hole" "Trap Monster" "Traptrix" "Tri-Brigade" "Triamid" + "Trickstar" "True Draco" "U.A." "Umbral Horror" "Umi" "Unchained" "Ursarctic" + "Utopia" "Utopic" "Valkyrie" "Vampire" "Vanquish Soul" "Vassal" "Vaylantz" + "Vendread" "Venom" "Vernusylph" "Virtual World" "Visas" "Vision HERO" "Void" + "Volcanic" "Vylon" "War Rock" "Watt" "White" "Wicked God" "Wind-Up" + "Windwitch" "Witchcrafter" "World Chalice" "World Legacy" "Worm" "X-Saber" + "Xyz" "Yang Zing" "Yosenju" "Yubel" "Zefra" "ZEXAL" "Zoodiac") + ;; Card Rarity - Not in use at the moment. + :RARITY + ("10000 Secret Rare" "Collector's Rare" "Common" "Ghost/Gold Hybrid Rare" + "Ghost Rare" "Gold Rare" "Gold Secret Rare" "Mosaic Rare" "Parallel Rare" + "Platinum Rare" "Platinum Secret Rare" "Premium Gold Rare" "Prismatic Secret Rare" + "Quarter Century Secret Rare" "Rare" "Rare Parallel Rare" "Rush Rare" + "Secret Pharaoh's Rare" "Secret Rare" "Shatterfoil" "Starfoil Rare" + "Starlight Rare" "Super Parallel Rare" "Super Rare" "Ultimate Rare" + "Ultra Parallel Rare" "Ultra Pharaoh's Rare" "Ultra Rare") + :LIMIT + ("10" "50" "100" "1000" "2000"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; search.lisp diff --git a/src/web/qr.lisp b/src/web/qr.lisp new file mode 100644 index 0000000..e6d83c4 --- /dev/null +++ b/src/web/qr.lisp @@ -0,0 +1,41 @@ +#| + +src/web/qr.lisp + +QR Generator + +New style web page with DEFCLASS brings some challenges: APPLY +#'FN e.g. MAKE-INSTANCE usually expects a PLIST e.g. :SLOT VALUE, +however, rendering with HTMX/HTML will require (SYMBOL-VALUE +:SLOT) e.g. "8-BIT-BYTE" which makes matching them up +tricky. Perhaps this is a solved problem. + +|# + +(in-package #:cl-deck-builder2.web) + +(defparameter *qr-settings* (make-instance 'qr-settings) + "The saved settings for the QR module.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/qr" :method :GET) () + "QR Generator Main Route. Display the index page. Login Required." + (v:info :qr "GET /qr") + + (with-logged-in-user + (setf (getf (response-headers *response*) :cache-control) "no-cache, must-revalidate") + (render-with-env #P"qr.html" + (append + (list :active "/qr") + (qr-to-plist *qr-settings* t))))) + +(defroute ("/qr" :method :POST) (&key _parsed) + "QR Generator Main Route. Update the *QR-SETTINGS* and display the result. Login Required." + (v:info :qr "POST /qr => ~a" _parsed) + + (with-logged-in-user + (alexandria:if-let ((settings (reinitialize-instance *qr-settings* :content _parsed))) + (alexandria:if-let ((html (qr-generate settings))) + html + (_ "Something went wrong. Try again?")) + (setf *qr-settings* (make-instance 'qr-settings))))) diff --git a/src/web/query.lisp b/src/web/query.lisp new file mode 100644 index 0000000..16301ea --- /dev/null +++ b/src/web/query.lisp @@ -0,0 +1,156 @@ +#| + +I didn't want to keep the query stuff in the model as it's not really +got anything to do with the model and more with the database. I tried +the DATABASE-OBJECT approach and that was clumsy too. + +I just want to write queries. Maybe I should have used CL-YESQL. + +TODO I have multiple of these too: saved-deck-list, +saved-category-list, constructed-deck-list ... where should these go? +Into their respective pacakges? + +|# + +(in-package #:cl-deck-builder2.web.query) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmacro select-attachment (&body body) + `(select-dao 'attachment + (sxql:order-by :created-at :desc) + ,@body)) + +(defun select-attachment-by-id (id) + (first (select-attachment + (sxql:where (:= :id id))))) + +(defmacro select-category (&body body) + `(select-dao 'category + ;; Why did I use this? + ;; (sxql:group-by name) + (sxql:order-by :created-at :desc) + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmacro select-feedback (&body body) + `(select-dao 'feedback + (mito:includes 'user) + (sxql:order-by :created-at :desc) + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO Now where does this go? db? toolkit? +(defun %sxql-like (field value) + "Helper function. Turn \"value\" into \"%value%\" for SQL LIKE queries." + (list :like field (format nil "%%~a%%" value))) + +(defmacro select-ydk-deck (&body body) + `(select-dao 'ydk-deck + (sxql:order-by :asc :created-at) + ,@body)) + +(defmacro select-deck-item (&body body) + `(select-dao 'deck-item + ;; (mito:includes 'ydk-deck) + (sxql:order-by :asc :created-at) + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmacro select-constructed-decks (&body body) + `(select-dao 'constructed-deck + (mito:includes 'ydk-deck) + (sxql:order-by :asc :created-at) + ,@body)) + +(defmacro select-constructed-deck-items (&body body) + `(select-dao 'constructed-deck-item + (mito:includes 'ygo-card 'deck-item 'ygo-set-item) + (sxql:order-by :asc :created-at) + ,@body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; builder.lisp +;; (defun unique-cards-in-decks () +;; "Select the unique cards in all DECK-ITEMs" +;; (let ((deck-items +;; (with-connection (db) +;; (mito:select-dao 'deck-item +;; (sxql:group-by :passcode)))) +;; (ht (make-hash-table))) +;; (dolist (deck-item deck-items ht) +;; (push deck-item (gethash (deck-id-of deck-item) ht))))) + +;; Asked for this one. For every card in a deck, show what deck the card is in... +(defun cards-in-all-decks () + "Select the unique cards in all DECK-ITEMs" + (let ((deck-items + (with-connection (db) + (with-transaction + (select-deck-item)))) + (ht (make-hash-table))) + (with-connection (db) + (with-transaction + (dolist (deck-item (reverse deck-items) ht) + (pushnew (list :deck-id (deck-id-of deck-item) + :ydk-deck (mito:find-dao 'ydk-deck :id (deck-id-of deck-item)) + :ygo-card (mito:find-dao 'ygo-card :id (deck-passcode-of deck-item))) + (gethash (deck-passcode-of deck-item) ht) + :key (lambda (plist) (getf plist :deck-id)))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun select-variant-condition () + (select-dao 'variant-condition)) + +(defun ygo-set-item-by-item-id (item-id) + (with-connection (db) + (with-transaction + (let ((set-items + (mito:select-dao 'ygo-set-item + (mito:includes 'ygo-set 'variant-condition) + (sxql:where (:= :item-id item-id))))) + (dolist (set-item set-items set-items) + (setf (item-of set-item) + (ygo-set-by-id (mito:object-id set-item)))))))) + +(defun ygo-set-item-by-id (id) + (with-connection (db) + (with-transaction + (let ((set-item (mito:select-dao 'ygo-set-item + (mito:includes 'ygo-set + 'variant-condition) + (sxql:where (:= :id id))))) + (setf (item-of set-item) + (ygo-set-by-id (mito:object-id set-item))) + set-item)))) + +(defun select-ygo-cc-item-variants (id) + "YGO-CC-ITEM is a relatively new addition, a database relationship between CC-ITEMs and VARIANT. Actually now that I think about it as I write this, YGO-CC-ITEM is a misnomer, and this is actually a CC-ITEM-VARIANT. +SELECT all the variants available for this CC-ITEM ID. + +This will generate a blank of 5 items if they don't already have corresponding YGO-SET-ITEMS in the database +It will also fetch all corresponding YGO-SET-ITEMS. If they do exist, REMOVE-DUPLICATES will use MITO:OBJECT-ID to remove them by VARIANT-OF. + +TODO FIXME" + (with-connection (db) + (with-transaction + (remove-duplicates + ;; Append the blanks and the existing data from the database, and remove-duplicates + (append + ;; Create a list of blanks + (mapcar (lambda (v) + (make-instance 'ygo-set-item :item-id id + :item (ygo-set-by-id id) + :variant v + :variant-id (mito:object-id v))) + (select-variant-condition)) + ;; Select what exists from the database, and fill in the YGO-SET info with the macro we wrote + (ygo-set-item-by-item-id id)) + :key (alexandria:compose #'mito:object-id #'variant-of))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO: old ygoprodeck code +(defun ygo-select-info-by-id (id &rest fields-and-values) + (apply #'find-dao 'ygo-info :id id fields-and-values)) + +(defun ygo-select-info-by-passcode (passcode &rest fields-and-values) + (apply #'find-dao 'ygo-info :passcode passcode fields-and-values)) diff --git a/src/web/search.lisp b/src/web/search.lisp new file mode 100644 index 0000000..de3daff --- /dev/null +++ b/src/web/search.lisp @@ -0,0 +1,138 @@ +#| + +src/web/search.lisp + +This is some ancient old dusty code that had preminitions of how +things would work in the future. + +The idea was that there'd be a list of default parameters, maybe +you've seen it in the code, *SEARCH-DEFAULTS*. And this machinery +would query that default, then build an SQL query to hit the +database based on what was read from Caveman2. + +This code needs a rewrite. + +|# + +(in-package #:cl-deck-builder2.web) + +;; TODO defstruct search-params? +(defparameter *search-defaults* + '(("direction" . "asc") + ("limit" . "10") + ("name" . "") + ("variant" . (1 . "on")) + ("offset" . "0") + ("sort-by" . "id"))) + +(defun make-op-list (kind name value &key (fuzzy nil)) + (if fuzzy + (setf kind "LIKE" + value (format nil "%~a%" value))) + (list + (alexandria:make-keyword kind) + (alexandria:make-keyword (string-upcase + (substitute #\_ #\- name))) + value)) + +(defparameter +param-field-whitelist+ + '("amazon-asin" "barcode" "brand" "buy-price" "category" "code" "created-by" + "deck-id" "desc" "description" "domestic-only" "edition" "email" "id" "kind" + "linkmarkers" "linkval" "manufacturer-sku" "max-qty" "msrp" "parent" "price" + "product-name" "rarity" "rarity-code" "sell-price" "tax-exempt" "total-qty" + "url" "weight" "wishlists" "atk" "def" "level" "scale" "passcode" "type" + "frame-type" "race" "attribute" "archetype" "name" "opt-qty" "qty" "condition") + "A whitelist of keywords allowed by user input on the database side.") + +(defun filter-alist (alist &optional (whitelist +param-field-whitelist+)) + "Filter ALIST by CAR matching MEMBER in WHITELIST." + (remove-if (lambda (pair) + (destructuring-bind (key . value) + pair + (if (and (member key whitelist :test #'equal) + value + (> (length value) 0)) + nil + t))) + alist)) + +;; (defparameter *variant-id* +;; (mito:object-id (find-dao 'variant :name "Near Mint"))) + +(defun make-where-clause (alist) + "Helper function. Will rewrite ALIST into a series of SXQL queries. This is terrible, and needs a rewrite." + (let* ((fuzzy-match (query-param "fuzzy-match" alist)) + (op (or (query-param "and-or" alist) "and")) + (params (or + (remove nil + (mapcar + (lambda (pair) + (destructuring-bind (name . value) + pair + (when (string= name "product-name") + (setf name "name")) + (when (and value (> (length value) 0)) + (let ((kind (make-kind name alist)) + (fuzzy (and (string= name "name") + (string= fuzzy-match "on")))) + (make-op-list kind name value :fuzzy fuzzy))))) + (filter-alist alist))) + (list (sxql:make-op :not-null :name))))) + (sxql:where + (apply #'sxql:make-op (alexandria:make-keyword (string-upcase op)) + params)))) + +;; TODO This isn't terrible but I don't know how to improve +(defun make-kind (kind alist) + (if + (or (string= kind "atk") + (string= kind "def") + (string= kind "level") + (string= kind "scale")) + (let ((param (query-param (format nil "~a-kind" kind) alist))) + (cond ((string= "eq" param) "=") + ((string= "ge" param) ">=") + ((string= "le" param) "<=") + ((string= "lt" param) "<") + ((string= "gt" param) ">") + (t "="))) + "=")) + +;; TODO default arguments +(defun make-search-query (class _parsed) + (let ((direction (or (query-param "direction" _parsed) "desc")) + (limit (or (query-param "limit" _parsed) "10")) + (sort-by (or (query-param "sort-by" _parsed) "id")) + (offset (or (query-param "offset" _parsed) "0"))) + (with-connection (db) + (with-transaction + (mito:select-dao class + (sxql:limit limit) + (sxql:offset offset) + (make-order-by direction sort-by) + (make-where-clause _parsed)))))) + +;; TODO this is bad and could be re-written using mito:count-dao maybe? +(defun make-count-query (class _parsed) + (let ((place (with-connection (db) + (with-transaction + (mito:retrieve-by-sql + (sxql:select + ((:as (:count :id) :count)) + (sxql:from + (alexandria:make-keyword + (string-upcase + (mito.class:table-name (find-class class))))) + (make-where-clause _parsed))))))) + (getf (car place) :count))) + +(defun make-order-by (direction sort-by) + "Construct SXQL:ORDER-BY list values" + (let ((dir (if (string= direction "asc") + :asc + :desc)) + (sortby (if (member sort-by +param-field-whitelist+ :test #'string=) + (alexandria:make-keyword + (string-upcase sort-by)) + :id))) + (sxql:order-by (list dir sortby)))) diff --git a/src/web/setup.lisp b/src/web/setup.lisp new file mode 100644 index 0000000..0e67a85 --- /dev/null +++ b/src/web/setup.lisp @@ -0,0 +1,35 @@ +(in-package #:cl-deck-builder2.web) + +;; +;; Application + +(defclass () ()) +(defvar *web* (make-instance ')) +(clear-routing-rules *web*) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Aha,, figured it out. Hermetic Setup! +(setup + ;; str->bool, t if a username exists, nil otherwise + :user-p (lambda (str) + (ignore-errors + (find-dao 'user :email str))) + ;; str->str, maps a username to a password (hash, hopefully) + :user-pass (lambda (arg) + (ignore-errors + (mito-auth:password-hash + (find-dao 'user :email arg)))) + ;; str->(list sym), maps a username to a list of roles, + ;; for example: (:user) (:user :tester :staff) (:user :admin) + :user-roles (lambda (arg) + (ignore-errors + (mapcar #'name-of + (user-roles-of + (find-dao 'user :email arg))))) + ;; the /expression/ for the session object. ningle:*session* on + ;; Ningle . + :session caveman2:*session* + ;; A function that displays an "access denied" message + :denied (lambda (&optional params) + (declare (ignore params)) + (cl-markup:html5 (:h1 "Generic auth denied page")))) diff --git a/src/web/sse.lisp b/src/web/sse.lisp new file mode 100644 index 0000000..0b09e5b --- /dev/null +++ b/src/web/sse.lisp @@ -0,0 +1,88 @@ +#| + +src/web/sse.lisp + +HTMX Server Sent Events + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.web.sse + (:use #:cl) + (:import-from #:websocket-driver + #:make-client + #:make-server + #:on + #:send + #:start-connection + #:close-connection) + (:import-from #:cl-deck-builder2.i18n + #:_) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:make-chat-client + #:make-chat-server + #:message)) + +(in-package #:cl-deck-builder2.web.sse) + +;; TODO (defclass chat-room () ()) +(defvar *connections* (make-hash-table)) + +(defvar *chat-handler* nil + "The WebSocket client on the server side.") + +(defun handle-new-connection (con) + (setf (gethash con *connections*) + ;; (format nil "user-~a" (random 100000)) + (princ-to-string (gensym "USER-")))) + +(defun broadcast-to-room (connection message) + (let ((message (format nil "

      ~a: ~a

      " + (gethash connection *connections*) + message))) + (loop :for con :being :the :hash-key :of *connections* :do + (send con message)))) + +(defun handle-close-connection (connection) + (let ((message (format nil "

      ... ~a disconnected.

      " + (gethash connection *connections*)))) + (loop :for con :being :the :hash-key :of *connections* :do + (send con message)))) + +(defun make-chat-server (env) + (handler-case + (let ((ws (make-server env))) + (on :open ws + (lambda () (handle-new-connection ws))) + + (on :message ws + (lambda (msg) (broadcast-to-room ws msg))) + + (on :close ws + (lambda (&key code reason) + (declare (ignore code reason)) + (handle-close-connection ws))) + + (lambda (responder) + (declare (ignore responder)) + (start-connection ws))) + (error (e) + (declare (ignore e)) + (_ "Something went wrong. Try again?")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun make-chat-client (&key (address "localhost") (port 5001) (endpoint "chat-server") &allow-other-keys) + (let ((url (format nil "ws://~a:~d/~a" address port endpoint))) + (when *chat-handler* + (close-connection *chat-handler*) + (setf *chat-handler* nil)) + (setf *chat-handler* + (start-connection + (make-client url))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun message (message) + (unless *chat-handler* + (make-chat-client)) + (send *chat-handler* message)) diff --git a/src/web/test.lisp b/src/web/test.lisp new file mode 100644 index 0000000..967d7d9 --- /dev/null +++ b/src/web/test.lisp @@ -0,0 +1,56 @@ +#| + +Workbench Module + +|# + +(in-package #:cl-deck-builder2.web) + +(defroute ("/test" :method :GET) (&key _parsed) + "Workbench Test Route" + (if _parsed + (format nil "~a" _parsed) + (render-with-env #P"workbench/test.html" + (list :id 1)))) + +(defun selected-sets-to-deck-listing (cards) + (let ((lst '())) + (with-connection (db) + (with-transaction + (dolist (card cards lst) + (destructuring-bind (set variant) + card + (let ((set (mito:find-dao 'ygo-set :id (cdr set))) + (variant (mito:find-dao 'variant-condition :id (cdr variant)))) + (with-slots ((passcode-id cl-deck-builder2.models.ygoprodeck.classes::passcode-id)) + set + (push (list :ygo-set set + :condition variant + :ygo-card (mito:find-dao 'ygo-card :id passcode-id)) + lst))))))))) + +(defroute ("/test" :method :POST) (&key _parsed) + "Workbench Test Route" + ;; (v:info :test "POST /test ~a" _parsed) + ;; (format nil "~a" _parsed) + (render-with-env #P"construct/constructed-deck-list.html" + (list + (with-output-to-string (s) + (format s "
      ")
      +    (alexandria:if-let ((cards (query-param "cards" _parsed)))
      +      (format s "~a" (selected-sets-to-deck-listing cards)))
      +    (format s "
      "))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/chat" :method :GET) () + "HTMX SSE Endpoint. Functional, but not in use. + +https://htmx.org/extensions/server-sent-events/" + (with-logged-in-user + (render-with-env #P"chat.html"))) + +(defroute ("/html" :method :GET) (&key |path|) + "Raw HTML viewer. Straight up render a file. Login Required." + (with-logged-in-user + (when |path| + (render-with-env (make-pathname :name |path|))))) diff --git a/src/web/tinymce.lisp b/src/web/tinymce.lisp new file mode 100644 index 0000000..f14a95f --- /dev/null +++ b/src/web/tinymce.lisp @@ -0,0 +1,61 @@ +#| + +src/web/tinymce.lisp + +TinyMCE Hosting + +TODO Handle POST Route + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.web.tinymce + (:use #:cl) + (:import-from #:caveman2 + #: + #:defroute + #:clear-routing-rules) + (:import-from #:cl-deck-builder2.web + #:render-with-env) + (:local-nicknames (#:v #:org.shirakumo.verbose)) + (:export #:*web*)) + +(in-package #:cl-deck-builder2.web.tinymce) + +(defclass () ()) +(defvar *web* (make-instance ')) +(clear-routing-rules *web*) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defclass mce-document () + ((name :initarg :name) + (path :initarg :path) + (body :initarg :body))) + +(defvar *current-document* (make-instance 'mce-document :name "test" :path #P"/tmp/test")) + +(defun update-current-document (name body) + (reinitialize-instance *current-document* :name name + :body body)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/" :method :GET) (&key _parsed) + "TinyMCE Index Page" + + ;; (declare (ignore _parsed)) + (v:info :tinymce "GET / ~a" _parsed) + + (render-with-env #P"tinymce/index.html" + `(:active ,"/tinymce" + :file ,*current-document*))) + +(defroute ("/" :method :POST) (&key |mode| |filename| |default| _parsed) + "TinyMCE POST Route: Handle User Input" + + (v:info :tinymce "POST / ~a" _parsed) + + (when (string= |mode| "create") + (update-current-document |filename| |default|)) + + (princ-to-string |default|)) diff --git a/src/web/toolkit.lisp b/src/web/toolkit.lisp new file mode 100644 index 0000000..ccf74b9 --- /dev/null +++ b/src/web/toolkit.lisp @@ -0,0 +1,78 @@ +#| + +src/web/toolkit.lisp + +Helper Functions - Web Toolkit + +TODO move to src/toolkit/web.lisp ? + +TODO Packaging up and exporting macros seems to be a bad idea, it +causes some kind of symbol errors. Maybe we need to be using the full +name of each thing in a defmacro? I feel like taichi would say don't +use a macro at all. + +|# + +(in-package #:cl-deck-builder2.web) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *md2html* "/usr/lib/cgit/filters/html-converters/md2html") + +(defmacro with-logged-in-user (&body body) + `(progn + ;; (v:info :web "~a" caveman2:*request*) + ;; + ;; Store the current REQUEST-PATH-INFO + ;; + ;; We lose the query parameters when we do it this way. Problem? Not sure. + ;; + ;; (setf (gethash :request-query-parameters caveman2:*session*) + ;; (lack.request:request-query-parameters caveman2:*request*)) + (setf (gethash :request-path-info caveman2:*session*) + (lack.request:request-path-info caveman2:*request*)) + (if (logged-in-p) + (progn + ,@body) + (progn + (flash-error (_ "Please log in.")) + (redirect "/user/login"))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-with-env (tpl &optional lst) + "Helper macro: APPEND some variables to ENV list passed to RENDER." + (render tpl + (append lst + `(:messages ,(flash-gethash) + :session ,*session* + :token ,(token) + :is-admin ,(ignore-errors + (user-admin-p + (find-user (user-name)))) + :is-verified ,(ignore-errors + (user-verified-p + (find-user (user-name)))) + :csrf_token ,(lack.middleware.csrf:csrf-token *session*))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-markdown (pathspec &key env title (tpl #P"markdown.html")) + "Render Markdown Page with template TPL" + (alexandria:if-let ((path (probe-file pathspec))) + (alexandria:if-let ((html (render-markdown-file pathspec))) + (render-with-env tpl + (append env + (list :title title :html html)))))) + +(defun render-markdown-file (pathspec) + "Wrap md2html from cgit installation." + (with-output-to-string (s) + (uiop:run-program *md2html* + :output s + :input pathspec))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun token () + "Helper function: get the CSRF token associated with this *REQUEST*." + ;; (cdr (assoc "lack.session" (request-cookies *request*) + ;; :test #'string=)) + ;; https://40ants.com/lisp-project-of-the-day/2020/06/0113-lack-middleware-csrf.html + (lack.middleware.csrf:csrf-html-tag *session*)) diff --git a/src/web/upload.lisp b/src/web/upload.lisp new file mode 100644 index 0000000..69497e3 --- /dev/null +++ b/src/web/upload.lisp @@ -0,0 +1,107 @@ +(in-package #:cl-deck-builder2.web) + +(defvar *icon-file-types* + (mapcar #'pathname-name + (mapcar (lambda (x) (uiop:enough-pathname x (merge-pathnames "img/file-icons-classic/" cl-deck-builder2.config::*static-directory*))) + (uiop:directory-files (merge-pathnames "img/file-icons-classic/" cl-deck-builder2.config::*static-directory*))))) + +(defun icon-or-url (file-url) + (let ((ext (pathname-type file-url)) + (thumbnails-allowed '("gif" "jpeg" "jpg" "png" "svg" "webp"))) + (cond + ((member ext thumbnails-allowed :test #'equal) file-url) + ((member ext *icon-file-types* :test #'equal) + (merge-pathnames + (make-pathname :directory "img/file-icons-classic" :name ext :type "svg") + cl-deck-builder2.config::*static-directory*)) + (t "/img/file-icons-classic/bin.svg")))) + +(defun render-upload-index-old () + "Helper Function. The purpose of RENDER-UPLOAD-INDEX is to generate a list of file information and thumbnail info from The files in the MITO-ATTACHMENT system. + +This is pretty crazy and probably needs to be unpacked into a few components: + +IMAGES: SELECT-DAO 'ATTACHMENT. This could probably just be something like GET-ATTACHMENTS. + +ICON-OR-URL: This component decides wether or not a provided URL might be a: JPG, PNG, GIF, SVG, etc., Binary File, or something we have icons for. + +THUMBNAILS-ALLOWED The supported viewable file types we want to forward straight to the web interface. + +ICON-EXISTS could probably use STATIC-PROBE-FILE-LIST. + +Then the final PLIST at the end could be similar to YDK-TO-TEMPLATE-DATA. ATTACHMENT-PLIST? + +We use icons from [dmhendricks/file-icon-vectors](https://github.com/dmhendricks/file-icon-vectors)." + (let ((images (select-attachment))) + (let ((files (mapcar + (lambda (image) + (let* ((url (mito-attachment:file-url image)) + (icon (icon-or-url url))) + `((:id . ,(mito:object-id image)) + (:url . ,url) + (:icon . ,icon) + (:attachment . ,image)))) + images))) + ))) + +(defun %images-to-plist (&rest images) + (let ((lst '())) + (dolist (image images (reverse lst)) + (let ((file-url (mito-attachment:file-url image))) + (push `((:id . ,(mito:object-id image)) + (:url . ,file-url) + (:icon . ,(icon-or-url file-url)) + (:attachment . ,image)) + lst))))) + +(defun render-upload-index () + (let* ((images (select-attachment)) + (files (apply #'%images-to-plist images))) + (render-with-env #P"upload.html" + `(:active "/upload" + :files ,files + :length ,(length images))))) + +(defroute ("/upload" :method :GET) () + "File Uploader Main Route." + (v:info :upload "GET /upload") + + (with-logged-in-user + (render-upload-index))) + +(defroute ("/upload" :method :POST) (&key _parsed) + "File Uploader POST processing Route. + +TODO Can this be merged with YDK uploader?" + (v:info :upload "POST /upload => ~a" _parsed) + + (with-logged-in-user + (let ((files (query-param "files" _parsed))) + (dolist (file files) + (destructuring-bind (content filename content-type) + file + (let ((success (create-attachment content filename content-type))) + (if success + (flash-message (format nil (_ "Upload success! ~a") + (mito-attachment:file-url success) + (mito-attachment:file-url success))) + (flash-error (_ "Error uploading file. Try again?"))))))) + (render-upload-index))) + +(defroute ("/upload/:id" :method :DELETE) (&key id) + "Remove an ATTACHMENT from the database using DELETE-BY-VALUES for ATTACHMENT by ID." + (v:info :upload "DELETE /upload/~d" id) + + (with-logged-in-user + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (let ((found (find-dao 'attachment :id id))) + (if found + (delete-by-values 'attachment :id id) + (progn + (flash-error (format nil (_ "No such attachment #~d") id)) + (redirect "/upload" 400))))) + (ratify:combined-error (e) + (flash-error e) + (redirect "/upload" 400))))) diff --git a/src/web/user.lisp b/src/web/user.lisp new file mode 100644 index 0000000..311c217 --- /dev/null +++ b/src/web/user.lisp @@ -0,0 +1,184 @@ +#| + +src/web/user.lisp + +User Interaction Frontend + +|# + +(in-package :cl-deck-builder2.web) + +;; This seems to be okay. The intent of overriding HERMETIC::AUTHORIZE +;; is we use MITO-AUTH for user authentication and their class isn't +;; just a password hash. So this is an undocumented way of customizing +;; authorization method using HERMETIC. +(defun hermetic::authorize (username password) + (let ((object (funcall hermetic::*user-p* username))) + (and object + (string= (mito-auth:password-hash object) + (mito-auth::make-password-hash + password + (mito-auth:password-salt object)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun already-logged-in-redirect () + "Tell the user they are already logged in, and send them to the home page." + (progn + (flash-error (format nil (_ "You are already logged in as: ~a") + (user-name))) + (redirect "/"))) + +(defun user-exists-register-redirect () + "Tell the user that email address already is registered, and send them to the registration page." + (progn + (flash-error (_ "A user with that email is already registered.")) + (redirect "/user/register" 302))) + +(defun login-redirect (email password &optional (target "/")) + "Try to log in using HERMETIC::LOGIN, and display an appropriate message upon success. + +TARGET may be set to the redirect target, and is provided from :REQUEST-PATH-INFO in *SESSION*." + (handler-case + (ratify:with-parsed-forms + ((:email email) + (:string password)) + (let ((params (list :|username| email :|password| password))) + (login params + (progn + (flash-message (format nil (_ "Hello, ~a!") + (user-name))) + (redirect target)) + (progn + (flash-error (_ "Incorrect password.")) + (redirect "/user/login")) + (progn + (flash-error (_ "No user corresponding to this email address.")) + (redirect "/user/login"))))) + (ratify:combined-error (e) + (flash-error e) + (redirect "/user/login" 302)))) + +(defun register-new-user (name email password) + "Try to register a new user by first SELECTing any matching USERs with EMAIL. If there is a match, use USER-EXISTS-REGISTER-REDIRECT. +Otherwise, create a new user, display the appropriate message, and redirect." + (handler-case + (ratify:with-parsed-forms + ((:string name) + (:string password) + (:email email)) + (with-connection (db) + (let ((found (mito:find-dao 'user :email email))) + ;; Try to select this username, if a username is returned, the + ;; user already exists, and display error. + ;; + ;; Otherwise, create the user and add to the database. + ;; Automatically direct to index.html on success. + (if found + (user-exists-register-redirect) + (progn + (if (create-user name email password) + (progn + (flash-message (_ "Success! Please log in.")) + (redirect "/user/login")) + (progn + (flash-message (_ "Something went wrong. Try again?")) + (redirect "/user/register")))))))) + (ratify:combined-error (e) + (flash-error e) + (flash-error (_ "Something went wrong. Try again?")) + (redirect "/user/register")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun render-user-list (&optional (tpl #P"user/index.html")) + "Helper function. Display User Index page." + (render-with-env tpl + `(:active "/user" + :user-list ,(select-dao 'user)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/user" :method :GET) () + "User Settings Main Index Page." + (with-logged-in-user + (render-with-env #P"user/index.html" + `(:active "/user")))) + +(defroute ("/user/login" :method :GET) () + "User Login Route - require the user to log in." + ;; Always display the USER/LOGIN page + (if (ignore-errors + (logged-in-p)) + (already-logged-in-redirect) + (render-with-env #P"user/login.html" + `(:active "/user")))) + +(defroute ("/user/login" :method :POST) (&key _parsed) + "User Login POST Route. We use HERMETIC:LOGGED-IN-P to do all the heavy lifting. LOGIN-REDIRECT does the redirect." + (v:info :web "POST /user/login => ~a" _parsed) + + ;; (v:info :web "REQUEST-PATH-INFO: ~a SESSION-REQUEST-PATH-INFO: ~a" + ;; (lack.request:request-path-info *request*) + ;; (gethash :request-path-info *session*)) + + (if (logged-in-p) + (already-logged-in-redirect) + (login-redirect (query-param "email" _parsed) + (query-param "password" _parsed) + (gethash :request-path-info *session*)))) + +;; TODO successmessage and errormessages +;; TODO POST /user/logout ? Currently just GET /user/logout will log you out :P +(defroute "/user/logout" () + "User Logout Route. GET request. Querying this from anywhere with the LACK.SESSION cookie set will trigger a logout and redirect." + (logout + (flash-message (_ "Logged out.")) + (flash-error (_ "You aren't logged in."))) + (redirect "/" 302)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Register +;; TODO React To Session Cookie In default.html +;; Not sure what this is about^ +(defroute ("/user/register" :method :GET) () + "User Registration Main Route. Registration required!" + (let ((logged-in (logged-in-p))) + (if logged-in + (already-logged-in-redirect) + (render-with-env #P"user/register.html" + `(:active "/user"))))) + +(defroute ("/user/register" :method :POST) (&key _parsed) + "User Registration POST Route. REGISTER-NEW-USER does all the work." + (v:info :web "POST /user/register => ~a" _parsed) + + (let ((logged-in (logged-in-p))) + (if logged-in + (already-logged-in-redirect) + (register-new-user (query-param "name" _parsed) + (query-param "email" _parsed) + (query-param "password" _parsed))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defroute ("/user/:id/delete" :method :POST) (&key id) + "User DELETE Route. ADMIN role only." + (v:info :user "DELETE /user ~d" id) + (handler-case + (ratify:with-parsed-forms + ((:integer id)) + (auth (:admin) + (with-connection (db) + (mito:delete-by-values 'user :id id) + ""))) + (ratify:combined-error (e) + (flash-error e)))) + +(defroute ("/user/:id/roles" :method :GET) (&key id) + "User Roles Route. ADMIN role only." + (auth (:admin) + (format nil "~{~s~^ ~}" + (mapcar #'name-of + (user-roles-of (find-dao 'user :id id)))))) + +(defroute ("/user/admin" :method :GET) () + "Main User List Route. ADMIN role only." + (auth (:admin) + (render-user-list #P"user/admin.html"))) diff --git a/src/web/widget.lisp b/src/web/widget.lisp new file mode 100644 index 0000000..1fdf8d3 --- /dev/null +++ b/src/web/widget.lisp @@ -0,0 +1,71 @@ +#| + +This is probably a complete waste of time, but this is a WIDGET class +that encapsulates doing stuff with web widgets. + +See the [reblocks Quickstart](https://40ants.com/reblocks/quickstart/) for more information on how this is intended to be structured and used. + +There are no guarantees that any of this is sane or useful. + +#+BEGIN_SRC +CLASS ztf_handler DEFINITION +TYPES t_sap_tables TYPE STANDARD TABLE OF dd02t WITH EMPTY KEY. + +DATA path TYPE string. +DATA searched_string TYPE string. +DATA page TYPE i. + +METHODS html_page RETURNING VALUE(html) TYPE string. +METHODS html_shellbar RETURNING VALUE(html) TYPE string. +METHODS html_searchbar RETURNING VALUE(html) TYPE string. +METHODS html_table RETURNING VALUE(html) TYPE string. +METHODS html_table_rows RETURNING VALUE(html) TYPE string. + +METHODS sap_table_getcount RETURNING VALUE(count) TYPE i. +METHODS sap_table_getlist RETURNING VALUE(sap_tables) TYPE t_sap_tables. +#+END_SRC +|# + +(defpackage #:cl-deck-builder2.web.widget + (:use #:cl + #:caveman2) + (:import-from #:cl-deck-builder2.web + #:render-with-env)) + +(in-package #:cl-deck-builder2.web.widget) + +(defclass widget-base (standard-object) + ((tables :accessor widget-tables + :initarg :tables + :initform '()) + ;; Hash table of registered templates. Mapping :PAGE to #P"page.html" + (templates :accessor widget-templates + :initform (make-hash-table))) + (:documentation "The WIDGET-BASE is the base class for other WIDGET objects. We have the minimum info: the environment TABLES and the template PATH.")) + +(defmethod widget-path ((obj widget-base) tag) + (gethash tag (widget-templates obj))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defmethod html-page ((obj widget-base) tag) + (render-with-env + (widget-path obj tag) + (list (widget-tables obj)))) + +(defmethod html-shellbar ((obj widget-bells)) + (render-with-env (widget-search-path obj) + (list (widget-tables obj)))) + +(in-package #:cl-deck-builder2.web) + +(defparameter *w* (make-instance 'cl-deck-builder2.web.widget::widget-bells + :path #P"workbench/tabs.html" + :search-path #P"workbench/test.html")) + +(defroute ("/test" :method :GET) (&key _parsed) + (declare (ignore _parsed)) + (cl-deck-builder2.web.widget::html-page *w*)) + +(defroute ("/test2" :method :GET) (&key _parsed) + (declare (ignore _parsed)) + (cl-deck-builder2.web.widget::html-shellbar *w*)) diff --git a/src/web/ydk.lisp b/src/web/ydk.lisp new file mode 100644 index 0000000..f495d6f --- /dev/null +++ b/src/web/ydk.lisp @@ -0,0 +1,101 @@ +(in-package #:cl-deck-builder2.web) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helper Functions +(defun upload-deck-by-content (name content &optional (sort t)) + "Helper Function. Create a new YDK deck with CREATED-BY set, a new deck name of NAME, with deck CONTENT. Optionally if SORT is T (which it is by default), sort the deck with YDK-SORTED before committing it to the database, with YDK-SYNC." + ;; TODO RATIFY:WITH-PARSED-FORMS here? + (assert (and name content)) + + ;; Insert the Deck + (let ((ydk (make-instance 'ydk :created-by "UPLOAD-DECK-BY-CONTENT" + :content content :name name))) + (unless ydk + (flash-error (_ "Something went wrong. Try again?")) + (redirect "/ydk")) + + (if sort + (setf ydk (ydk-sorted ydk))) + + (let ((deck (ignore-errors + (ydk-sync ydk)))) + (if deck + (redirect (format nil "/decks/~d/view" (mito:object-id deck))) + (progn + (flash-error (_ "Something went wrong. Try again?")) + (redirect "/ydk")))))) + +;; TODO Since we now accept multiple file uploads, we'll also either have to: +;; 1) Allow multiple file names +;; 2) Disallow naming multiple file uploads +;; 3) Something else +(defun process-uploaded-files (files query query-filename &optional (random-filename nil)) + "Helper Function. Process FILES uploaded in the YDK POST route." + (v:info :ydk "PROCESS-UPLOADED-FILES: ~a ~a ~a" files query query-filename) + (if files + (dolist (file files) + (destructuring-bind (content filename content-type) + file + + ;; First - if the user specified a filename, use that as the file name. + (ignore-errors + (ratify:with-parsed-forms + ((:string query-filename)) + (setf filename query-filename))) + + ;; If RANDOM-FILENAME is T, just use a random file key anyway. + (if random-filename + (setf filename + (random-pathname-name + :type (pathname-type filename)))) + + ;; Then - Try to save whatever was uploaded using CREATE-ATTACHMENT-FROM-UPLOAD + (let ((attachment (create-attachment content filename content-type))) + ;; Then - If that was successful, use *that* newly + ;; created ATTACHMENT and pass it to + ;; UPLOAD-DECK-BY-CONTENT which is the old mechanism we + ;; first wrote, that handles everything nicely with + ;; YDK-SYNC and YDK-SORTED abstracting things. + (if attachment + (upload-deck-by-content filename + (uiop:slurp-stream-string + (mito-attachment:get-object attachment))) + (progn + (flash :errors "YDK Upload failed! Try again?") + (redirect "/ydk" 302)))))) + (progn + ;; If QUERY-FILENAME is set, supersede FILENAME with that. + ;; If we got here something went wrong with the FILE + ;; parse. Try to use QUERY before flashing an error. We do + ;; the same thing - enfore :STRING with + ;; RATIFY:WITH-PARSED-FORMS. Maybe this is a good case for + ;; RESTART-CASE? + (handler-case + (ratify:with-parsed-forms + ((:string query) + (:string query-filename)) + (flash :messages "YDK-SUBMIT: using QUERY instead of FILE-QUERY") + (upload-deck-by-content query-filename query)) + (ratify:combined-error (e) + (flash :errors (format nil "YDK Upload failed! Try again?~%~a" e)) + (redirect "/ydk" 302)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; YDK Upload +(defroute ("/ydk" :method :GET) () + "YDK Upload Main Route" + (v:info :ydk "GET /ydk") + + (with-logged-in-user + (render-with-env #P"ydk/index.html" + `(:active "/ydk")))) + + +(defroute ("/ydk" :method :POST) (&key _parsed) + "YDK Upload POST Processing Route" + (v:info :ydk "POST /ydk => ~a" _parsed) + + (with-logged-in-user + (process-uploaded-files (query-param "files" _parsed) + (query-param "query" _parsed) + (query-param "filename" _parsed)))) diff --git a/src/workers/main.lisp b/src/workers/main.lisp new file mode 100644 index 0000000..4bf25a8 --- /dev/null +++ b/src/workers/main.lisp @@ -0,0 +1,13 @@ +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2.workers + (:use :cl) + (:export :background-job)) + +(in-package #:cl-deck-builder2.workers) + +;; Background-Job class for saving metadata into RDBMS +(defclass background-job (psy:worker) ()) + +(defmethod psy:perform ((psy:worker background-job) &rest args) + (format nil "Hello, world!~#[~:; Here are my args: ~a~]~%" args)) diff --git a/static/css/site.css b/static/css/site.css new file mode 100644 index 0000000..0e7e01f --- /dev/null +++ b/static/css/site.css @@ -0,0 +1,133 @@ +@charset "UTF-8"; + +body, html { + height: 100%; +} + +#main { + min-height: 90%; + background-image: url("/img/noise.png"); + background-repeat: repeat; +} + +.footer { + color: white; + background-image: linear-gradient(#2367a9, #050a30); +} + +/* https://www.w3schools.com/howto/howto_css_zoom_hover.asp */ +.zoom { + transition: transform .2s; /* Animation */ + width: auto; + height: auto; +} + +.zoom:hover { + /* (1000% zoom - Note: if the zoom is too large, it will go outside of the viewport) + TranslateX by 50% to prevent overflow */ + transform: scale(1.2); +} + +/* https://htmx.org/attributes/hx-indicator/ */ +.htmx-indicator{ + display:none; + opacity:0; + transition: opacity 500ms ease-in; +} +.htmx-request .htmx-indicator{ + display:inline-block; + opacity:1; +} +.htmx-request.htmx-indicator{ + display:inline-block; + opacity:1; +} + +/*********************************************************************************/ +.htmx-swapping div, tr.htmx-swapping td { + opacity: 0; + transition: opacity 1s ease-out; +} + +/*********************************************************************************/ +/* */ +.main-deck-list { + color: #000; + background-color: #FF8B53; + background-image: url("/img/noise.png"); + background-blend-mode: overlay; +} +.extra-deck-list { + color: #000; + background-color: #BC5A84; + background-image: url("/img/noise.png"); + background-blend-mode: overlay; +} +.side-deck-list { + color: #000; + background-color: #1D9E74; + background-image: url("/img/noise.png"); + background-blend-mode: overlay; +} + +/*********************************************************************************/ +/* background-image: url("/img/noise.png"), linear-gradient(rgba(0,0,0,0.5),rgba(0,0,0,0.5)); */ +.effect {color: #000; background-color: #FF8B53; background-image: url("/img/noise.png"); } +.fusion {color: #000; background-color: #A086B7; background-image: url("/img/noise.png"); } +.link {color: #fff; background-color: #00008B; background-image: url("/img/noise.png"); } +.normal {color: #000; background-color: #FDE68A; background-image: url("/img/noise.png"); } +.ritual {color: #000; background-color: #9DB5CC; background-image: url("/img/noise.png"); } +.skill {color: #fff; background-color: #078cc4; background-image: url("/img/noise.png"); } +.spell {color: #fff; background-color: #1D9E74; background-image: url("/img/noise.png"); } +.synchro {color: #000; background-color: #CCCCCC; background-image: url("/img/noise.png"); } +.token {color: #000; background-color: #C0C0C0; background-image: url("/img/noise.png"); } +.trap {color: #fff; background-color: #BC5A84; background-image: url("/img/noise.png"); } +.xyz {color: #fff; background-color: #000; background-image: url("/img/noise.png"); } + +/*********************************************************************************/ +.effect_pendulum { + color: #000; + background-color: #1d9e74; + background-image: url("/img/noise.png"), linear-gradient(#ff8b53 20%, rgba(0,0,0,0) 100%); + background-blend-mode: normal; +} +.fusion_pendulum { + color: #000; + background-color: #1d9e74; + background-image: url("/img/noise.png"), linear-gradient(#a086b7 20%, rgba(0,0,0,0) 100%); + background-blend-mode: normal; +} +.normal_pendulum { + color: #000; + background-color: #1d9e74; + background-image: url("/img/noise.png"), linear-gradient(#fde68a 20%, rgba(0,0,0,0) 100%); + background-blend-mode: normal; +} +.ritual_pendulum { + color: #000; + background-color: #1d9e74; + background-image: url("/img/noise.png"), linear-gradient(#9db5cc 20%, rgba(0,0,0,0) 100%); + background-blend-mode: normal; +} +.synchro_pendulum { + color: #000; + background-color: #1d9e74; + background-image: url("/img/noise.png"), linear-gradient(#cccccc 20%, rgba(0,0,0,0) 100%); + background-blend-mode: normal; +} +.xyz_pendulum { + color: #fff; + background-color: #1d9e74; + background-image: url("/img/noise.png"), linear-gradient(#000 20%, rgba(0,0,0,0) 100%); + background-blend-mode: normal; +} + +input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + margin: 0; +} + +[data-loading] { + display: none; +} diff --git a/static/css/w3.css b/static/css/w3.css new file mode 100644 index 0000000..fd89e8d --- /dev/null +++ b/static/css/w3.css @@ -0,0 +1,235 @@ +/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */ +html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} +/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ +html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} +article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} +audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} +audio:not([controls]){display:none;height:0}[hidden],template{display:none} +a{background-color:transparent}a:active,a:hover{outline-width:0} +abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} +b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} +small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} +code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} +button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} +button,input{overflow:visible}button,select{text-transform:none} +button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} +button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} +button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} +fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} +legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} +[type=checkbox],[type=radio]{padding:0} +[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} +[type=search]{-webkit-appearance:textfield;outline-offset:-2px} +[type=search]::-webkit-search-decoration{-webkit-appearance:none} +::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} +/* End extract */ +html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} +h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px} +.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace} +h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} +hr{border:0;border-top:1px solid #eee;margin:20px 0} +.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} +.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} +.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} +.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} +.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} +.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} +.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} +.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} +.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} +.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} +.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} +.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} +.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} +.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} +.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} +.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} +.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} +.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} +.w3-dropdown-hover:hover .w3-dropdown-content{display:block} +.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} +.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} +.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} +.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} +.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} +.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} +.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} +.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} +.w3-main,#main{transition:margin-left .4s} +.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} +.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} +.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} +.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} +.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} +.w3-bar .w3-button{white-space:normal} +.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} +.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} +.w3-responsive{display:block;overflow-x:auto} +.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, +.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} +.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} +.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} +.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} +.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} +@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} +.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} +.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} +@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} +.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} +.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} +.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} +.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} +.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} +.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} +.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} +@media (max-width:1205px){.w3-auto{max-width:95%}} +@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} +.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} +.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} +.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} +@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} +@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} +@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} +@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} +.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} +.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} +.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} +.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} +.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} +.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} +.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} +.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} +.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} +.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} +.w3-display-position{position:absolute} +.w3-circle{border-radius:50%} +.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} +.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} +.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} +.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} +.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} +.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} +.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} +.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} +.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} +.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} +.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} +.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} +.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} +.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} +.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} +.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} +.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} +.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} +.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} +.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} +.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} +.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} +.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} +.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} +.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} +.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} +.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} +.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} +.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} +.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} +.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} +.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} +.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} +.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important} +.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important} +.w3-left{float:left!important}.w3-right{float:right!important} +.w3-button:hover{color:#000!important;background-color:#ccc!important} +.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} +.w3-hover-none:hover{box-shadow:none!important} +/* Colors */ +.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} +.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} +.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} +.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} +.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} +.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} +.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} +.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} +.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} +.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} +.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} +.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} +.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} +.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} +.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} +.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} +.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} +.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} +.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} +.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} +.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} +.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} +.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} +.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} +.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} +.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} +.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} +.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} +.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} +.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} +.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} +.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} +.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} +.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} +.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} +.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} +.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} +.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} +.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} +.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} +.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} +.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} +.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} +.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} +.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} +.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} +.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} +.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} +.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} +.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} +.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} +.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} +.w3-text-black,.w3-hover-text-black:hover{color:#000!important} +.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} +.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} +.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} +.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} +.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} +.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} +.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} +.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} +.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} +.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} +.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} +.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} +.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} +.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} +.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} +.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} +.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} +.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} +.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} +.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} +.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} +.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} +.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} +.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} +.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} +.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} +.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} +.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} +.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} +.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} +.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} \ No newline at end of file diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000..a1f00f7 Binary files /dev/null and b/static/favicon.png differ diff --git a/static/img/attribute/Continuous.svg b/static/img/attribute/Continuous.svg new file mode 100644 index 0000000..967def8 --- /dev/null +++ b/static/img/attribute/Continuous.svg @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Created by Falzar FZ + + + + Created by Falzar FZ + + + + + + + + diff --git a/static/img/attribute/Counter.svg b/static/img/attribute/Counter.svg new file mode 100644 index 0000000..5519ff7 --- /dev/null +++ b/static/img/attribute/Counter.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + diff --git a/static/img/attribute/DARK.svg b/static/img/attribute/DARK.svg new file mode 100644 index 0000000..c30722e --- /dev/null +++ b/static/img/attribute/DARK.svg @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/DIVINE.svg b/static/img/attribute/DIVINE.svg new file mode 100644 index 0000000..dadf9c5 --- /dev/null +++ b/static/img/attribute/DIVINE.svg @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/EARTH.svg b/static/img/attribute/EARTH.svg new file mode 100644 index 0000000..a71e67f --- /dev/null +++ b/static/img/attribute/EARTH.svg @@ -0,0 +1,607 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/Equip.svg b/static/img/attribute/Equip.svg new file mode 100644 index 0000000..c7afb59 --- /dev/null +++ b/static/img/attribute/Equip.svg @@ -0,0 +1,455 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Created by Falzar FZ + + + + + + + + + + diff --git a/static/img/attribute/FIRE.svg b/static/img/attribute/FIRE.svg new file mode 100644 index 0000000..2a87c01 --- /dev/null +++ b/static/img/attribute/FIRE.svg @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/Field.svg b/static/img/attribute/Field.svg new file mode 100644 index 0000000..ed99803 --- /dev/null +++ b/static/img/attribute/Field.svg @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Created by Falzar FZ + + + + + + + + diff --git a/static/img/attribute/LIGHT.svg b/static/img/attribute/LIGHT.svg new file mode 100644 index 0000000..1f6b54e --- /dev/null +++ b/static/img/attribute/LIGHT.svg @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/Normal-N.svg b/static/img/attribute/Normal-N.svg new file mode 100644 index 0000000..c03fa8e --- /dev/null +++ b/static/img/attribute/Normal-N.svg @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + diff --git a/static/img/attribute/Normal.svg b/static/img/attribute/Normal.svg new file mode 100644 index 0000000..2ad94ad --- /dev/null +++ b/static/img/attribute/Normal.svg @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + diff --git a/static/img/attribute/Quick-Play.svg b/static/img/attribute/Quick-Play.svg new file mode 100644 index 0000000..703c7f9 --- /dev/null +++ b/static/img/attribute/Quick-Play.svg @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Created by Falzar FZ + + + + + + + + + diff --git a/static/img/attribute/Ritual.svg b/static/img/attribute/Ritual.svg new file mode 100644 index 0000000..97909dd --- /dev/null +++ b/static/img/attribute/Ritual.svg @@ -0,0 +1,360 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Created by Falzar FZ + + + + + + + diff --git a/static/img/attribute/SPELL.svg b/static/img/attribute/SPELL.svg new file mode 100644 index 0000000..b62fe6b --- /dev/null +++ b/static/img/attribute/SPELL.svg @@ -0,0 +1,785 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/TRAP.svg b/static/img/attribute/TRAP.svg new file mode 100644 index 0000000..90428e2 --- /dev/null +++ b/static/img/attribute/TRAP.svg @@ -0,0 +1,701 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/WATER.svg b/static/img/attribute/WATER.svg new file mode 100644 index 0000000..f9971d5 --- /dev/null +++ b/static/img/attribute/WATER.svg @@ -0,0 +1,816 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/attribute/WIND.svg b/static/img/attribute/WIND.svg new file mode 100644 index 0000000..2c7ea4e --- /dev/null +++ b/static/img/attribute/WIND.svg @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Created by Falzar FZ + + + + + + + + + + + + + diff --git a/static/img/file-icons-classic/3g2.svg b/static/img/file-icons-classic/3g2.svg new file mode 100644 index 0000000..5e4bc6e --- /dev/null +++ b/static/img/file-icons-classic/3g2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/3ga.svg b/static/img/file-icons-classic/3ga.svg new file mode 100644 index 0000000..a5390d7 --- /dev/null +++ b/static/img/file-icons-classic/3ga.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/3gp.svg b/static/img/file-icons-classic/3gp.svg new file mode 100644 index 0000000..81c39c1 --- /dev/null +++ b/static/img/file-icons-classic/3gp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/7z.svg b/static/img/file-icons-classic/7z.svg new file mode 100644 index 0000000..76f519a --- /dev/null +++ b/static/img/file-icons-classic/7z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aa.svg b/static/img/file-icons-classic/aa.svg new file mode 100644 index 0000000..5d02cef --- /dev/null +++ b/static/img/file-icons-classic/aa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aac.svg b/static/img/file-icons-classic/aac.svg new file mode 100644 index 0000000..d9a4b8b --- /dev/null +++ b/static/img/file-icons-classic/aac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ac.svg b/static/img/file-icons-classic/ac.svg new file mode 100644 index 0000000..808eaf3 --- /dev/null +++ b/static/img/file-icons-classic/ac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/accdb.svg b/static/img/file-icons-classic/accdb.svg new file mode 100644 index 0000000..048fbe5 --- /dev/null +++ b/static/img/file-icons-classic/accdb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/accdt.svg b/static/img/file-icons-classic/accdt.svg new file mode 100644 index 0000000..b11abe6 --- /dev/null +++ b/static/img/file-icons-classic/accdt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ace.svg b/static/img/file-icons-classic/ace.svg new file mode 100644 index 0000000..7f21f0b --- /dev/null +++ b/static/img/file-icons-classic/ace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/adn.svg b/static/img/file-icons-classic/adn.svg new file mode 100644 index 0000000..4ec6387 --- /dev/null +++ b/static/img/file-icons-classic/adn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ai.svg b/static/img/file-icons-classic/ai.svg new file mode 100644 index 0000000..3720c35 --- /dev/null +++ b/static/img/file-icons-classic/ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aif.svg b/static/img/file-icons-classic/aif.svg new file mode 100644 index 0000000..dcf73f4 --- /dev/null +++ b/static/img/file-icons-classic/aif.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aifc.svg b/static/img/file-icons-classic/aifc.svg new file mode 100644 index 0000000..910866f --- /dev/null +++ b/static/img/file-icons-classic/aifc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aiff.svg b/static/img/file-icons-classic/aiff.svg new file mode 100644 index 0000000..79d3263 --- /dev/null +++ b/static/img/file-icons-classic/aiff.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ait.svg b/static/img/file-icons-classic/ait.svg new file mode 100644 index 0000000..09db707 --- /dev/null +++ b/static/img/file-icons-classic/ait.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/amr.svg b/static/img/file-icons-classic/amr.svg new file mode 100644 index 0000000..771c5d9 --- /dev/null +++ b/static/img/file-icons-classic/amr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ani.svg b/static/img/file-icons-classic/ani.svg new file mode 100644 index 0000000..b494ce6 --- /dev/null +++ b/static/img/file-icons-classic/ani.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/apk.svg b/static/img/file-icons-classic/apk.svg new file mode 100644 index 0000000..88ef63b --- /dev/null +++ b/static/img/file-icons-classic/apk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/app.svg b/static/img/file-icons-classic/app.svg new file mode 100644 index 0000000..d4fbb83 --- /dev/null +++ b/static/img/file-icons-classic/app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/applescript.svg b/static/img/file-icons-classic/applescript.svg new file mode 100644 index 0000000..aeb4a94 --- /dev/null +++ b/static/img/file-icons-classic/applescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asax.svg b/static/img/file-icons-classic/asax.svg new file mode 100644 index 0000000..6b502a6 --- /dev/null +++ b/static/img/file-icons-classic/asax.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asc.svg b/static/img/file-icons-classic/asc.svg new file mode 100644 index 0000000..7bebc88 --- /dev/null +++ b/static/img/file-icons-classic/asc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ascx.svg b/static/img/file-icons-classic/ascx.svg new file mode 100644 index 0000000..80e5399 --- /dev/null +++ b/static/img/file-icons-classic/ascx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asf.svg b/static/img/file-icons-classic/asf.svg new file mode 100644 index 0000000..4000fd8 --- /dev/null +++ b/static/img/file-icons-classic/asf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ash.svg b/static/img/file-icons-classic/ash.svg new file mode 100644 index 0000000..d7ebb97 --- /dev/null +++ b/static/img/file-icons-classic/ash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ashx.svg b/static/img/file-icons-classic/ashx.svg new file mode 100644 index 0000000..5a16bc4 --- /dev/null +++ b/static/img/file-icons-classic/ashx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asm.svg b/static/img/file-icons-classic/asm.svg new file mode 100644 index 0000000..1ab99da --- /dev/null +++ b/static/img/file-icons-classic/asm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asmx.svg b/static/img/file-icons-classic/asmx.svg new file mode 100644 index 0000000..4c25036 --- /dev/null +++ b/static/img/file-icons-classic/asmx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asp.svg b/static/img/file-icons-classic/asp.svg new file mode 100644 index 0000000..e1481df --- /dev/null +++ b/static/img/file-icons-classic/asp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aspx.svg b/static/img/file-icons-classic/aspx.svg new file mode 100644 index 0000000..f110284 --- /dev/null +++ b/static/img/file-icons-classic/aspx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/asx.svg b/static/img/file-icons-classic/asx.svg new file mode 100644 index 0000000..45b8947 --- /dev/null +++ b/static/img/file-icons-classic/asx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/au.svg b/static/img/file-icons-classic/au.svg new file mode 100644 index 0000000..3929f29 --- /dev/null +++ b/static/img/file-icons-classic/au.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aup.svg b/static/img/file-icons-classic/aup.svg new file mode 100644 index 0000000..0cfa832 --- /dev/null +++ b/static/img/file-icons-classic/aup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/avi.svg b/static/img/file-icons-classic/avi.svg new file mode 100644 index 0000000..3616d30 --- /dev/null +++ b/static/img/file-icons-classic/avi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/axd.svg b/static/img/file-icons-classic/axd.svg new file mode 100644 index 0000000..73be59c --- /dev/null +++ b/static/img/file-icons-classic/axd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/aze.svg b/static/img/file-icons-classic/aze.svg new file mode 100644 index 0000000..1aa3912 --- /dev/null +++ b/static/img/file-icons-classic/aze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bak.svg b/static/img/file-icons-classic/bak.svg new file mode 100644 index 0000000..93794cb --- /dev/null +++ b/static/img/file-icons-classic/bak.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bash.svg b/static/img/file-icons-classic/bash.svg new file mode 100644 index 0000000..62df364 --- /dev/null +++ b/static/img/file-icons-classic/bash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bat.svg b/static/img/file-icons-classic/bat.svg new file mode 100644 index 0000000..310e22b --- /dev/null +++ b/static/img/file-icons-classic/bat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bin.svg b/static/img/file-icons-classic/bin.svg new file mode 100644 index 0000000..e2abd50 --- /dev/null +++ b/static/img/file-icons-classic/bin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/blank.svg b/static/img/file-icons-classic/blank.svg new file mode 100644 index 0000000..f28402e --- /dev/null +++ b/static/img/file-icons-classic/blank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bmp.svg b/static/img/file-icons-classic/bmp.svg new file mode 100644 index 0000000..1c80620 --- /dev/null +++ b/static/img/file-icons-classic/bmp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bowerrc.svg b/static/img/file-icons-classic/bowerrc.svg new file mode 100644 index 0000000..a4ddbd6 --- /dev/null +++ b/static/img/file-icons-classic/bowerrc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bpg.svg b/static/img/file-icons-classic/bpg.svg new file mode 100644 index 0000000..99fa884 --- /dev/null +++ b/static/img/file-icons-classic/bpg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/browser.svg b/static/img/file-icons-classic/browser.svg new file mode 100644 index 0000000..79fa571 --- /dev/null +++ b/static/img/file-icons-classic/browser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bz2.svg b/static/img/file-icons-classic/bz2.svg new file mode 100644 index 0000000..2c0f0b0 --- /dev/null +++ b/static/img/file-icons-classic/bz2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/bzempty.svg b/static/img/file-icons-classic/bzempty.svg new file mode 100644 index 0000000..237cb71 --- /dev/null +++ b/static/img/file-icons-classic/bzempty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/c.svg b/static/img/file-icons-classic/c.svg new file mode 100644 index 0000000..229cb40 --- /dev/null +++ b/static/img/file-icons-classic/c.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cab.svg b/static/img/file-icons-classic/cab.svg new file mode 100644 index 0000000..f58a984 --- /dev/null +++ b/static/img/file-icons-classic/cab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cad.svg b/static/img/file-icons-classic/cad.svg new file mode 100644 index 0000000..bfd9531 --- /dev/null +++ b/static/img/file-icons-classic/cad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/caf.svg b/static/img/file-icons-classic/caf.svg new file mode 100644 index 0000000..e55477f --- /dev/null +++ b/static/img/file-icons-classic/caf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cal.svg b/static/img/file-icons-classic/cal.svg new file mode 100644 index 0000000..4134281 --- /dev/null +++ b/static/img/file-icons-classic/cal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/catalog.json b/static/img/file-icons-classic/catalog.json new file mode 100644 index 0000000..357913b --- /dev/null +++ b/static/img/file-icons-classic/catalog.json @@ -0,0 +1 @@ +["3g2","3ga","3gp","7z","aa","aac","ac","accdb","accdt","ace","adn","ai","aif","aifc","aiff","ait","amr","ani","apk","app","applescript","asax","asc","ascx","asf","ash","ashx","asm","asmx","asp","aspx","asx","au","aup","avi","axd","aze","bak","bash","bat","bin","blank","bmp","bowerrc","bpg","browser","bz2","bzempty","c","cab","cad","caf","cal","cd","cdda","cer","cfg","cfm","cfml","cgi","chm","class","cmd","code-workspace","codekit","coffee","coffeelintignore","com","compile","conf","config","cpp","cptx","cr2","crdownload","crt","crypt","cs","csh","cson","csproj","css","csv","cue","cur","dart","dat","data","db","dbf","deb","default","dgn","dist","diz","dll","dmg","dng","doc","docb","docm","docx","dot","dotm","dotx","download","dpj","ds_store","dsn","dtd","dwg","dxf","editorconfig","el","elf","eml","enc","eot","eps","epub","eslintignore","exe","f4v","fax","fb2","fla","flac","flv","fnt","folder","fon","gadget","gdp","gem","gif","gitattributes","gitignore","go","gpg","gpl","gradle","gz","h","handlebars","hbs","heic","hlp","hs","hsl","htm","html","ibooks","icns","ico","ics","idx","iff","ifo","image","img","iml","in","inc","indd","inf","info","ini","inv","iso","j2","jar","java","jpe","jpeg","jpg","js","json","jsp","jsx","key","kf8","kmk","ksh","kt","kts","kup","less","lex","licx","lisp","lit","lnk","lock","log","lua","m","m2v","m3u","m3u8","m4","m4a","m4r","m4v","map","master","mc","md","mdb","mdf","me","mi","mid","midi","mk","mkv","mm","mng","mo","mobi","mod","mov","mp2","mp3","mp4","mpa","mpd","mpe","mpeg","mpg","mpga","mpp","mpt","msg","msi","msu","nef","nes","nfo","nix","npmignore","ocx","odb","ods","odt","ogg","ogv","ost","otf","ott","ova","ovf","p12","p7b","pages","part","pcd","pdb","pdf","pem","pfx","pgp","ph","phar","php","pid","pkg","pl","plist","pm","png","po","pom","pot","potx","pps","ppsx","ppt","pptm","pptx","prop","ps","ps1","psd","psp","pst","pub","py","pyc","qt","ra","ram","rar","raw","rb","rdf","rdl","reg","resx","retry","rm","rom","rpm","rpt","rsa","rss","rst","rtf","ru","rub","sass","scss","sdf","sed","sh","sit","sitemap","skin","sldm","sldx","sln","sol","sphinx","sql","sqlite","step","stl","svg","swd","swf","swift","swp","sys","tar","tax","tcsh","tex","tfignore","tga","tgz","tif","tiff","tmp","tmx","torrent","tpl","ts","tsv","ttf","twig","txt","udf","vb","vbproj","vbs","vcd","vcf","vcs","vdi","vdx","vmdk","vob","vox","vscodeignore","vsd","vss","vst","vsx","vtx","war","wav","wbk","webinfo","webm","webp","wma","wmf","wmv","woff","woff2","wps","wsf","xaml","xcf","xfl","xlm","xls","xlsm","xlsx","xlt","xltm","xltx","xml","xpi","xps","xrb","xsd","xsl","xspf","xz","yaml","yml","z","zip","zsh"] \ No newline at end of file diff --git a/static/img/file-icons-classic/cd.svg b/static/img/file-icons-classic/cd.svg new file mode 100644 index 0000000..27e0e2f --- /dev/null +++ b/static/img/file-icons-classic/cd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cdda.svg b/static/img/file-icons-classic/cdda.svg new file mode 100644 index 0000000..b416484 --- /dev/null +++ b/static/img/file-icons-classic/cdda.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cer.svg b/static/img/file-icons-classic/cer.svg new file mode 100644 index 0000000..a96586c --- /dev/null +++ b/static/img/file-icons-classic/cer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cfg.svg b/static/img/file-icons-classic/cfg.svg new file mode 100644 index 0000000..5889d5d --- /dev/null +++ b/static/img/file-icons-classic/cfg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cfm.svg b/static/img/file-icons-classic/cfm.svg new file mode 100644 index 0000000..993e0ab --- /dev/null +++ b/static/img/file-icons-classic/cfm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cfml.svg b/static/img/file-icons-classic/cfml.svg new file mode 100644 index 0000000..c07fa40 --- /dev/null +++ b/static/img/file-icons-classic/cfml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cgi.svg b/static/img/file-icons-classic/cgi.svg new file mode 100644 index 0000000..0ba2e2b --- /dev/null +++ b/static/img/file-icons-classic/cgi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/chm.svg b/static/img/file-icons-classic/chm.svg new file mode 100644 index 0000000..a96a118 --- /dev/null +++ b/static/img/file-icons-classic/chm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/class.svg b/static/img/file-icons-classic/class.svg new file mode 100644 index 0000000..ce2d4cd --- /dev/null +++ b/static/img/file-icons-classic/class.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cmd.svg b/static/img/file-icons-classic/cmd.svg new file mode 100644 index 0000000..a666c36 --- /dev/null +++ b/static/img/file-icons-classic/cmd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/code-workspace.svg b/static/img/file-icons-classic/code-workspace.svg new file mode 100644 index 0000000..a3e0e35 --- /dev/null +++ b/static/img/file-icons-classic/code-workspace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/codekit.svg b/static/img/file-icons-classic/codekit.svg new file mode 100644 index 0000000..ccba98e --- /dev/null +++ b/static/img/file-icons-classic/codekit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/coffee.svg b/static/img/file-icons-classic/coffee.svg new file mode 100644 index 0000000..fa087a5 --- /dev/null +++ b/static/img/file-icons-classic/coffee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/coffeelintignore.svg b/static/img/file-icons-classic/coffeelintignore.svg new file mode 100644 index 0000000..2dd1c39 --- /dev/null +++ b/static/img/file-icons-classic/coffeelintignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/com.svg b/static/img/file-icons-classic/com.svg new file mode 100644 index 0000000..aeca78c --- /dev/null +++ b/static/img/file-icons-classic/com.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/compile.svg b/static/img/file-icons-classic/compile.svg new file mode 100644 index 0000000..3a51e60 --- /dev/null +++ b/static/img/file-icons-classic/compile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/conf.svg b/static/img/file-icons-classic/conf.svg new file mode 100644 index 0000000..a1362aa --- /dev/null +++ b/static/img/file-icons-classic/conf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/config.svg b/static/img/file-icons-classic/config.svg new file mode 100644 index 0000000..b95e77c --- /dev/null +++ b/static/img/file-icons-classic/config.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cpp.svg b/static/img/file-icons-classic/cpp.svg new file mode 100644 index 0000000..f827eff --- /dev/null +++ b/static/img/file-icons-classic/cpp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cptx.svg b/static/img/file-icons-classic/cptx.svg new file mode 100644 index 0000000..0a07c09 --- /dev/null +++ b/static/img/file-icons-classic/cptx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cr2.svg b/static/img/file-icons-classic/cr2.svg new file mode 100644 index 0000000..0f9aede --- /dev/null +++ b/static/img/file-icons-classic/cr2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/crdownload.svg b/static/img/file-icons-classic/crdownload.svg new file mode 100644 index 0000000..0b12bfb --- /dev/null +++ b/static/img/file-icons-classic/crdownload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/crt.svg b/static/img/file-icons-classic/crt.svg new file mode 100644 index 0000000..196f65c --- /dev/null +++ b/static/img/file-icons-classic/crt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/crypt.svg b/static/img/file-icons-classic/crypt.svg new file mode 100644 index 0000000..5263b6e --- /dev/null +++ b/static/img/file-icons-classic/crypt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cs.svg b/static/img/file-icons-classic/cs.svg new file mode 100644 index 0000000..97490c4 --- /dev/null +++ b/static/img/file-icons-classic/cs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/csh.svg b/static/img/file-icons-classic/csh.svg new file mode 100644 index 0000000..e5404cb --- /dev/null +++ b/static/img/file-icons-classic/csh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cson.svg b/static/img/file-icons-classic/cson.svg new file mode 100644 index 0000000..03f30b8 --- /dev/null +++ b/static/img/file-icons-classic/cson.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/csproj.svg b/static/img/file-icons-classic/csproj.svg new file mode 100644 index 0000000..46ab7fe --- /dev/null +++ b/static/img/file-icons-classic/csproj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/css.svg b/static/img/file-icons-classic/css.svg new file mode 100644 index 0000000..76c8666 --- /dev/null +++ b/static/img/file-icons-classic/css.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/csv.svg b/static/img/file-icons-classic/csv.svg new file mode 100644 index 0000000..1a54426 --- /dev/null +++ b/static/img/file-icons-classic/csv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cue.svg b/static/img/file-icons-classic/cue.svg new file mode 100644 index 0000000..634320c --- /dev/null +++ b/static/img/file-icons-classic/cue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/cur.svg b/static/img/file-icons-classic/cur.svg new file mode 100644 index 0000000..a091449 --- /dev/null +++ b/static/img/file-icons-classic/cur.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dart.svg b/static/img/file-icons-classic/dart.svg new file mode 100644 index 0000000..7c2a6d9 --- /dev/null +++ b/static/img/file-icons-classic/dart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dat.svg b/static/img/file-icons-classic/dat.svg new file mode 100644 index 0000000..0351bf8 --- /dev/null +++ b/static/img/file-icons-classic/dat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/data.svg b/static/img/file-icons-classic/data.svg new file mode 100644 index 0000000..a455a99 --- /dev/null +++ b/static/img/file-icons-classic/data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/db.svg b/static/img/file-icons-classic/db.svg new file mode 100644 index 0000000..6996d94 --- /dev/null +++ b/static/img/file-icons-classic/db.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dbf.svg b/static/img/file-icons-classic/dbf.svg new file mode 100644 index 0000000..6d89194 --- /dev/null +++ b/static/img/file-icons-classic/dbf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/deb.svg b/static/img/file-icons-classic/deb.svg new file mode 100644 index 0000000..50a1910 --- /dev/null +++ b/static/img/file-icons-classic/deb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/default.svg b/static/img/file-icons-classic/default.svg new file mode 100644 index 0000000..b19f8fb --- /dev/null +++ b/static/img/file-icons-classic/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dgn.svg b/static/img/file-icons-classic/dgn.svg new file mode 100644 index 0000000..ba44e93 --- /dev/null +++ b/static/img/file-icons-classic/dgn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dist.svg b/static/img/file-icons-classic/dist.svg new file mode 100644 index 0000000..2cff977 --- /dev/null +++ b/static/img/file-icons-classic/dist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/diz.svg b/static/img/file-icons-classic/diz.svg new file mode 100644 index 0000000..8b9b5e6 --- /dev/null +++ b/static/img/file-icons-classic/diz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dll.svg b/static/img/file-icons-classic/dll.svg new file mode 100644 index 0000000..1f4c8a8 --- /dev/null +++ b/static/img/file-icons-classic/dll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dmg.svg b/static/img/file-icons-classic/dmg.svg new file mode 100644 index 0000000..aa7af78 --- /dev/null +++ b/static/img/file-icons-classic/dmg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dng.svg b/static/img/file-icons-classic/dng.svg new file mode 100644 index 0000000..96c26c4 --- /dev/null +++ b/static/img/file-icons-classic/dng.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/doc.svg b/static/img/file-icons-classic/doc.svg new file mode 100644 index 0000000..78a9ee7 --- /dev/null +++ b/static/img/file-icons-classic/doc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/docb.svg b/static/img/file-icons-classic/docb.svg new file mode 100644 index 0000000..c97a3e7 --- /dev/null +++ b/static/img/file-icons-classic/docb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/docm.svg b/static/img/file-icons-classic/docm.svg new file mode 100644 index 0000000..fa38edb --- /dev/null +++ b/static/img/file-icons-classic/docm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/docx.svg b/static/img/file-icons-classic/docx.svg new file mode 100644 index 0000000..d3529a1 --- /dev/null +++ b/static/img/file-icons-classic/docx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dot.svg b/static/img/file-icons-classic/dot.svg new file mode 100644 index 0000000..e131871 --- /dev/null +++ b/static/img/file-icons-classic/dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dotm.svg b/static/img/file-icons-classic/dotm.svg new file mode 100644 index 0000000..8c08479 --- /dev/null +++ b/static/img/file-icons-classic/dotm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dotx.svg b/static/img/file-icons-classic/dotx.svg new file mode 100644 index 0000000..a951ccb --- /dev/null +++ b/static/img/file-icons-classic/dotx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/download.svg b/static/img/file-icons-classic/download.svg new file mode 100644 index 0000000..c670c80 --- /dev/null +++ b/static/img/file-icons-classic/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dpj.svg b/static/img/file-icons-classic/dpj.svg new file mode 100644 index 0000000..142328c --- /dev/null +++ b/static/img/file-icons-classic/dpj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ds_store.svg b/static/img/file-icons-classic/ds_store.svg new file mode 100644 index 0000000..b860f97 --- /dev/null +++ b/static/img/file-icons-classic/ds_store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dsn.svg b/static/img/file-icons-classic/dsn.svg new file mode 100644 index 0000000..3c70360 --- /dev/null +++ b/static/img/file-icons-classic/dsn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dtd.svg b/static/img/file-icons-classic/dtd.svg new file mode 100644 index 0000000..64f56f8 --- /dev/null +++ b/static/img/file-icons-classic/dtd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dwg.svg b/static/img/file-icons-classic/dwg.svg new file mode 100644 index 0000000..f57d6f9 --- /dev/null +++ b/static/img/file-icons-classic/dwg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/dxf.svg b/static/img/file-icons-classic/dxf.svg new file mode 100644 index 0000000..e8853df --- /dev/null +++ b/static/img/file-icons-classic/dxf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/editorconfig.svg b/static/img/file-icons-classic/editorconfig.svg new file mode 100644 index 0000000..765d647 --- /dev/null +++ b/static/img/file-icons-classic/editorconfig.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/el.svg b/static/img/file-icons-classic/el.svg new file mode 100644 index 0000000..8319a05 --- /dev/null +++ b/static/img/file-icons-classic/el.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/elf.svg b/static/img/file-icons-classic/elf.svg new file mode 100644 index 0000000..27fdc42 --- /dev/null +++ b/static/img/file-icons-classic/elf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/eml.svg b/static/img/file-icons-classic/eml.svg new file mode 100644 index 0000000..5702b29 --- /dev/null +++ b/static/img/file-icons-classic/eml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/enc.svg b/static/img/file-icons-classic/enc.svg new file mode 100644 index 0000000..7ded266 --- /dev/null +++ b/static/img/file-icons-classic/enc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/eot.svg b/static/img/file-icons-classic/eot.svg new file mode 100644 index 0000000..8a36828 --- /dev/null +++ b/static/img/file-icons-classic/eot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/eps.svg b/static/img/file-icons-classic/eps.svg new file mode 100644 index 0000000..9d03fe9 --- /dev/null +++ b/static/img/file-icons-classic/eps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/epub.svg b/static/img/file-icons-classic/epub.svg new file mode 100644 index 0000000..9bcf867 --- /dev/null +++ b/static/img/file-icons-classic/epub.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/eslintignore.svg b/static/img/file-icons-classic/eslintignore.svg new file mode 100644 index 0000000..2dd1c39 --- /dev/null +++ b/static/img/file-icons-classic/eslintignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/exe.svg b/static/img/file-icons-classic/exe.svg new file mode 100644 index 0000000..7f57732 --- /dev/null +++ b/static/img/file-icons-classic/exe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/f4v.svg b/static/img/file-icons-classic/f4v.svg new file mode 100644 index 0000000..c418480 --- /dev/null +++ b/static/img/file-icons-classic/f4v.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/fax.svg b/static/img/file-icons-classic/fax.svg new file mode 100644 index 0000000..b6b9e97 --- /dev/null +++ b/static/img/file-icons-classic/fax.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/fb2.svg b/static/img/file-icons-classic/fb2.svg new file mode 100644 index 0000000..97600ef --- /dev/null +++ b/static/img/file-icons-classic/fb2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/fla.svg b/static/img/file-icons-classic/fla.svg new file mode 100644 index 0000000..eb7fb1e --- /dev/null +++ b/static/img/file-icons-classic/fla.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/flac.svg b/static/img/file-icons-classic/flac.svg new file mode 100644 index 0000000..6b92fd7 --- /dev/null +++ b/static/img/file-icons-classic/flac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/flv.svg b/static/img/file-icons-classic/flv.svg new file mode 100644 index 0000000..6145082 --- /dev/null +++ b/static/img/file-icons-classic/flv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/fnt.svg b/static/img/file-icons-classic/fnt.svg new file mode 100644 index 0000000..d3880cf --- /dev/null +++ b/static/img/file-icons-classic/fnt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/folder.svg b/static/img/file-icons-classic/folder.svg new file mode 100644 index 0000000..f5969c3 --- /dev/null +++ b/static/img/file-icons-classic/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/fon.svg b/static/img/file-icons-classic/fon.svg new file mode 100644 index 0000000..ce380f6 --- /dev/null +++ b/static/img/file-icons-classic/fon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gadget.svg b/static/img/file-icons-classic/gadget.svg new file mode 100644 index 0000000..176bcc0 --- /dev/null +++ b/static/img/file-icons-classic/gadget.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gdp.svg b/static/img/file-icons-classic/gdp.svg new file mode 100644 index 0000000..7fb05c6 --- /dev/null +++ b/static/img/file-icons-classic/gdp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gem.svg b/static/img/file-icons-classic/gem.svg new file mode 100644 index 0000000..212b500 --- /dev/null +++ b/static/img/file-icons-classic/gem.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gif.svg b/static/img/file-icons-classic/gif.svg new file mode 100644 index 0000000..5c5024b --- /dev/null +++ b/static/img/file-icons-classic/gif.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gitattributes.svg b/static/img/file-icons-classic/gitattributes.svg new file mode 100644 index 0000000..7e0aba6 --- /dev/null +++ b/static/img/file-icons-classic/gitattributes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gitignore.svg b/static/img/file-icons-classic/gitignore.svg new file mode 100644 index 0000000..2dd1c39 --- /dev/null +++ b/static/img/file-icons-classic/gitignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/go.svg b/static/img/file-icons-classic/go.svg new file mode 100644 index 0000000..488f842 --- /dev/null +++ b/static/img/file-icons-classic/go.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gpg.svg b/static/img/file-icons-classic/gpg.svg new file mode 100644 index 0000000..c9a4410 --- /dev/null +++ b/static/img/file-icons-classic/gpg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gpl.svg b/static/img/file-icons-classic/gpl.svg new file mode 100644 index 0000000..fa4bd4a --- /dev/null +++ b/static/img/file-icons-classic/gpl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gradle.svg b/static/img/file-icons-classic/gradle.svg new file mode 100644 index 0000000..d2ba14f --- /dev/null +++ b/static/img/file-icons-classic/gradle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/gz.svg b/static/img/file-icons-classic/gz.svg new file mode 100644 index 0000000..a9eeb07 --- /dev/null +++ b/static/img/file-icons-classic/gz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/h.svg b/static/img/file-icons-classic/h.svg new file mode 100644 index 0000000..af7f7ce --- /dev/null +++ b/static/img/file-icons-classic/h.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/handlebars.svg b/static/img/file-icons-classic/handlebars.svg new file mode 100644 index 0000000..8c32682 --- /dev/null +++ b/static/img/file-icons-classic/handlebars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/hbs.svg b/static/img/file-icons-classic/hbs.svg new file mode 100644 index 0000000..ef3893d --- /dev/null +++ b/static/img/file-icons-classic/hbs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/heic.svg b/static/img/file-icons-classic/heic.svg new file mode 100644 index 0000000..1c6c6d7 --- /dev/null +++ b/static/img/file-icons-classic/heic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/hlp.svg b/static/img/file-icons-classic/hlp.svg new file mode 100644 index 0000000..afac1e6 --- /dev/null +++ b/static/img/file-icons-classic/hlp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/hs.svg b/static/img/file-icons-classic/hs.svg new file mode 100644 index 0000000..4885111 --- /dev/null +++ b/static/img/file-icons-classic/hs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/hsl.svg b/static/img/file-icons-classic/hsl.svg new file mode 100644 index 0000000..a1c0b21 --- /dev/null +++ b/static/img/file-icons-classic/hsl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/htm.svg b/static/img/file-icons-classic/htm.svg new file mode 100644 index 0000000..285fa02 --- /dev/null +++ b/static/img/file-icons-classic/htm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/html.svg b/static/img/file-icons-classic/html.svg new file mode 100644 index 0000000..949d82f --- /dev/null +++ b/static/img/file-icons-classic/html.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ibooks.svg b/static/img/file-icons-classic/ibooks.svg new file mode 100644 index 0000000..bfa5adf --- /dev/null +++ b/static/img/file-icons-classic/ibooks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/icns.svg b/static/img/file-icons-classic/icns.svg new file mode 100644 index 0000000..48fd4f6 --- /dev/null +++ b/static/img/file-icons-classic/icns.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ico.svg b/static/img/file-icons-classic/ico.svg new file mode 100644 index 0000000..9f7f0e2 --- /dev/null +++ b/static/img/file-icons-classic/ico.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ics.svg b/static/img/file-icons-classic/ics.svg new file mode 100644 index 0000000..7a370b5 --- /dev/null +++ b/static/img/file-icons-classic/ics.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/idx.svg b/static/img/file-icons-classic/idx.svg new file mode 100644 index 0000000..847f80b --- /dev/null +++ b/static/img/file-icons-classic/idx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/iff.svg b/static/img/file-icons-classic/iff.svg new file mode 100644 index 0000000..b8fdda2 --- /dev/null +++ b/static/img/file-icons-classic/iff.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ifo.svg b/static/img/file-icons-classic/ifo.svg new file mode 100644 index 0000000..3838258 --- /dev/null +++ b/static/img/file-icons-classic/ifo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/image.svg b/static/img/file-icons-classic/image.svg new file mode 100644 index 0000000..5072e2e --- /dev/null +++ b/static/img/file-icons-classic/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/img.svg b/static/img/file-icons-classic/img.svg new file mode 100644 index 0000000..3b963bb --- /dev/null +++ b/static/img/file-icons-classic/img.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/iml.svg b/static/img/file-icons-classic/iml.svg new file mode 100644 index 0000000..daeb5cf --- /dev/null +++ b/static/img/file-icons-classic/iml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/in.svg b/static/img/file-icons-classic/in.svg new file mode 100644 index 0000000..ae3fc0a --- /dev/null +++ b/static/img/file-icons-classic/in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/inc.svg b/static/img/file-icons-classic/inc.svg new file mode 100644 index 0000000..01ebb4b --- /dev/null +++ b/static/img/file-icons-classic/inc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/indd.svg b/static/img/file-icons-classic/indd.svg new file mode 100644 index 0000000..4590539 --- /dev/null +++ b/static/img/file-icons-classic/indd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/inf.svg b/static/img/file-icons-classic/inf.svg new file mode 100644 index 0000000..323251e --- /dev/null +++ b/static/img/file-icons-classic/inf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/info.svg b/static/img/file-icons-classic/info.svg new file mode 100644 index 0000000..51d1d22 --- /dev/null +++ b/static/img/file-icons-classic/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ini.svg b/static/img/file-icons-classic/ini.svg new file mode 100644 index 0000000..0eeadeb --- /dev/null +++ b/static/img/file-icons-classic/ini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/inv.svg b/static/img/file-icons-classic/inv.svg new file mode 100644 index 0000000..c47e58c --- /dev/null +++ b/static/img/file-icons-classic/inv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/iso.svg b/static/img/file-icons-classic/iso.svg new file mode 100644 index 0000000..2d7f2fa --- /dev/null +++ b/static/img/file-icons-classic/iso.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/j2.svg b/static/img/file-icons-classic/j2.svg new file mode 100644 index 0000000..6db5588 --- /dev/null +++ b/static/img/file-icons-classic/j2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/jar.svg b/static/img/file-icons-classic/jar.svg new file mode 100644 index 0000000..6c4ea53 --- /dev/null +++ b/static/img/file-icons-classic/jar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/java.svg b/static/img/file-icons-classic/java.svg new file mode 100644 index 0000000..0e6fae1 --- /dev/null +++ b/static/img/file-icons-classic/java.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/jpe.svg b/static/img/file-icons-classic/jpe.svg new file mode 100644 index 0000000..f98fdec --- /dev/null +++ b/static/img/file-icons-classic/jpe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/jpeg.svg b/static/img/file-icons-classic/jpeg.svg new file mode 100644 index 0000000..38b49f6 --- /dev/null +++ b/static/img/file-icons-classic/jpeg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/jpg.svg b/static/img/file-icons-classic/jpg.svg new file mode 100644 index 0000000..008800d --- /dev/null +++ b/static/img/file-icons-classic/jpg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/js.svg b/static/img/file-icons-classic/js.svg new file mode 100644 index 0000000..1a688f1 --- /dev/null +++ b/static/img/file-icons-classic/js.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/json.svg b/static/img/file-icons-classic/json.svg new file mode 100644 index 0000000..3519a5a --- /dev/null +++ b/static/img/file-icons-classic/json.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/jsp.svg b/static/img/file-icons-classic/jsp.svg new file mode 100644 index 0000000..41357c3 --- /dev/null +++ b/static/img/file-icons-classic/jsp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/jsx.svg b/static/img/file-icons-classic/jsx.svg new file mode 100644 index 0000000..05d5ca7 --- /dev/null +++ b/static/img/file-icons-classic/jsx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/key.svg b/static/img/file-icons-classic/key.svg new file mode 100644 index 0000000..bdadcd7 --- /dev/null +++ b/static/img/file-icons-classic/key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/kf8.svg b/static/img/file-icons-classic/kf8.svg new file mode 100644 index 0000000..8d766c3 --- /dev/null +++ b/static/img/file-icons-classic/kf8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/kmk.svg b/static/img/file-icons-classic/kmk.svg new file mode 100644 index 0000000..69e4294 --- /dev/null +++ b/static/img/file-icons-classic/kmk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ksh.svg b/static/img/file-icons-classic/ksh.svg new file mode 100644 index 0000000..78038e6 --- /dev/null +++ b/static/img/file-icons-classic/ksh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/kt.svg b/static/img/file-icons-classic/kt.svg new file mode 100644 index 0000000..3e3f775 --- /dev/null +++ b/static/img/file-icons-classic/kt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/kts.svg b/static/img/file-icons-classic/kts.svg new file mode 100644 index 0000000..71ef2dd --- /dev/null +++ b/static/img/file-icons-classic/kts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/kup.svg b/static/img/file-icons-classic/kup.svg new file mode 100644 index 0000000..3ed3181 --- /dev/null +++ b/static/img/file-icons-classic/kup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/less.svg b/static/img/file-icons-classic/less.svg new file mode 100644 index 0000000..cd0e8de --- /dev/null +++ b/static/img/file-icons-classic/less.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/lex.svg b/static/img/file-icons-classic/lex.svg new file mode 100644 index 0000000..c91ab9d --- /dev/null +++ b/static/img/file-icons-classic/lex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/licx.svg b/static/img/file-icons-classic/licx.svg new file mode 100644 index 0000000..d5ffd31 --- /dev/null +++ b/static/img/file-icons-classic/licx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/lisp.svg b/static/img/file-icons-classic/lisp.svg new file mode 100644 index 0000000..70feb93 --- /dev/null +++ b/static/img/file-icons-classic/lisp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/lit.svg b/static/img/file-icons-classic/lit.svg new file mode 100644 index 0000000..4dbb532 --- /dev/null +++ b/static/img/file-icons-classic/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/lnk.svg b/static/img/file-icons-classic/lnk.svg new file mode 100644 index 0000000..8f3346f --- /dev/null +++ b/static/img/file-icons-classic/lnk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/lock.svg b/static/img/file-icons-classic/lock.svg new file mode 100644 index 0000000..b5264f2 --- /dev/null +++ b/static/img/file-icons-classic/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/log.svg b/static/img/file-icons-classic/log.svg new file mode 100644 index 0000000..2056b28 --- /dev/null +++ b/static/img/file-icons-classic/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/lua.svg b/static/img/file-icons-classic/lua.svg new file mode 100644 index 0000000..f388b7a --- /dev/null +++ b/static/img/file-icons-classic/lua.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m.svg b/static/img/file-icons-classic/m.svg new file mode 100644 index 0000000..8ed8ddc --- /dev/null +++ b/static/img/file-icons-classic/m.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m2v.svg b/static/img/file-icons-classic/m2v.svg new file mode 100644 index 0000000..1d46397 --- /dev/null +++ b/static/img/file-icons-classic/m2v.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m3u.svg b/static/img/file-icons-classic/m3u.svg new file mode 100644 index 0000000..4fd2312 --- /dev/null +++ b/static/img/file-icons-classic/m3u.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m3u8.svg b/static/img/file-icons-classic/m3u8.svg new file mode 100644 index 0000000..916f88c --- /dev/null +++ b/static/img/file-icons-classic/m3u8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m4.svg b/static/img/file-icons-classic/m4.svg new file mode 100644 index 0000000..a044287 --- /dev/null +++ b/static/img/file-icons-classic/m4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m4a.svg b/static/img/file-icons-classic/m4a.svg new file mode 100644 index 0000000..3f2b106 --- /dev/null +++ b/static/img/file-icons-classic/m4a.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m4r.svg b/static/img/file-icons-classic/m4r.svg new file mode 100644 index 0000000..fda2096 --- /dev/null +++ b/static/img/file-icons-classic/m4r.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/m4v.svg b/static/img/file-icons-classic/m4v.svg new file mode 100644 index 0000000..619039e --- /dev/null +++ b/static/img/file-icons-classic/m4v.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/map.svg b/static/img/file-icons-classic/map.svg new file mode 100644 index 0000000..de569c7 --- /dev/null +++ b/static/img/file-icons-classic/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/master.svg b/static/img/file-icons-classic/master.svg new file mode 100644 index 0000000..24ae5be --- /dev/null +++ b/static/img/file-icons-classic/master.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mc.svg b/static/img/file-icons-classic/mc.svg new file mode 100644 index 0000000..eefdc13 --- /dev/null +++ b/static/img/file-icons-classic/mc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/md.svg b/static/img/file-icons-classic/md.svg new file mode 100644 index 0000000..b2bd3fd --- /dev/null +++ b/static/img/file-icons-classic/md.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mdb.svg b/static/img/file-icons-classic/mdb.svg new file mode 100644 index 0000000..ebe6a70 --- /dev/null +++ b/static/img/file-icons-classic/mdb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mdf.svg b/static/img/file-icons-classic/mdf.svg new file mode 100644 index 0000000..ed4e01e --- /dev/null +++ b/static/img/file-icons-classic/mdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/me.svg b/static/img/file-icons-classic/me.svg new file mode 100644 index 0000000..8417b05 --- /dev/null +++ b/static/img/file-icons-classic/me.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mi.svg b/static/img/file-icons-classic/mi.svg new file mode 100644 index 0000000..ff20452 --- /dev/null +++ b/static/img/file-icons-classic/mi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mid.svg b/static/img/file-icons-classic/mid.svg new file mode 100644 index 0000000..2afdb5c --- /dev/null +++ b/static/img/file-icons-classic/mid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/midi.svg b/static/img/file-icons-classic/midi.svg new file mode 100644 index 0000000..06071a7 --- /dev/null +++ b/static/img/file-icons-classic/midi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mk.svg b/static/img/file-icons-classic/mk.svg new file mode 100644 index 0000000..4ea388d --- /dev/null +++ b/static/img/file-icons-classic/mk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mkv.svg b/static/img/file-icons-classic/mkv.svg new file mode 100644 index 0000000..7eec925 --- /dev/null +++ b/static/img/file-icons-classic/mkv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mm.svg b/static/img/file-icons-classic/mm.svg new file mode 100644 index 0000000..29b4bee --- /dev/null +++ b/static/img/file-icons-classic/mm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mng.svg b/static/img/file-icons-classic/mng.svg new file mode 100644 index 0000000..4525fbc --- /dev/null +++ b/static/img/file-icons-classic/mng.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mo.svg b/static/img/file-icons-classic/mo.svg new file mode 100644 index 0000000..571f76d --- /dev/null +++ b/static/img/file-icons-classic/mo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mobi.svg b/static/img/file-icons-classic/mobi.svg new file mode 100644 index 0000000..a0a299c --- /dev/null +++ b/static/img/file-icons-classic/mobi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mod.svg b/static/img/file-icons-classic/mod.svg new file mode 100644 index 0000000..1319fe2 --- /dev/null +++ b/static/img/file-icons-classic/mod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mov.svg b/static/img/file-icons-classic/mov.svg new file mode 100644 index 0000000..21c12dd --- /dev/null +++ b/static/img/file-icons-classic/mov.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mp2.svg b/static/img/file-icons-classic/mp2.svg new file mode 100644 index 0000000..dce1aa7 --- /dev/null +++ b/static/img/file-icons-classic/mp2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mp3.svg b/static/img/file-icons-classic/mp3.svg new file mode 100644 index 0000000..0e25673 --- /dev/null +++ b/static/img/file-icons-classic/mp3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mp4.svg b/static/img/file-icons-classic/mp4.svg new file mode 100644 index 0000000..be90ef2 --- /dev/null +++ b/static/img/file-icons-classic/mp4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpa.svg b/static/img/file-icons-classic/mpa.svg new file mode 100644 index 0000000..9707c44 --- /dev/null +++ b/static/img/file-icons-classic/mpa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpd.svg b/static/img/file-icons-classic/mpd.svg new file mode 100644 index 0000000..a36393d --- /dev/null +++ b/static/img/file-icons-classic/mpd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpe.svg b/static/img/file-icons-classic/mpe.svg new file mode 100644 index 0000000..f73fa7b --- /dev/null +++ b/static/img/file-icons-classic/mpe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpeg.svg b/static/img/file-icons-classic/mpeg.svg new file mode 100644 index 0000000..2631f70 --- /dev/null +++ b/static/img/file-icons-classic/mpeg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpg.svg b/static/img/file-icons-classic/mpg.svg new file mode 100644 index 0000000..d2f60ba --- /dev/null +++ b/static/img/file-icons-classic/mpg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpga.svg b/static/img/file-icons-classic/mpga.svg new file mode 100644 index 0000000..63a89c1 --- /dev/null +++ b/static/img/file-icons-classic/mpga.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpp.svg b/static/img/file-icons-classic/mpp.svg new file mode 100644 index 0000000..1e5bc8e --- /dev/null +++ b/static/img/file-icons-classic/mpp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/mpt.svg b/static/img/file-icons-classic/mpt.svg new file mode 100644 index 0000000..76e1a30 --- /dev/null +++ b/static/img/file-icons-classic/mpt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/msg.svg b/static/img/file-icons-classic/msg.svg new file mode 100644 index 0000000..0ddd9ca --- /dev/null +++ b/static/img/file-icons-classic/msg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/msi.svg b/static/img/file-icons-classic/msi.svg new file mode 100644 index 0000000..6657d9c --- /dev/null +++ b/static/img/file-icons-classic/msi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/msu.svg b/static/img/file-icons-classic/msu.svg new file mode 100644 index 0000000..b1a9d79 --- /dev/null +++ b/static/img/file-icons-classic/msu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/nef.svg b/static/img/file-icons-classic/nef.svg new file mode 100644 index 0000000..d25da5f --- /dev/null +++ b/static/img/file-icons-classic/nef.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/nes.svg b/static/img/file-icons-classic/nes.svg new file mode 100644 index 0000000..5461fa6 --- /dev/null +++ b/static/img/file-icons-classic/nes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/nfo.svg b/static/img/file-icons-classic/nfo.svg new file mode 100644 index 0000000..5b7a1f0 --- /dev/null +++ b/static/img/file-icons-classic/nfo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/nix.svg b/static/img/file-icons-classic/nix.svg new file mode 100644 index 0000000..c40c6fd --- /dev/null +++ b/static/img/file-icons-classic/nix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/npmignore.svg b/static/img/file-icons-classic/npmignore.svg new file mode 100644 index 0000000..2dd1c39 --- /dev/null +++ b/static/img/file-icons-classic/npmignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ocx.svg b/static/img/file-icons-classic/ocx.svg new file mode 100644 index 0000000..2963442 --- /dev/null +++ b/static/img/file-icons-classic/ocx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/odb.svg b/static/img/file-icons-classic/odb.svg new file mode 100644 index 0000000..fa7b655 --- /dev/null +++ b/static/img/file-icons-classic/odb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ods.svg b/static/img/file-icons-classic/ods.svg new file mode 100644 index 0000000..46930f4 --- /dev/null +++ b/static/img/file-icons-classic/ods.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/odt.svg b/static/img/file-icons-classic/odt.svg new file mode 100644 index 0000000..d966291 --- /dev/null +++ b/static/img/file-icons-classic/odt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ogg.svg b/static/img/file-icons-classic/ogg.svg new file mode 100644 index 0000000..8770b91 --- /dev/null +++ b/static/img/file-icons-classic/ogg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ogv.svg b/static/img/file-icons-classic/ogv.svg new file mode 100644 index 0000000..62acac9 --- /dev/null +++ b/static/img/file-icons-classic/ogv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ost.svg b/static/img/file-icons-classic/ost.svg new file mode 100644 index 0000000..e0d5556 --- /dev/null +++ b/static/img/file-icons-classic/ost.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/otf.svg b/static/img/file-icons-classic/otf.svg new file mode 100644 index 0000000..778b2c4 --- /dev/null +++ b/static/img/file-icons-classic/otf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ott.svg b/static/img/file-icons-classic/ott.svg new file mode 100644 index 0000000..bbd041d --- /dev/null +++ b/static/img/file-icons-classic/ott.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ova.svg b/static/img/file-icons-classic/ova.svg new file mode 100644 index 0000000..aec7d6f --- /dev/null +++ b/static/img/file-icons-classic/ova.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ovf.svg b/static/img/file-icons-classic/ovf.svg new file mode 100644 index 0000000..af15b39 --- /dev/null +++ b/static/img/file-icons-classic/ovf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/p12.svg b/static/img/file-icons-classic/p12.svg new file mode 100644 index 0000000..ec86c32 --- /dev/null +++ b/static/img/file-icons-classic/p12.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/p7b.svg b/static/img/file-icons-classic/p7b.svg new file mode 100644 index 0000000..54b69c6 --- /dev/null +++ b/static/img/file-icons-classic/p7b.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pages.svg b/static/img/file-icons-classic/pages.svg new file mode 100644 index 0000000..d97b8f7 --- /dev/null +++ b/static/img/file-icons-classic/pages.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/part.svg b/static/img/file-icons-classic/part.svg new file mode 100644 index 0000000..fec8246 --- /dev/null +++ b/static/img/file-icons-classic/part.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pcd.svg b/static/img/file-icons-classic/pcd.svg new file mode 100644 index 0000000..6eb0f75 --- /dev/null +++ b/static/img/file-icons-classic/pcd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pdb.svg b/static/img/file-icons-classic/pdb.svg new file mode 100644 index 0000000..9403e03 --- /dev/null +++ b/static/img/file-icons-classic/pdb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pdf.svg b/static/img/file-icons-classic/pdf.svg new file mode 100644 index 0000000..babf198 --- /dev/null +++ b/static/img/file-icons-classic/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pem.svg b/static/img/file-icons-classic/pem.svg new file mode 100644 index 0000000..254380e --- /dev/null +++ b/static/img/file-icons-classic/pem.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pfx.svg b/static/img/file-icons-classic/pfx.svg new file mode 100644 index 0000000..8a96bf1 --- /dev/null +++ b/static/img/file-icons-classic/pfx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pgp.svg b/static/img/file-icons-classic/pgp.svg new file mode 100644 index 0000000..87c3208 --- /dev/null +++ b/static/img/file-icons-classic/pgp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ph.svg b/static/img/file-icons-classic/ph.svg new file mode 100644 index 0000000..1286dc2 --- /dev/null +++ b/static/img/file-icons-classic/ph.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/phar.svg b/static/img/file-icons-classic/phar.svg new file mode 100644 index 0000000..066e286 --- /dev/null +++ b/static/img/file-icons-classic/phar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/php.svg b/static/img/file-icons-classic/php.svg new file mode 100644 index 0000000..7a21f87 --- /dev/null +++ b/static/img/file-icons-classic/php.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pid.svg b/static/img/file-icons-classic/pid.svg new file mode 100644 index 0000000..8e91e5d --- /dev/null +++ b/static/img/file-icons-classic/pid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pkg.svg b/static/img/file-icons-classic/pkg.svg new file mode 100644 index 0000000..48f6448 --- /dev/null +++ b/static/img/file-icons-classic/pkg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pl.svg b/static/img/file-icons-classic/pl.svg new file mode 100644 index 0000000..2f5b942 --- /dev/null +++ b/static/img/file-icons-classic/pl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/plist.svg b/static/img/file-icons-classic/plist.svg new file mode 100644 index 0000000..6d6146a --- /dev/null +++ b/static/img/file-icons-classic/plist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pm.svg b/static/img/file-icons-classic/pm.svg new file mode 100644 index 0000000..2c6a6be --- /dev/null +++ b/static/img/file-icons-classic/pm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/png.svg b/static/img/file-icons-classic/png.svg new file mode 100644 index 0000000..49432d3 --- /dev/null +++ b/static/img/file-icons-classic/png.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/po.svg b/static/img/file-icons-classic/po.svg new file mode 100644 index 0000000..4184f10 --- /dev/null +++ b/static/img/file-icons-classic/po.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pom.svg b/static/img/file-icons-classic/pom.svg new file mode 100644 index 0000000..3517f8b --- /dev/null +++ b/static/img/file-icons-classic/pom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pot.svg b/static/img/file-icons-classic/pot.svg new file mode 100644 index 0000000..919f7ef --- /dev/null +++ b/static/img/file-icons-classic/pot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/potx.svg b/static/img/file-icons-classic/potx.svg new file mode 100644 index 0000000..bdfcd33 --- /dev/null +++ b/static/img/file-icons-classic/potx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pps.svg b/static/img/file-icons-classic/pps.svg new file mode 100644 index 0000000..8647fb6 --- /dev/null +++ b/static/img/file-icons-classic/pps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ppsx.svg b/static/img/file-icons-classic/ppsx.svg new file mode 100644 index 0000000..cd9c3ff --- /dev/null +++ b/static/img/file-icons-classic/ppsx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ppt.svg b/static/img/file-icons-classic/ppt.svg new file mode 100644 index 0000000..54737a9 --- /dev/null +++ b/static/img/file-icons-classic/ppt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pptm.svg b/static/img/file-icons-classic/pptm.svg new file mode 100644 index 0000000..49425ed --- /dev/null +++ b/static/img/file-icons-classic/pptm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pptx.svg b/static/img/file-icons-classic/pptx.svg new file mode 100644 index 0000000..e5cd09e --- /dev/null +++ b/static/img/file-icons-classic/pptx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/prop.svg b/static/img/file-icons-classic/prop.svg new file mode 100644 index 0000000..2d44071 --- /dev/null +++ b/static/img/file-icons-classic/prop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ps.svg b/static/img/file-icons-classic/ps.svg new file mode 100644 index 0000000..c9699ca --- /dev/null +++ b/static/img/file-icons-classic/ps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ps1.svg b/static/img/file-icons-classic/ps1.svg new file mode 100644 index 0000000..1657e6f --- /dev/null +++ b/static/img/file-icons-classic/ps1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/psd.svg b/static/img/file-icons-classic/psd.svg new file mode 100644 index 0000000..e8af1f6 --- /dev/null +++ b/static/img/file-icons-classic/psd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/psp.svg b/static/img/file-icons-classic/psp.svg new file mode 100644 index 0000000..ce0af52 --- /dev/null +++ b/static/img/file-icons-classic/psp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pst.svg b/static/img/file-icons-classic/pst.svg new file mode 100644 index 0000000..8a69e94 --- /dev/null +++ b/static/img/file-icons-classic/pst.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pub.svg b/static/img/file-icons-classic/pub.svg new file mode 100644 index 0000000..7a78f1d --- /dev/null +++ b/static/img/file-icons-classic/pub.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/py.svg b/static/img/file-icons-classic/py.svg new file mode 100644 index 0000000..7de406b --- /dev/null +++ b/static/img/file-icons-classic/py.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/pyc.svg b/static/img/file-icons-classic/pyc.svg new file mode 100644 index 0000000..4ffd574 --- /dev/null +++ b/static/img/file-icons-classic/pyc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/qt.svg b/static/img/file-icons-classic/qt.svg new file mode 100644 index 0000000..f3a1566 --- /dev/null +++ b/static/img/file-icons-classic/qt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ra.svg b/static/img/file-icons-classic/ra.svg new file mode 100644 index 0000000..5a8d06c --- /dev/null +++ b/static/img/file-icons-classic/ra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ram.svg b/static/img/file-icons-classic/ram.svg new file mode 100644 index 0000000..4bb5e3b --- /dev/null +++ b/static/img/file-icons-classic/ram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rar.svg b/static/img/file-icons-classic/rar.svg new file mode 100644 index 0000000..bf6821a --- /dev/null +++ b/static/img/file-icons-classic/rar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/raw.svg b/static/img/file-icons-classic/raw.svg new file mode 100644 index 0000000..fc8fc3a --- /dev/null +++ b/static/img/file-icons-classic/raw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rb.svg b/static/img/file-icons-classic/rb.svg new file mode 100644 index 0000000..141a78b --- /dev/null +++ b/static/img/file-icons-classic/rb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rdf.svg b/static/img/file-icons-classic/rdf.svg new file mode 100644 index 0000000..6a7cef0 --- /dev/null +++ b/static/img/file-icons-classic/rdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rdl.svg b/static/img/file-icons-classic/rdl.svg new file mode 100644 index 0000000..31d7477 --- /dev/null +++ b/static/img/file-icons-classic/rdl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/reg.svg b/static/img/file-icons-classic/reg.svg new file mode 100644 index 0000000..65c837e --- /dev/null +++ b/static/img/file-icons-classic/reg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/resx.svg b/static/img/file-icons-classic/resx.svg new file mode 100644 index 0000000..43d19e6 --- /dev/null +++ b/static/img/file-icons-classic/resx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/retry.svg b/static/img/file-icons-classic/retry.svg new file mode 100644 index 0000000..c2bf17c --- /dev/null +++ b/static/img/file-icons-classic/retry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rm.svg b/static/img/file-icons-classic/rm.svg new file mode 100644 index 0000000..7a5c8d6 --- /dev/null +++ b/static/img/file-icons-classic/rm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rom.svg b/static/img/file-icons-classic/rom.svg new file mode 100644 index 0000000..876c443 --- /dev/null +++ b/static/img/file-icons-classic/rom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rpm.svg b/static/img/file-icons-classic/rpm.svg new file mode 100644 index 0000000..485d940 --- /dev/null +++ b/static/img/file-icons-classic/rpm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rpt.svg b/static/img/file-icons-classic/rpt.svg new file mode 100644 index 0000000..ca6ec07 --- /dev/null +++ b/static/img/file-icons-classic/rpt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rsa.svg b/static/img/file-icons-classic/rsa.svg new file mode 100644 index 0000000..483b8ef --- /dev/null +++ b/static/img/file-icons-classic/rsa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rss.svg b/static/img/file-icons-classic/rss.svg new file mode 100644 index 0000000..6679ae3 --- /dev/null +++ b/static/img/file-icons-classic/rss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rst.svg b/static/img/file-icons-classic/rst.svg new file mode 100644 index 0000000..1d38c64 --- /dev/null +++ b/static/img/file-icons-classic/rst.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rtf.svg b/static/img/file-icons-classic/rtf.svg new file mode 100644 index 0000000..1fc7b90 --- /dev/null +++ b/static/img/file-icons-classic/rtf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ru.svg b/static/img/file-icons-classic/ru.svg new file mode 100644 index 0000000..c7b7630 --- /dev/null +++ b/static/img/file-icons-classic/ru.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/rub.svg b/static/img/file-icons-classic/rub.svg new file mode 100644 index 0000000..5d84d47 --- /dev/null +++ b/static/img/file-icons-classic/rub.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sass.svg b/static/img/file-icons-classic/sass.svg new file mode 100644 index 0000000..ab71efc --- /dev/null +++ b/static/img/file-icons-classic/sass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/scss.svg b/static/img/file-icons-classic/scss.svg new file mode 100644 index 0000000..d66ef8b --- /dev/null +++ b/static/img/file-icons-classic/scss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sdf.svg b/static/img/file-icons-classic/sdf.svg new file mode 100644 index 0000000..42fcaad --- /dev/null +++ b/static/img/file-icons-classic/sdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sed.svg b/static/img/file-icons-classic/sed.svg new file mode 100644 index 0000000..919bb14 --- /dev/null +++ b/static/img/file-icons-classic/sed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sh.svg b/static/img/file-icons-classic/sh.svg new file mode 100644 index 0000000..1a882e5 --- /dev/null +++ b/static/img/file-icons-classic/sh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sit.svg b/static/img/file-icons-classic/sit.svg new file mode 100644 index 0000000..3175095 --- /dev/null +++ b/static/img/file-icons-classic/sit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sitemap.svg b/static/img/file-icons-classic/sitemap.svg new file mode 100644 index 0000000..efa12e9 --- /dev/null +++ b/static/img/file-icons-classic/sitemap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/skin.svg b/static/img/file-icons-classic/skin.svg new file mode 100644 index 0000000..7143a51 --- /dev/null +++ b/static/img/file-icons-classic/skin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sldm.svg b/static/img/file-icons-classic/sldm.svg new file mode 100644 index 0000000..41ed221 --- /dev/null +++ b/static/img/file-icons-classic/sldm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sldx.svg b/static/img/file-icons-classic/sldx.svg new file mode 100644 index 0000000..230aba4 --- /dev/null +++ b/static/img/file-icons-classic/sldx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sln.svg b/static/img/file-icons-classic/sln.svg new file mode 100644 index 0000000..b6b132a --- /dev/null +++ b/static/img/file-icons-classic/sln.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sol.svg b/static/img/file-icons-classic/sol.svg new file mode 100644 index 0000000..1d120f0 --- /dev/null +++ b/static/img/file-icons-classic/sol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sphinx.svg b/static/img/file-icons-classic/sphinx.svg new file mode 100644 index 0000000..6689256 --- /dev/null +++ b/static/img/file-icons-classic/sphinx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sql.svg b/static/img/file-icons-classic/sql.svg new file mode 100644 index 0000000..c93f33e --- /dev/null +++ b/static/img/file-icons-classic/sql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sqlite.svg b/static/img/file-icons-classic/sqlite.svg new file mode 100644 index 0000000..9ff1938 --- /dev/null +++ b/static/img/file-icons-classic/sqlite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/step.svg b/static/img/file-icons-classic/step.svg new file mode 100644 index 0000000..647897e --- /dev/null +++ b/static/img/file-icons-classic/step.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/stl.svg b/static/img/file-icons-classic/stl.svg new file mode 100644 index 0000000..fb5597b --- /dev/null +++ b/static/img/file-icons-classic/stl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/svg.svg b/static/img/file-icons-classic/svg.svg new file mode 100644 index 0000000..f7b3141 --- /dev/null +++ b/static/img/file-icons-classic/svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/swd.svg b/static/img/file-icons-classic/swd.svg new file mode 100644 index 0000000..eaebca3 --- /dev/null +++ b/static/img/file-icons-classic/swd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/swf.svg b/static/img/file-icons-classic/swf.svg new file mode 100644 index 0000000..6beac3d --- /dev/null +++ b/static/img/file-icons-classic/swf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/swift.svg b/static/img/file-icons-classic/swift.svg new file mode 100644 index 0000000..1af5d99 --- /dev/null +++ b/static/img/file-icons-classic/swift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/swp.svg b/static/img/file-icons-classic/swp.svg new file mode 100644 index 0000000..ab0bd8d --- /dev/null +++ b/static/img/file-icons-classic/swp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/sys.svg b/static/img/file-icons-classic/sys.svg new file mode 100644 index 0000000..63623d6 --- /dev/null +++ b/static/img/file-icons-classic/sys.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tar.svg b/static/img/file-icons-classic/tar.svg new file mode 100644 index 0000000..519731a --- /dev/null +++ b/static/img/file-icons-classic/tar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tax.svg b/static/img/file-icons-classic/tax.svg new file mode 100644 index 0000000..2791565 --- /dev/null +++ b/static/img/file-icons-classic/tax.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tcsh.svg b/static/img/file-icons-classic/tcsh.svg new file mode 100644 index 0000000..2bae3f6 --- /dev/null +++ b/static/img/file-icons-classic/tcsh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tex.svg b/static/img/file-icons-classic/tex.svg new file mode 100644 index 0000000..4088110 --- /dev/null +++ b/static/img/file-icons-classic/tex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tfignore.svg b/static/img/file-icons-classic/tfignore.svg new file mode 100644 index 0000000..2dd1c39 --- /dev/null +++ b/static/img/file-icons-classic/tfignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tga.svg b/static/img/file-icons-classic/tga.svg new file mode 100644 index 0000000..6884f9b --- /dev/null +++ b/static/img/file-icons-classic/tga.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tgz.svg b/static/img/file-icons-classic/tgz.svg new file mode 100644 index 0000000..966f76d --- /dev/null +++ b/static/img/file-icons-classic/tgz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tif.svg b/static/img/file-icons-classic/tif.svg new file mode 100644 index 0000000..d86a7c2 --- /dev/null +++ b/static/img/file-icons-classic/tif.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tiff.svg b/static/img/file-icons-classic/tiff.svg new file mode 100644 index 0000000..41f3e5a --- /dev/null +++ b/static/img/file-icons-classic/tiff.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tmp.svg b/static/img/file-icons-classic/tmp.svg new file mode 100644 index 0000000..f52b00c --- /dev/null +++ b/static/img/file-icons-classic/tmp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tmx.svg b/static/img/file-icons-classic/tmx.svg new file mode 100644 index 0000000..2cc1e37 --- /dev/null +++ b/static/img/file-icons-classic/tmx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/torrent.svg b/static/img/file-icons-classic/torrent.svg new file mode 100644 index 0000000..c23ffe5 --- /dev/null +++ b/static/img/file-icons-classic/torrent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tpl.svg b/static/img/file-icons-classic/tpl.svg new file mode 100644 index 0000000..61fcc4e --- /dev/null +++ b/static/img/file-icons-classic/tpl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ts.svg b/static/img/file-icons-classic/ts.svg new file mode 100644 index 0000000..0c489b4 --- /dev/null +++ b/static/img/file-icons-classic/ts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/tsv.svg b/static/img/file-icons-classic/tsv.svg new file mode 100644 index 0000000..d2edafb --- /dev/null +++ b/static/img/file-icons-classic/tsv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/ttf.svg b/static/img/file-icons-classic/ttf.svg new file mode 100644 index 0000000..aac1c96 --- /dev/null +++ b/static/img/file-icons-classic/ttf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/twig.svg b/static/img/file-icons-classic/twig.svg new file mode 100644 index 0000000..579fa77 --- /dev/null +++ b/static/img/file-icons-classic/twig.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/txt.svg b/static/img/file-icons-classic/txt.svg new file mode 100644 index 0000000..496d593 --- /dev/null +++ b/static/img/file-icons-classic/txt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/udf.svg b/static/img/file-icons-classic/udf.svg new file mode 100644 index 0000000..5de105d --- /dev/null +++ b/static/img/file-icons-classic/udf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vb.svg b/static/img/file-icons-classic/vb.svg new file mode 100644 index 0000000..a3de697 --- /dev/null +++ b/static/img/file-icons-classic/vb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vbproj.svg b/static/img/file-icons-classic/vbproj.svg new file mode 100644 index 0000000..f67d2d1 --- /dev/null +++ b/static/img/file-icons-classic/vbproj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vbs.svg b/static/img/file-icons-classic/vbs.svg new file mode 100644 index 0000000..056173c --- /dev/null +++ b/static/img/file-icons-classic/vbs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vcd.svg b/static/img/file-icons-classic/vcd.svg new file mode 100644 index 0000000..77435da --- /dev/null +++ b/static/img/file-icons-classic/vcd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vcf.svg b/static/img/file-icons-classic/vcf.svg new file mode 100644 index 0000000..7c9ce72 --- /dev/null +++ b/static/img/file-icons-classic/vcf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vcs.svg b/static/img/file-icons-classic/vcs.svg new file mode 100644 index 0000000..2b2cf79 --- /dev/null +++ b/static/img/file-icons-classic/vcs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vdi.svg b/static/img/file-icons-classic/vdi.svg new file mode 100644 index 0000000..8a0e639 --- /dev/null +++ b/static/img/file-icons-classic/vdi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vdx.svg b/static/img/file-icons-classic/vdx.svg new file mode 100644 index 0000000..b0a9108 --- /dev/null +++ b/static/img/file-icons-classic/vdx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vmdk.svg b/static/img/file-icons-classic/vmdk.svg new file mode 100644 index 0000000..34317f0 --- /dev/null +++ b/static/img/file-icons-classic/vmdk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vob.svg b/static/img/file-icons-classic/vob.svg new file mode 100644 index 0000000..00e5f64 --- /dev/null +++ b/static/img/file-icons-classic/vob.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vox.svg b/static/img/file-icons-classic/vox.svg new file mode 100644 index 0000000..a3a6518 --- /dev/null +++ b/static/img/file-icons-classic/vox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vscodeignore.svg b/static/img/file-icons-classic/vscodeignore.svg new file mode 100644 index 0000000..a3e0e35 --- /dev/null +++ b/static/img/file-icons-classic/vscodeignore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vsd.svg b/static/img/file-icons-classic/vsd.svg new file mode 100644 index 0000000..8801bef --- /dev/null +++ b/static/img/file-icons-classic/vsd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vss.svg b/static/img/file-icons-classic/vss.svg new file mode 100644 index 0000000..84ce428 --- /dev/null +++ b/static/img/file-icons-classic/vss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vst.svg b/static/img/file-icons-classic/vst.svg new file mode 100644 index 0000000..7b17e8e --- /dev/null +++ b/static/img/file-icons-classic/vst.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vsx.svg b/static/img/file-icons-classic/vsx.svg new file mode 100644 index 0000000..8fae382 --- /dev/null +++ b/static/img/file-icons-classic/vsx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/vtx.svg b/static/img/file-icons-classic/vtx.svg new file mode 100644 index 0000000..07cd871 --- /dev/null +++ b/static/img/file-icons-classic/vtx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/war.svg b/static/img/file-icons-classic/war.svg new file mode 100644 index 0000000..b660a39 --- /dev/null +++ b/static/img/file-icons-classic/war.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wav.svg b/static/img/file-icons-classic/wav.svg new file mode 100644 index 0000000..3a525ad --- /dev/null +++ b/static/img/file-icons-classic/wav.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wbk.svg b/static/img/file-icons-classic/wbk.svg new file mode 100644 index 0000000..962814e --- /dev/null +++ b/static/img/file-icons-classic/wbk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/webinfo.svg b/static/img/file-icons-classic/webinfo.svg new file mode 100644 index 0000000..79fa571 --- /dev/null +++ b/static/img/file-icons-classic/webinfo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/webm.svg b/static/img/file-icons-classic/webm.svg new file mode 100644 index 0000000..7f67838 --- /dev/null +++ b/static/img/file-icons-classic/webm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/webp.svg b/static/img/file-icons-classic/webp.svg new file mode 100644 index 0000000..add6c2a --- /dev/null +++ b/static/img/file-icons-classic/webp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wma.svg b/static/img/file-icons-classic/wma.svg new file mode 100644 index 0000000..d967339 --- /dev/null +++ b/static/img/file-icons-classic/wma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wmf.svg b/static/img/file-icons-classic/wmf.svg new file mode 100644 index 0000000..da0bff1 --- /dev/null +++ b/static/img/file-icons-classic/wmf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wmv.svg b/static/img/file-icons-classic/wmv.svg new file mode 100644 index 0000000..3a44fc3 --- /dev/null +++ b/static/img/file-icons-classic/wmv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/woff.svg b/static/img/file-icons-classic/woff.svg new file mode 100644 index 0000000..3361869 --- /dev/null +++ b/static/img/file-icons-classic/woff.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/woff2.svg b/static/img/file-icons-classic/woff2.svg new file mode 100644 index 0000000..8687d5b --- /dev/null +++ b/static/img/file-icons-classic/woff2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wps.svg b/static/img/file-icons-classic/wps.svg new file mode 100644 index 0000000..9eccbe4 --- /dev/null +++ b/static/img/file-icons-classic/wps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/wsf.svg b/static/img/file-icons-classic/wsf.svg new file mode 100644 index 0000000..2676e59 --- /dev/null +++ b/static/img/file-icons-classic/wsf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xaml.svg b/static/img/file-icons-classic/xaml.svg new file mode 100644 index 0000000..b36b80d --- /dev/null +++ b/static/img/file-icons-classic/xaml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xcf.svg b/static/img/file-icons-classic/xcf.svg new file mode 100644 index 0000000..7464caf --- /dev/null +++ b/static/img/file-icons-classic/xcf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xfl.svg b/static/img/file-icons-classic/xfl.svg new file mode 100644 index 0000000..02b0aa3 --- /dev/null +++ b/static/img/file-icons-classic/xfl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xlm.svg b/static/img/file-icons-classic/xlm.svg new file mode 100644 index 0000000..f13a44b --- /dev/null +++ b/static/img/file-icons-classic/xlm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xls.svg b/static/img/file-icons-classic/xls.svg new file mode 100644 index 0000000..5102c0b --- /dev/null +++ b/static/img/file-icons-classic/xls.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xlsm.svg b/static/img/file-icons-classic/xlsm.svg new file mode 100644 index 0000000..9fdb8a2 --- /dev/null +++ b/static/img/file-icons-classic/xlsm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xlsx.svg b/static/img/file-icons-classic/xlsx.svg new file mode 100644 index 0000000..3a5d610 --- /dev/null +++ b/static/img/file-icons-classic/xlsx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xlt.svg b/static/img/file-icons-classic/xlt.svg new file mode 100644 index 0000000..0008337 --- /dev/null +++ b/static/img/file-icons-classic/xlt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xltm.svg b/static/img/file-icons-classic/xltm.svg new file mode 100644 index 0000000..b8ace08 --- /dev/null +++ b/static/img/file-icons-classic/xltm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xltx.svg b/static/img/file-icons-classic/xltx.svg new file mode 100644 index 0000000..c06c902 --- /dev/null +++ b/static/img/file-icons-classic/xltx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xml.svg b/static/img/file-icons-classic/xml.svg new file mode 100644 index 0000000..7096392 --- /dev/null +++ b/static/img/file-icons-classic/xml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xpi.svg b/static/img/file-icons-classic/xpi.svg new file mode 100644 index 0000000..b37c989 --- /dev/null +++ b/static/img/file-icons-classic/xpi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xps.svg b/static/img/file-icons-classic/xps.svg new file mode 100644 index 0000000..f5a4632 --- /dev/null +++ b/static/img/file-icons-classic/xps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xrb.svg b/static/img/file-icons-classic/xrb.svg new file mode 100644 index 0000000..5fbf7cc --- /dev/null +++ b/static/img/file-icons-classic/xrb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xsd.svg b/static/img/file-icons-classic/xsd.svg new file mode 100644 index 0000000..eb26b1e --- /dev/null +++ b/static/img/file-icons-classic/xsd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xsl.svg b/static/img/file-icons-classic/xsl.svg new file mode 100644 index 0000000..062c4c6 --- /dev/null +++ b/static/img/file-icons-classic/xsl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xspf.svg b/static/img/file-icons-classic/xspf.svg new file mode 100644 index 0000000..2a59c7e --- /dev/null +++ b/static/img/file-icons-classic/xspf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/xz.svg b/static/img/file-icons-classic/xz.svg new file mode 100644 index 0000000..cbc7776 --- /dev/null +++ b/static/img/file-icons-classic/xz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/yaml.svg b/static/img/file-icons-classic/yaml.svg new file mode 100644 index 0000000..c13ea31 --- /dev/null +++ b/static/img/file-icons-classic/yaml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/yml.svg b/static/img/file-icons-classic/yml.svg new file mode 100644 index 0000000..59f66b2 --- /dev/null +++ b/static/img/file-icons-classic/yml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/z.svg b/static/img/file-icons-classic/z.svg new file mode 100644 index 0000000..c8ae280 --- /dev/null +++ b/static/img/file-icons-classic/z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/zip.svg b/static/img/file-icons-classic/zip.svg new file mode 100644 index 0000000..f9bcec1 --- /dev/null +++ b/static/img/file-icons-classic/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/file-icons-classic/zsh.svg b/static/img/file-icons-classic/zsh.svg new file mode 100644 index 0000000..ac2959d --- /dev/null +++ b/static/img/file-icons-classic/zsh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/icon-256x256.png b/static/img/icon-256x256.png new file mode 100644 index 0000000..7881e93 Binary files /dev/null and b/static/img/icon-256x256.png differ diff --git a/static/img/icon-512x512.png b/static/img/icon-512x512.png new file mode 100644 index 0000000..1c85c2d Binary files /dev/null and b/static/img/icon-512x512.png differ diff --git a/static/img/noise.png b/static/img/noise.png new file mode 100644 index 0000000..c12624a Binary files /dev/null and b/static/img/noise.png differ diff --git a/static/js/chat.js b/static/js/chat.js new file mode 100644 index 0000000..9534fa0 --- /dev/null +++ b/static/js/chat.js @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////////////////////////////////// + +function receivedMessage(msg) { + document.querySelector('#chat-echo-area') + .insertAdjacentHTML("beforeend", msg.data); +} + +// TODO {{ server-name }}:{{ server-port }} +const ws = new WebSocket("ws://" + window.location.host + "/chat-server"); +ws.addEventListener('message', receivedMessage); + +//////////////////////////////////////////////////////////////////////////////// + +const inputField = document.getElementById("chat-input"); +inputField.addEventListener("keyup", (evt) => { + if (evt.key === "Enter") { + ws.send(evt.target.value); + evt.target.value = ""; + } +}); diff --git a/static/js/driver.js b/static/js/driver.js new file mode 100644 index 0000000..feec4e2 --- /dev/null +++ b/static/js/driver.js @@ -0,0 +1,14 @@ +import { driver } from "driver.js"; +import "driver.js/dist/driver.css"; + +const driverObj = driver({ + showProgress: true, + steps: [ + { element: '.page-header', popover: { title: 'Title', description: 'Description' } }, + { element: '.top-nav', popover: { title: 'Title', description: 'Description' } }, + { element: '.sidebar', popover: { title: 'Title', description: 'Description' } }, + { element: '.footer', popover: { title: 'Title', description: 'Description' } }, + ] +}); + +driverObj.drive(); diff --git a/static/js/mittens.js b/static/js/mittens.js new file mode 100644 index 0000000..074423f --- /dev/null +++ b/static/js/mittens.js @@ -0,0 +1,41 @@ +const b = (id) => ` +
      + 🎈 +
      + `; +const s = (id) => ` +
      + 🧽 +
      + `; +const m = document.getElementById('interactive-menu'); +function balloons() { confetti(b) }; +function sponges() { confetti(s) }; + +function confetti(f) { + const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) + for (let i = 0; i < 3; i++) { + const id = `c-${String(Math.random()).slice(2, 9)}` + m.insertAdjacentHTML('beforeend', f(id)) + sink(id) + } + async function sink(id) { + const e = document.getElementById(id); + const startTop = getComputedStyle(e)?.top ?? '0px'; + let top = parseInt(startTop.slice(0, startTop.indexOf('p')), 10); + while (top < vh + 100) { + top = top + 2; + e.style.top = `${top}px`; + await new Promise(r => setTimeout(r, 16.67)); + } + e.remove(); + } +} diff --git a/static/js/register-sw.js b/static/js/register-sw.js new file mode 100644 index 0000000..d3d8662 --- /dev/null +++ b/static/js/register-sw.js @@ -0,0 +1,18 @@ +const registerServiceWorker = async () => { + if ("serviceWorker" in navigator) { + try { + const registration = await navigator.serviceWorker.register('/js/service-worker.js', {scope: "/"}); + if (registration.installing) { + console.log("Service worker installing"); + } else if (registration.waiting) { + console.log("Service worker installed"); + } else if (registration.active) { + console.log("Service worker active"); + } + } catch (error) { + console.error(`Registration failed with ${error}`); + } + } +}; + +registerServiceWorker(); diff --git a/static/js/service-worker.js b/static/js/service-worker.js new file mode 100644 index 0000000..8a0c7f9 --- /dev/null +++ b/static/js/service-worker.js @@ -0,0 +1,54 @@ +// This is the service worker with the combined offline experience (Offline page + Offline copy of pages) + +const CACHE = "pwabuilder-offline-page"; + +importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); + +// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html"; +const offlineFallbackPage = "offline.html"; + +self.addEventListener("message", (event) => { + if (event.data && event.data.type === "SKIP_WAITING") { + self.skipWaiting(); + } +}); + +self.addEventListener('install', async (event) => { + event.waitUntil( + caches.open(CACHE) + .then((cache) => cache.add(offlineFallbackPage)) + ); +}); + +if (workbox.navigationPreload.isSupported()) { + workbox.navigationPreload.enable(); +} + +workbox.routing.registerRoute( + new RegExp('/*'), + new workbox.strategies.StaleWhileRevalidate({ + cacheName: CACHE + }) +); + +self.addEventListener('fetch', (event) => { + if (event.request.mode === 'navigate') { + event.respondWith((async () => { + try { + const preloadResp = await event.preloadResponse; + + if (preloadResp) { + return preloadResp; + } + + const networkResp = await fetch(event.request); + return networkResp; + } catch (error) { + + const cache = await caches.open(CACHE); + const cachedResp = await cache.match(offlineFallbackPage); + return cachedResp; + } + })()); + } +}); diff --git a/static/js/site.js b/static/js/site.js new file mode 100644 index 0000000..edd23e0 --- /dev/null +++ b/static/js/site.js @@ -0,0 +1,54 @@ +function sort_table (){ + /* https://stackoverflow.com/questions/3160277/jquery-table-sort */ + const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; + + const comparer = (idx, asc) => (a, b) => ((v1, v2) => + v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2, undefined, {numeric:true}) + )(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx)); + + // do the work... + window.addEventListener('load', function() { + document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => { + const table = th.closest('table'); + Array.from(table.querySelectorAll('tr:nth-child(n+2)')) + .sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc)) + .forEach(tr => table.appendChild(tr) ); + }))); + }, false); +} + +function toggle_hidden(id) { + var x = document.getElementById(id); + if (x.className.indexOf("is-hidden") == -1) { + /* It is showing, hide it */ + x.className = x.className.replace("is-visible", "is-hidden"); + /* Special case for the sidebar */ + if (id == "sidebar") { + document.getElementById("main").style.marginLeft = "0px"; + } + } else { + /* It's hidden, show it */ + x.className = x.className.replace("is-hidden", "is-visible"); + if (id == "sidebar") { + document.getElementById("main").style.marginLeft = "200px"; + } + } +} + +/* https://github.com/jloh/bulma-prefers-dark/issues/22#issuecomment-663790065 */ +function toggle_darkness() { + let darkLink = document.getElementById('dark-theme'); + if(darkLink){ + darkLink.remove(); + }else{ + darkLink = document.createElement('link'); + darkLink.crossOrigin = "anonymous"; + darkLink.href = "https://cdnjs.cloudflare.com/ajax/libs/bulma-prefers-dark/0.1.0-beta.1/bulma-prefers-dark.min.css"; + darkLink.id = "dark-theme"; + darkLink.integrity = "sha512-8L9NjgWBr9opkijcN9ZZCzzl7T3hVqji0baeKdTvfq1VN119XV4RNCGGI6vAF8ygQkSK0Qew84toTqqpzmbxUw=="; + darkLink.referrerPolicy="no-referrer"; + darkLink.rel="stylesheet"; + + document.head.appendChild(darkLink); + } +} diff --git a/static/js/tinymce/icons/default/icons.min.js b/static/js/tinymce/icons/default/icons.min.js new file mode 100644 index 0000000..e3750c6 --- /dev/null +++ b/static/js/tinymce/icons/default/icons.min.js @@ -0,0 +1 @@ +tinymce.IconManager.add("default",{icons:{"accessibility-check":'',"accordion-toggle":'',accordion:'',"action-next":'',"action-prev":'',addtag:'',"ai-prompt":'',ai:'',"align-center":'',"align-justify":'',"align-left":'',"align-none":'',"align-right":'',"arrow-left":'',"arrow-right":'',bold:'',bookmark:'',"border-style":'',"border-width":'',brightness:'',browse:'',cancel:'',"cell-background-color":'',"cell-border-color":'',"change-case":'',"character-count":'',"checklist-rtl":'',checklist:'',checkmark:'',"chevron-down":'',"chevron-left":'',"chevron-right":'',"chevron-up":'',close:'',"code-sample":'',"color-levels":'',"color-picker":'',"color-swatch-remove-color":'',"color-swatch":'',"comment-add":'',comment:'',contrast:'',copy:'',crop:'',"cut-column":'',"cut-row":'',cut:'',"document-properties":'',drag:'',"duplicate-column":'',"duplicate-row":'',duplicate:'',"edit-block":'',"edit-image":'',"embed-page":'',embed:'',emoji:'',export:'',fill:'',"flip-horizontally":'',"flip-vertically":'',footnote:'',"format-painter":'',format:'',fullscreen:'',gallery:'',gamma:'',help:'',"highlight-bg-color":'',home:'',"horizontal-rule":'',"image-options":'',image:'',indent:'',info:'',"insert-character":'',"insert-time":'',invert:'',italic:'',language:'',"line-height":'',line:'',link:'',"list-bull-circle":'',"list-bull-default":'',"list-bull-square":'',"list-num-default-rtl":'',"list-num-default":'',"list-num-lower-alpha-rtl":'',"list-num-lower-alpha":'',"list-num-lower-greek-rtl":'',"list-num-lower-greek":'',"list-num-lower-roman-rtl":'',"list-num-lower-roman":'',"list-num-upper-alpha-rtl":'',"list-num-upper-alpha":'',"list-num-upper-roman-rtl":'',"list-num-upper-roman":'',lock:'',ltr:'',minus:'',"more-drawer":'',"new-document":'',"new-tab":'',"non-breaking":'',notice:'',"ordered-list-rtl":'',"ordered-list":'',orientation:'',outdent:'',"page-break":'',paragraph:'',"paste-column-after":'',"paste-column-before":'',"paste-row-after":'',"paste-row-before":'',"paste-text":'',paste:'',"permanent-pen":'',plus:'',preferences:'',preview:'',print:'',quote:'',redo:'',reload:'',"remove-formatting":'',remove:'',"resize-handle":'',resize:'',"restore-draft":'',"rotate-left":'',"rotate-right":'',rtl:'',save:'',search:'',"select-all":'',selected:'',send:'',settings:'',sharpen:'',sourcecode:'',"spell-check":'',"strike-through":'',subscript:'',superscript:'',"table-caption":'',"table-cell-classes":'',"table-cell-properties":'',"table-cell-select-all":'',"table-cell-select-inner":'',"table-classes":'',"table-delete-column":'',"table-delete-row":'',"table-delete-table":'',"table-insert-column-after":'',"table-insert-column-before":'',"table-insert-row-above":'',"table-insert-row-after":'',"table-left-header":'',"table-merge-cells":'',"table-row-numbering-rtl":'',"table-row-numbering":'',"table-row-properties":'',"table-split-cells":'',"table-top-header":'',table:'',"template-add":'',template:'',"temporary-placeholder":'',"text-color":'',"text-size-decrease":'',"text-size-increase":'',toc:'',translate:'',typography:'',underline:'',undo:'',unlink:'',unlock:'',"unordered-list":'',unselected:'',upload:'',user:'',"vertical-align":'',visualblocks:'',visualchars:'',warning:'',"zoom-in":'',"zoom-out":''}}); \ No newline at end of file diff --git a/static/js/tinymce/langs/README.md b/static/js/tinymce/langs/README.md new file mode 100644 index 0000000..cd93d8c --- /dev/null +++ b/static/js/tinymce/langs/README.md @@ -0,0 +1,3 @@ +This is where language files should be placed. + +Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce diff --git a/static/js/tinymce/license.txt b/static/js/tinymce/license.txt new file mode 100644 index 0000000..3a49f66 --- /dev/null +++ b/static/js/tinymce/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/static/js/tinymce/models/dom/model.min.js b/static/js/tinymce/models/dom/model.min.js new file mode 100644 index 0000000..a6b5053 --- /dev/null +++ b/static/js/tinymce/models/dom/model.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.ModelManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(o=n=e,(r=String).prototype.isPrototypeOf(o)||(null===(s=n.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var o,n,r,s})(t)===e,o=e=>t=>typeof t===e,n=e=>t=>e===t,r=t("string"),s=t("object"),l=t("array"),a=n(null),c=o("boolean"),i=n(void 0),m=e=>!(e=>null==e)(e),d=o("function"),u=o("number"),f=()=>{},g=e=>()=>e,h=e=>e,p=(e,t)=>e===t;function w(e,...t){return(...o)=>{const n=t.concat(o);return e.apply(null,n)}}const b=e=>t=>!e(t),v=e=>e(),y=g(!1),x=g(!0);class C{constructor(e,t){this.tag=e,this.value=t}static some(e){return new C(!0,e)}static none(){return C.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?C.some(e(this.value)):C.none()}bind(e){return this.tag?e(this.value):C.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:C.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return m(e)?C.some(e):C.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}C.singletonNone=new C(!1);const S=Array.prototype.slice,T=Array.prototype.indexOf,R=Array.prototype.push,D=(e,t)=>{return o=e,n=t,T.call(o,n)>-1;var o,n},O=(e,t)=>{for(let o=0,n=e.length;o{const o=[];for(let n=0;n{const o=e.length,n=new Array(o);for(let r=0;r{for(let o=0,n=e.length;o{const o=[],n=[];for(let r=0,s=e.length;r{const o=[];for(let n=0,r=e.length;n(((e,t)=>{for(let o=e.length-1;o>=0;o--)t(e[o],o)})(e,((e,n)=>{o=t(o,e,n)})),o),A=(e,t,o)=>(N(e,((e,n)=>{o=t(o,e,n)})),o),L=(e,t)=>((e,t,o)=>{for(let n=0,r=e.length;n{for(let o=0,n=e.length;o{const t=[];for(let o=0,n=e.length;oM(E(e,t)),P=(e,t)=>{for(let o=0,n=e.length;o{const o={};for(let n=0,r=e.length;nt>=0&&tF(e,0),$=e=>F(e,e.length-1),V=(e,t)=>{for(let o=0;o{const o=q(e);for(let n=0,r=o.length;nY(e,((e,o)=>({k:o,v:t(e,o)}))),Y=(e,t)=>{const o={};return G(e,((e,n)=>{const r=t(e,n);o[r.k]=r.v})),o},J=(e,t)=>{const o=[];return G(e,((e,n)=>{o.push(t(e,n))})),o},Q=e=>J(e,h),X=(e,t)=>U.call(e,t),Z="undefined"!=typeof window?window:Function("return this;")(),ee=(e,t)=>((e,t)=>{let o=null!=t?t:Z;for(let t=0;t{const t=ee("ownerDocument.defaultView",e);return s(e)&&((e=>((e,t)=>{const o=((e,t)=>ee(e,t))(e,t);if(null==o)throw new Error(e+" not available on this browser");return o})("HTMLElement",e))(t).prototype.isPrototypeOf(e)||/^HTML\w*Element$/.test(te(e).constructor.name))},ne=e=>e.dom.nodeName.toLowerCase(),re=e=>e.dom.nodeType,se=e=>t=>re(t)===e,le=e=>8===re(e)||"#comment"===ne(e),ae=e=>ce(e)&&oe(e.dom),ce=se(1),ie=se(3),me=se(9),de=se(11),ue=e=>t=>ce(t)&&ne(t)===e,fe=(e,t,o)=>{if(!(r(o)||c(o)||u(o)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",o,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,o+"")},ge=(e,t,o)=>{fe(e.dom,t,o)},he=(e,t)=>{const o=e.dom;G(t,((e,t)=>{fe(o,t,e)}))},pe=(e,t)=>{const o=e.dom.getAttribute(t);return null===o?void 0:o},we=(e,t)=>C.from(pe(e,t)),be=(e,t)=>{e.dom.removeAttribute(t)},ve=e=>A(e.dom.attributes,((e,t)=>(e[t.name]=t.value,e)),{}),ye=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},xe={fromHtml:(e,t)=>{const o=(t||document).createElement("div");if(o.innerHTML=e,!o.hasChildNodes()||o.childNodes.length>1){const t="HTML does not have a single root node";throw console.error(t,e),new Error(t)}return ye(o.childNodes[0])},fromTag:(e,t)=>{const o=(t||document).createElement(e);return ye(o)},fromText:(e,t)=>{const o=(t||document).createTextNode(e);return ye(o)},fromDom:ye,fromPoint:(e,t,o)=>C.from(e.dom.elementFromPoint(t,o)).map(ye)},Ce=(e,t)=>{const o=e.dom;if(1!==o.nodeType)return!1;{const e=o;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},Se=e=>1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType||0===e.childElementCount,Te=(e,t)=>{const o=void 0===t?document:t.dom;return Se(o)?C.none():C.from(o.querySelector(e)).map(xe.fromDom)},Re=(e,t)=>e.dom===t.dom,De=(e,t)=>{const o=e.dom,n=t.dom;return o!==n&&o.contains(n)},Oe=Ce,ke=e=>xe.fromDom(e.dom.ownerDocument),Ee=e=>me(e)?e:ke(e),Ne=e=>C.from(e.dom.parentNode).map(xe.fromDom),Be=e=>C.from(e.dom.parentElement).map(xe.fromDom),_e=(e,t)=>{const o=d(t)?t:y;let n=e.dom;const r=[];for(;null!==n.parentNode&&void 0!==n.parentNode;){const e=n.parentNode,t=xe.fromDom(e);if(r.push(t),!0===o(t))break;n=e}return r},ze=e=>C.from(e.dom.previousSibling).map(xe.fromDom),Ae=e=>C.from(e.dom.nextSibling).map(xe.fromDom),Le=e=>E(e.dom.childNodes,xe.fromDom),We=(e,t)=>{const o=e.dom.childNodes;return C.from(o[t]).map(xe.fromDom)},Me=(e,t)=>{Ne(e).each((o=>{o.dom.insertBefore(t.dom,e.dom)}))},je=(e,t)=>{Ae(e).fold((()=>{Ne(e).each((e=>{Ie(e,t)}))}),(e=>{Me(e,t)}))},Pe=(e,t)=>{const o=(e=>We(e,0))(e);o.fold((()=>{Ie(e,t)}),(o=>{e.dom.insertBefore(t.dom,o.dom)}))},Ie=(e,t)=>{e.dom.appendChild(t.dom)},Fe=(e,t)=>{Me(e,t),Ie(t,e)},He=(e,t)=>{N(t,((o,n)=>{const r=0===n?e:t[n-1];je(r,o)}))},$e=(e,t)=>{N(t,(t=>{Ie(e,t)}))},Ve=e=>{e.dom.textContent="",N(Le(e),(e=>{qe(e)}))},qe=e=>{const t=e.dom;null!==t.parentNode&&t.parentNode.removeChild(t)},Ue=e=>{const t=Le(e);t.length>0&&He(e,t),qe(e)},Ge=(e,t)=>xe.fromDom(e.dom.cloneNode(t)),Ke=e=>Ge(e,!1),Ye=e=>Ge(e,!0),Je=(e,t)=>{const o=xe.fromTag(t),n=ve(e);return he(o,n),o},Qe=["tfoot","thead","tbody","colgroup"],Xe=(e,t,o)=>({element:e,rowspan:t,colspan:o}),Ze=(e,t,o)=>({element:e,cells:t,section:o}),et=(e,t,o)=>({element:e,isNew:t,isLocked:o}),tt=(e,t,o,n)=>({element:e,cells:t,section:o,isNew:n}),ot=d(Element.prototype.attachShadow)&&d(Node.prototype.getRootNode),nt=g(ot),rt=ot?e=>xe.fromDom(e.dom.getRootNode()):Ee,st=e=>xe.fromDom(e.dom.host),lt=e=>{const t=ie(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const o=t.ownerDocument;return(e=>{const t=rt(e);return de(o=t)&&m(o.dom.host)?C.some(t):C.none();var o})(xe.fromDom(t)).fold((()=>o.body.contains(t)),(n=lt,r=st,e=>n(r(e))));var n,r},at=e=>{const t=e.dom.body;if(null==t)throw new Error("Body is not available yet");return xe.fromDom(t)},ct=(e,t)=>{let o=[];return N(Le(e),(e=>{t(e)&&(o=o.concat([e])),o=o.concat(ct(e,t))})),o},it=(e,t,o)=>((e,o,n)=>_(_e(e,n),(e=>Ce(e,t))))(e,0,o),mt=(e,t)=>((e,o)=>_(Le(e),(e=>Ce(e,t))))(e),dt=(e,t)=>((e,t)=>{const o=void 0===t?document:t.dom;return Se(o)?[]:E(o.querySelectorAll(e),xe.fromDom)})(t,e);var ut=(e,t,o,n,r)=>e(o,n)?C.some(o):d(r)&&r(o)?C.none():t(o,n,r);const ft=(e,t,o)=>{let n=e.dom;const r=d(o)?o:y;for(;n.parentNode;){n=n.parentNode;const e=xe.fromDom(n);if(t(e))return C.some(e);if(r(e))break}return C.none()},gt=(e,t,o)=>ut(((e,t)=>t(e)),ft,e,t,o),ht=(e,t,o)=>ft(e,(e=>Ce(e,t)),o),pt=(e,t)=>((e,o)=>L(e.dom.childNodes,(e=>{return o=xe.fromDom(e),Ce(o,t);var o})).map(xe.fromDom))(e),wt=(e,t)=>Te(t,e),bt=(e,t,o)=>ut(((e,t)=>Ce(e,t)),ht,e,t,o),vt=(e,t,o=p)=>e.exists((e=>o(e,t))),yt=e=>{const t=[],o=e=>{t.push(e)};for(let t=0;te?C.some(t):C.none(),Ct=(e,t,o)=>""===t||e.length>=t.length&&e.substr(o,o+t.length)===t,St=(e,t,o=0,n)=>{const r=e.indexOf(t,o);return-1!==r&&(!!i(n)||r+t.length<=n)},Tt=(e,t)=>Ct(e,t,0),Rt=(e,t)=>Ct(e,t,e.length-t.length),Dt=(e=>t=>t.replace(e,""))(/^\s+|\s+$/g),Ot=e=>e.length>0,kt=e=>void 0!==e.style&&d(e.style.getPropertyValue),Et=(e,t,o)=>{if(!r(o))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",o,":: Element ",e),new Error("CSS value must be a string: "+o);kt(e)&&e.style.setProperty(t,o)},Nt=(e,t,o)=>{const n=e.dom;Et(n,t,o)},Bt=(e,t)=>{const o=e.dom;G(t,((e,t)=>{Et(o,t,e)}))},_t=(e,t)=>{const o=e.dom,n=window.getComputedStyle(o).getPropertyValue(t);return""!==n||lt(e)?n:zt(o,t)},zt=(e,t)=>kt(e)?e.style.getPropertyValue(t):"",At=(e,t)=>{const o=e.dom,n=zt(o,t);return C.from(n).filter((e=>e.length>0))},Lt=(e,t)=>{((e,t)=>{kt(e)&&e.style.removeProperty(t)})(e.dom,t),vt(we(e,"style").map(Dt),"")&&be(e,"style")},Wt=(e,t,o=0)=>we(e,t).map((e=>parseInt(e,10))).getOr(o),Mt=(e,t)=>Wt(e,t,1),jt=e=>ue("col")(e)?Wt(e,"span",1)>1:Mt(e,"colspan")>1,Pt=e=>Mt(e,"rowspan")>1,It=(e,t)=>parseInt(_t(e,t),10),Ft=g(10),Ht=g(10),$t=(e,t)=>Vt(e,t,x),Vt=(e,t,o)=>j(Le(e),(e=>Ce(e,t)?o(e)?[e]:[]:Vt(e,t,o))),qt=(e,t)=>((e,t,o=y)=>o(t)?C.none():D(e,ne(t))?C.some(t):ht(t,e.join(","),(e=>Ce(e,"table")||o(e))))(["td","th"],e,t),Ut=e=>$t(e,"th,td"),Gt=e=>Ce(e,"colgroup")?mt(e,"col"):j(Jt(e),(e=>mt(e,"col"))),Kt=(e,t)=>bt(e,"table",t),Yt=e=>$t(e,"tr"),Jt=e=>Kt(e).fold(g([]),(e=>mt(e,"colgroup"))),Qt=(e,t)=>E(e,(e=>{if("colgroup"===ne(e)){const t=E(Gt(e),(e=>{const t=Wt(e,"span",1);return Xe(e,1,t)}));return Ze(e,t,"colgroup")}{const o=E(Ut(e),(e=>{const t=Wt(e,"rowspan",1),o=Wt(e,"colspan",1);return Xe(e,t,o)}));return Ze(e,o,t(e))}})),Xt=e=>Ne(e).map((e=>{const t=ne(e);return(e=>D(Qe,e))(t)?t:"tbody"})).getOr("tbody"),Zt=e=>{const t=Yt(e),o=[...Jt(e),...t];return Qt(o,Xt)},eo=e=>{let t,o=!1;return(...n)=>(o||(o=!0,t=e.apply(null,n)),t)},to=()=>oo(0,0),oo=(e,t)=>({major:e,minor:t}),no={nu:oo,detect:(e,t)=>{const o=String(t).toLowerCase();return 0===e.length?to():((e,t)=>{const o=((e,t)=>{for(let o=0;oNumber(t.replace(o,"$"+e));return oo(n(1),n(2))})(e,o)},unknown:to},ro=(e,t)=>{const o=String(t).toLowerCase();return L(e,(e=>e.search(o)))},so=/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,lo=e=>t=>St(t,e),ao=[{name:"Edge",versionRegexes:[/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],search:e=>St(e,"edge/")&&St(e,"chrome")&&St(e,"safari")&&St(e,"applewebkit")},{name:"Chromium",brand:"Chromium",versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/,so],search:e=>St(e,"chrome")&&!St(e,"chromeframe")},{name:"IE",versionRegexes:[/.*?msie\ ?([0-9]+)\.([0-9]+).*/,/.*?rv:([0-9]+)\.([0-9]+).*/],search:e=>St(e,"msie")||St(e,"trident")},{name:"Opera",versionRegexes:[so,/.*?opera\/([0-9]+)\.([0-9]+).*/],search:lo("opera")},{name:"Firefox",versionRegexes:[/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],search:lo("firefox")},{name:"Safari",versionRegexes:[so,/.*?cpu os ([0-9]+)_([0-9]+).*/],search:e=>(St(e,"safari")||St(e,"mobile/"))&&St(e,"applewebkit")}],co=[{name:"Windows",search:lo("win"),versionRegexes:[/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]},{name:"iOS",search:e=>St(e,"iphone")||St(e,"ipad"),versionRegexes:[/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,/.*cpu os ([0-9]+)_([0-9]+).*/,/.*cpu iphone os ([0-9]+)_([0-9]+).*/]},{name:"Android",search:lo("android"),versionRegexes:[/.*?android\ ?([0-9]+)\.([0-9]+).*/]},{name:"macOS",search:lo("mac os x"),versionRegexes:[/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]},{name:"Linux",search:lo("linux"),versionRegexes:[]},{name:"Solaris",search:lo("sunos"),versionRegexes:[]},{name:"FreeBSD",search:lo("freebsd"),versionRegexes:[]},{name:"ChromeOS",search:lo("cros"),versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/]}],io={browsers:g(ao),oses:g(co)},mo="Edge",uo="Chromium",fo="Opera",go="Firefox",ho="Safari",po=e=>{const t=e.current,o=e.version,n=e=>()=>t===e;return{current:t,version:o,isEdge:n(mo),isChromium:n(uo),isIE:n("IE"),isOpera:n(fo),isFirefox:n(go),isSafari:n(ho)}},wo=()=>po({current:void 0,version:no.unknown()}),bo=po,vo=(g(mo),g(uo),g("IE"),g(fo),g(go),g(ho),"Windows"),yo="Android",xo="Linux",Co="macOS",So="Solaris",To="FreeBSD",Ro="ChromeOS",Do=e=>{const t=e.current,o=e.version,n=e=>()=>t===e;return{current:t,version:o,isWindows:n(vo),isiOS:n("iOS"),isAndroid:n(yo),isMacOS:n(Co),isLinux:n(xo),isSolaris:n(So),isFreeBSD:n(To),isChromeOS:n(Ro)}},Oo=()=>Do({current:void 0,version:no.unknown()}),ko=Do,Eo=(g(vo),g("iOS"),g(yo),g(xo),g(Co),g(So),g(To),g(Ro),e=>window.matchMedia(e).matches);let No=eo((()=>((e,t,o)=>{const n=io.browsers(),r=io.oses(),s=t.bind((e=>((e,t)=>V(t.brands,(t=>{const o=t.brand.toLowerCase();return L(e,(e=>{var t;return o===(null===(t=e.brand)||void 0===t?void 0:t.toLowerCase())})).map((e=>({current:e.name,version:no.nu(parseInt(t.version,10),0)})))})))(n,e))).orThunk((()=>((e,t)=>ro(e,t).map((e=>{const o=no.detect(e.versionRegexes,t);return{current:e.name,version:o}})))(n,e))).fold(wo,bo),l=((e,t)=>ro(e,t).map((e=>{const o=no.detect(e.versionRegexes,t);return{current:e.name,version:o}})))(r,e).fold(Oo,ko),a=((e,t,o,n)=>{const r=e.isiOS()&&!0===/ipad/i.test(o),s=e.isiOS()&&!r,l=e.isiOS()||e.isAndroid(),a=l||n("(pointer:coarse)"),c=r||!s&&l&&n("(min-device-width:768px)"),i=s||l&&!c,m=t.isSafari()&&e.isiOS()&&!1===/safari/i.test(o),d=!i&&!c&&!m;return{isiPad:g(r),isiPhone:g(s),isTablet:g(c),isPhone:g(i),isTouch:g(a),isAndroid:e.isAndroid,isiOS:e.isiOS,isWebView:g(m),isDesktop:g(d)}})(l,s,e,o);return{browser:s,os:l,deviceType:a}})(navigator.userAgent,C.from(navigator.userAgentData),Eo)));const Bo=()=>No(),_o=(e,t)=>{const o=o=>{const n=t(o);if(n<=0||null===n){const t=_t(o,e);return parseFloat(t)||0}return n},n=(e,t)=>A(t,((t,o)=>{const n=_t(e,o),r=void 0===n?0:parseInt(n,10);return isNaN(r)?t:t+r}),0);return{set:(t,o)=>{if(!u(o)&&!o.match(/^[0-9]+$/))throw new Error(e+".set accepts only positive integer values. Value was "+o);const n=t.dom;kt(n)&&(n.style[e]=o+"px")},get:o,getOuter:o,aggregate:n,max:(e,t,o)=>{const r=n(e,o);return t>r?t-r:0}}},zo=(e,t,o)=>((e,t)=>(e=>{const t=parseFloat(e);return isNaN(t)?C.none():C.some(t)})(e).getOr(t))(_t(e,t),o),Ao=_o("width",(e=>e.dom.offsetWidth)),Lo=e=>Ao.get(e),Wo=e=>Ao.getOuter(e),Mo=e=>((e,t)=>{const o=e.dom,n=o.getBoundingClientRect().width||o.offsetWidth;return"border-box"===t?n:((e,t,o,n)=>t-zo(e,`padding-${o}`,0)-zo(e,`padding-${n}`,0)-zo(e,`border-${o}-width`,0)-zo(e,`border-${n}-width`,0))(e,n,"left","right")})(e,"content-box"),jo=(e,t,o)=>{const n=e.cells,r=n.slice(0,t),s=n.slice(t),l=r.concat(o).concat(s);return Fo(e,l)},Po=(e,t,o)=>jo(e,t,[o]),Io=(e,t,o)=>{e.cells[t]=o},Fo=(e,t)=>tt(e.element,t,e.section,e.isNew),Ho=(e,t)=>e.cells[t],$o=(e,t)=>Ho(e,t).element,Vo=e=>e.cells.length,qo=e=>{const t=B(e,(e=>"colgroup"===e.section));return{rows:t.fail,cols:t.pass}},Uo=(e,t,o)=>{const n=E(e.cells,o);return tt(t(e.element),n,e.section,!0)},Go="data-snooker-locked-cols",Ko=e=>we(e,Go).bind((e=>C.from(e.match(/\d+/g)))).map((e=>I(e,x))),Yo=e=>{const t=A(qo(e).rows,((e,t)=>(N(t.cells,((t,o)=>{t.isLocked&&(e[o]=!0)})),e)),{}),o=J(t,((e,t)=>parseInt(t,10)));return((e,t)=>{const o=S.call(e,0);return o.sort(void 0),o})(o)},Jo=(e,t)=>e+","+t,Qo=(e,t)=>{const o=j(e.all,(e=>e.cells));return _(o,t)},Xo=e=>{const t={},o=[],n=H(e).map((e=>e.element)).bind(Kt).bind(Ko).getOr({});let r=0,s=0,l=0;const{pass:a,fail:c}=B(e,(e=>"colgroup"===e.section));N(c,(e=>{const a=[];N(e.cells,(e=>{let o=0;for(;void 0!==t[Jo(l,o)];)o++;const r=((e,t)=>X(e,t)&&void 0!==e[t]&&null!==e[t])(n,o.toString()),c=((e,t,o,n,r,s)=>({element:e,rowspan:t,colspan:o,row:n,column:r,isLocked:s}))(e.element,e.rowspan,e.colspan,l,o,r);for(let n=0;n{const t=(e=>{const t={};let o=0;return N(e.cells,(e=>{const n=e.colspan;k(n,(r=>{const s=o+r;t[s]=((e,t,o)=>({element:e,colspan:t,column:o}))(e.element,n,s)})),o+=n})),t})(e),o=((e,t)=>({element:e,columns:t}))(e.element,Q(t));return{colgroups:[o],columns:t}})).getOrThunk((()=>({colgroups:[],columns:{}}))),d=((e,t)=>({rows:e,columns:t}))(r,s);return{grid:d,access:t,all:o,columns:i,colgroups:m}},Zo=e=>{const t=Zt(e);return Xo(t)},en=Xo,tn=(e,t,o)=>C.from(e.access[Jo(t,o)]),on=(e,t,o)=>{const n=Qo(e,(e=>o(t,e.element)));return n.length>0?C.some(n[0]):C.none()},nn=Qo,rn=e=>j(e.all,(e=>e.cells)),sn=e=>Q(e.columns),ln=e=>q(e.columns).length>0,an=(e,t)=>C.from(e.columns[t]),cn=(e,t=x)=>{const o=e.grid,n=k(o.columns,h),r=k(o.rows,h);return E(n,(o=>mn((()=>j(r,(t=>tn(e,t,o).filter((e=>e.column===o)).toArray()))),(e=>1===e.colspan&&t(e.element)),(()=>tn(e,0,o)))))},mn=(e,t,o)=>{const n=e();return L(n,t).orThunk((()=>C.from(n[0]).orThunk(o))).map((e=>e.element))},dn=e=>{const t=e.grid,o=k(t.rows,h),n=k(t.columns,h);return E(o,(t=>mn((()=>j(n,(o=>tn(e,t,o).filter((e=>e.row===t)).fold(g([]),(e=>[e]))))),(e=>1===e.rowspan),(()=>tn(e,t,0)))))},un=(e,t)=>o=>"rtl"===fn(o)?t:e,fn=e=>"rtl"===_t(e,"direction")?"rtl":"ltr",gn=_o("height",(e=>{const t=e.dom;return lt(e)?t.getBoundingClientRect().height:t.offsetHeight})),hn=e=>gn.get(e),pn=e=>gn.getOuter(e),wn=(e,t)=>({left:e,top:t,translate:(o,n)=>wn(e+o,t+n)}),bn=wn,vn=(e,t)=>void 0!==e?e:void 0!==t?t:0,yn=e=>{const t=e.dom.ownerDocument,o=t.body,n=t.defaultView,r=t.documentElement;if(o===e.dom)return bn(o.offsetLeft,o.offsetTop);const s=vn(null==n?void 0:n.pageYOffset,r.scrollTop),l=vn(null==n?void 0:n.pageXOffset,r.scrollLeft),a=vn(r.clientTop,o.clientTop),c=vn(r.clientLeft,o.clientLeft);return xn(e).translate(l-c,s-a)},xn=e=>{const t=e.dom,o=t.ownerDocument.body;return o===t?bn(o.offsetLeft,o.offsetTop):lt(e)?(e=>{const t=e.getBoundingClientRect();return bn(t.left,t.top)})(t):bn(0,0)},Cn=(e,t)=>({row:e,y:t}),Sn=(e,t)=>({col:e,x:t}),Tn=e=>yn(e).left+Wo(e),Rn=e=>yn(e).left,Dn=(e,t)=>Sn(e,Rn(t)),On=(e,t)=>Sn(e,Tn(t)),kn=e=>yn(e).top,En=(e,t)=>Cn(e,kn(t)),Nn=(e,t)=>Cn(e,kn(t)+pn(t)),Bn=(e,t,o)=>{if(0===o.length)return[];const n=E(o.slice(1),((t,o)=>t.map((t=>e(o,t))))),r=o[o.length-1].map((e=>t(o.length-1,e)));return n.concat([r])},_n={delta:h,positions:e=>Bn(En,Nn,e),edge:kn},zn=un({delta:h,edge:Rn,positions:e=>Bn(Dn,On,e)},{delta:e=>-e,edge:Tn,positions:e=>Bn(On,Dn,e)}),An={delta:(e,t)=>zn(t).delta(e,t),positions:(e,t)=>zn(t).positions(e,t),edge:e=>zn(e).edge(e)},Ln={unsupportedLength:["em","ex","cap","ch","ic","rem","lh","rlh","vw","vh","vi","vb","vmin","vmax","cm","mm","Q","in","pc","pt","px"],fixed:["px","pt"],relative:["%"],empty:[""]},Wn=(()=>{const e="[0-9]+",t="[eE][+-]?"+e,o=e=>`(?:${e})?`,n=["Infinity",e+"\\."+o(e)+o(t),"\\."+e+o(t),e+o(t)].join("|");return new RegExp(`^([+-]?(?:${n}))(.*)$`)})(),Mn=/(\d+(\.\d+)?)%/,jn=/(\d+(\.\d+)?)px|em/,Pn=ue("col"),In=(e,t,o)=>{const n=Be(e).getOrThunk((()=>at(ke(e))));return t(e)/o(n)*100},Fn=(e,t)=>{Nt(e,"width",t+"px")},Hn=(e,t)=>{Nt(e,"width",t+"%")},$n=(e,t)=>{Nt(e,"height",t+"px")},Vn=e=>{const t=(e=>{return zo(t=e,"height",t.dom.offsetHeight)+"px";var t})(e);return t?((e,t,o,n)=>{const r=parseFloat(e);return Rt(e,"%")&&"table"!==ne(t)?((e,t,o,n)=>{const r=Kt(e).map((e=>{const n=o(e);return Math.floor(t/100*n)})).getOr(t);return n(e,r),r})(t,r,o,n):r})(t,e,hn,$n):hn(e)},qn=(e,t)=>At(e,t).orThunk((()=>we(e,t).map((e=>e+"px")))),Un=e=>qn(e,"width"),Gn=e=>In(e,Lo,Mo),Kn=e=>{return Pn(e)?Lo(e):zo(t=e,"width",t.dom.offsetWidth);var t},Yn=e=>((e,t,o)=>o(e)/Mt(e,"rowspan"))(e,0,Vn),Jn=(e,t,o)=>{Nt(e,"width",t+o)},Qn=e=>In(e,Lo,Mo)+"%",Xn=g(Mn),Zn=ue("col"),er=e=>Un(e).getOrThunk((()=>Kn(e)+"px")),tr=e=>{return(t=e,qn(t,"height")).getOrThunk((()=>Yn(e)+"px"));var t},or=(e,t,o,n,r,s)=>e.filter(n).fold((()=>s(((e,t)=>{if(t<0||t>=e.length-1)return C.none();const o=e[t].fold((()=>{const o=(e=>{const t=S.call(e,0);return t.reverse(),t})(e.slice(0,t));return V(o,((e,t)=>e.map((e=>({value:e,delta:t+1})))))}),(e=>C.some({value:e,delta:0}))),n=e[t+1].fold((()=>{const o=e.slice(t+1);return V(o,((e,t)=>e.map((e=>({value:e,delta:t+1})))))}),(e=>C.some({value:e,delta:1})));return o.bind((e=>n.map((t=>{const o=t.delta+e.delta;return Math.abs(t.value-e.value)/o}))))})(o,t))),(e=>r(e))),nr=(e,t,o,n)=>{const r=cn(e),s=ln(e)?(e=>E(sn(e),(e=>C.from(e.element))))(e):r,l=[C.some(An.edge(t))].concat(E(An.positions(r,t),(e=>e.map((e=>e.x))))),a=b(jt);return E(s,((e,t)=>or(e,t,l,a,(e=>{if((e=>{const t=Bo().browser,o=t.isChromium()||t.isFirefox();return!Zn(e)||o})(e))return o(e);{const e=null!=(s=r[t])?h(s):C.none();return or(e,t,l,a,(e=>n(C.some(Lo(e)))),n)}var s}),n)))},rr=e=>e.map((e=>e+"px")).getOr(""),sr=(e,t,o)=>nr(e,t,Kn,(e=>e.getOrThunk(o.minCellWidth))),lr=(e,t,o,n,r)=>{const s=dn(e),l=[C.some(o.edge(t))].concat(E(o.positions(s,t),(e=>e.map((e=>e.y)))));return E(s,((e,t)=>or(e,t,l,b(Pt),n,r)))},ar=(e,t)=>()=>lt(e)?t(e):parseFloat(At(e,"width").getOr("0")),cr=e=>{const t=ar(e,(e=>parseFloat(Qn(e)))),o=ar(e,Lo);return{width:t,pixelWidth:o,getWidths:(t,o)=>((e,t,o)=>nr(e,t,Gn,(e=>e.fold((()=>o.minCellWidth()),(e=>e/o.pixelWidth()*100)))))(t,e,o),getCellDelta:e=>e/o()*100,singleColumnWidth:(e,t)=>[100-e],minCellWidth:()=>Ft()/o()*100,setElementWidth:Hn,adjustTableWidth:o=>{const n=t();Hn(e,n+o/100*n)},isRelative:!0,label:"percent"}},ir=e=>{const t=ar(e,Lo);return{width:t,pixelWidth:t,getWidths:(t,o)=>sr(t,e,o),getCellDelta:h,singleColumnWidth:(e,t)=>[Math.max(Ft(),e+t)-e],minCellWidth:Ft,setElementWidth:Fn,adjustTableWidth:o=>{const n=t()+o;Fn(e,n)},isRelative:!1,label:"pixel"}},mr=e=>Un(e).fold((()=>(e=>{const t=ar(e,Lo),o=g(0);return{width:t,pixelWidth:t,getWidths:(t,o)=>sr(t,e,o),getCellDelta:o,singleColumnWidth:g([0]),minCellWidth:o,setElementWidth:f,adjustTableWidth:f,isRelative:!0,label:"none"}})(e)),(t=>((e,t)=>null!==Xn().exec(t)?cr(e):ir(e))(e,t))),dr=ir,ur=cr,fr=(e,t,o)=>{const n=e[o].element,r=xe.fromTag("td");Ie(r,xe.fromTag("br")),(t?Ie:Pe)(n,r)},gr=((e,t)=>{const o=t=>e(t)?C.from(t.dom.nodeValue):C.none();return{get:t=>{if(!e(t))throw new Error("Can only get text value of a text node");return o(t).getOr("")},getOption:o,set:(t,o)=>{if(!e(t))throw new Error("Can only set raw text value of a text node");t.dom.nodeValue=o}}})(ie),hr=e=>gr.get(e),pr=e=>gr.getOption(e),wr=(e,t)=>gr.set(e,t),br=e=>"img"===ne(e)?1:pr(e).fold((()=>Le(e).length),(e=>e.length)),vr=["img","br"],yr=e=>pr(e).filter((e=>0!==e.trim().length||e.indexOf("\xa0")>-1)).isSome()||D(vr,ne(e))||(e=>ae(e)&&"false"===pe(e,"contenteditable"))(e),xr=e=>((e,t)=>{const o=e=>{for(let n=0;nSr(e,yr),Sr=(e,t)=>{const o=e=>{const n=Le(e);for(let e=n.length-1;e>=0;e--){const r=n[e];if(t(r))return C.some(r);const s=o(r);if(s.isSome())return s}return C.none()};return o(e)},Tr={scope:["row","col"]},Rr=e=>()=>{const t=xe.fromTag("td",e.dom);return Ie(t,xe.fromTag("br",e.dom)),t},Dr=e=>()=>xe.fromTag("col",e.dom),Or=e=>()=>xe.fromTag("colgroup",e.dom),kr=e=>()=>xe.fromTag("tr",e.dom),Er=(e,t,o)=>{const n=((e,t)=>{const o=Je(e,t),n=Le(Ye(e));return $e(o,n),o})(e,t);return G(o,((e,t)=>{null===e?be(n,t):ge(n,t,e)})),n},Nr=e=>e,Br=(e,t,o)=>{const n=(e,t)=>{((e,t)=>{const o=e.dom,n=t.dom;kt(o)&&kt(n)&&(n.style.cssText=o.style.cssText)})(e.element,t),Lt(t,"height"),1!==e.colspan&&Lt(t,"width")};return{col:o=>{const r=xe.fromTag(ne(o.element),t.dom);return n(o,r),e(o.element,r),r},colgroup:Or(t),row:kr(t),cell:r=>{const s=xe.fromTag(ne(r.element),t.dom),l=o.getOr(["strong","em","b","i","span","font","h1","h2","h3","h4","h5","h6","p","div"]),a=l.length>0?((e,t,o)=>xr(e).map((n=>{const r=o.join(","),s=it(n,r,(t=>Re(t,e)));return z(s,((e,t)=>{const o=Ke(t);return Ie(e,o),o}),t)})).getOr(t))(r.element,s,l):s;return Ie(a,xe.fromTag("br")),n(r,s),((e,t)=>{G(Tr,((o,n)=>we(e,n).filter((e=>D(o,e))).each((e=>ge(t,n,e)))))})(r.element,s),e(r.element,s),s},replace:Er,colGap:Dr(t),gap:Rr(t)}},_r=e=>({col:Dr(e),colgroup:Or(e),row:kr(e),cell:Rr(e),replace:Nr,colGap:Dr(e),gap:Rr(e)}),zr=e=>t=>t.options.get(e),Ar="100%",Lr=e=>{var t;const o=e.dom,n=null!==(t=o.getParent(e.selection.getStart(),o.isBlock))&&void 0!==t?t:e.getBody();return Mo(xe.fromDom(n))+"px"},Wr=e=>C.from(e.options.get("table_clone_elements")),Mr=zr("table_header_type"),jr=zr("table_column_resizing"),Pr=e=>"preservetable"===jr(e),Ir=e=>"resizetable"===jr(e),Fr=zr("table_sizing_mode"),Hr=e=>"relative"===Fr(e),$r=e=>"fixed"===Fr(e),Vr=e=>"responsive"===Fr(e),qr=zr("table_resize_bars"),Ur=zr("table_style_by_css"),Gr=zr("table_merge_content_on_paste"),Kr=e=>{const t=e.options,o=t.get("table_default_attributes");return t.isSet("table_default_attributes")?o:((e,t)=>Vr(e)||Ur(e)?t:$r(e)?{...t,width:Lr(e)}:{...t,width:Ar})(e,o)},Yr=zr("table_use_colgroups"),Jr=e=>bt(e,"[contenteditable]"),Qr=(e,t=!1)=>lt(e)?e.dom.isContentEditable:Jr(e).fold(g(t),(e=>"true"===Xr(e))),Xr=e=>e.dom.contentEditable,Zr=e=>xe.fromDom(e.getBody()),es=e=>t=>Re(t,Zr(e)),ts=e=>{be(e,"data-mce-style");const t=e=>be(e,"data-mce-style");N(Ut(e),t),N(Gt(e),t),N(Yt(e),t)},os=e=>xe.fromDom(e.selection.getStart()),ns=e=>e.getBoundingClientRect().width,rs=e=>e.getBoundingClientRect().height,ss=e=>gt(e,ue("table")).exists(Qr),ls=(e,t)=>{const o=t.column,n=t.column+t.colspan-1,r=t.row,s=t.row+t.rowspan-1;return o<=e.finishCol&&n>=e.startCol&&r<=e.finishRow&&s>=e.startRow},as=(e,t)=>t.column>=e.startCol&&t.column+t.colspan-1<=e.finishCol&&t.row>=e.startRow&&t.row+t.rowspan-1<=e.finishRow,cs=(e,t,o)=>{const n=on(e,t,Re),r=on(e,o,Re);return n.bind((e=>r.map((t=>{return o=e,n=t,{startRow:Math.min(o.row,n.row),startCol:Math.min(o.column,n.column),finishRow:Math.max(o.row+o.rowspan-1,n.row+n.rowspan-1),finishCol:Math.max(o.column+o.colspan-1,n.column+n.colspan-1)};var o,n}))))},is=(e,t,o)=>cs(e,t,o).map((t=>{const o=nn(e,w(ls,t));return E(o,(e=>e.element))})),ms=(e,t)=>on(e,t,((e,t)=>De(t,e))).map((e=>e.element)),ds=(e,t,o)=>{const n=fs(e);return is(n,t,o)},us=(e,t,o,n,r)=>{const s=fs(e),l=Re(e,o)?C.some(t):ms(s,t),a=Re(e,r)?C.some(n):ms(s,n);return l.bind((e=>a.bind((t=>is(s,e,t)))))},fs=Zo;var gs=["body","p","div","article","aside","figcaption","figure","footer","header","nav","section","ol","ul","li","table","thead","tbody","tfoot","caption","tr","td","th","h1","h2","h3","h4","h5","h6","blockquote","pre","address"],hs=()=>({up:g({selector:ht,closest:bt,predicate:ft,all:_e}),down:g({selector:dt,predicate:ct}),styles:g({get:_t,getRaw:At,set:Nt,remove:Lt}),attrs:g({get:pe,set:ge,remove:be,copyTo:(e,t)=>{const o=ve(e);he(t,o)}}),insert:g({before:Me,after:je,afterAll:He,append:Ie,appendAll:$e,prepend:Pe,wrap:Fe}),remove:g({unwrap:Ue,remove:qe}),create:g({nu:xe.fromTag,clone:e=>xe.fromDom(e.dom.cloneNode(!1)),text:xe.fromText}),query:g({comparePosition:(e,t)=>e.dom.compareDocumentPosition(t.dom),prevSibling:ze,nextSibling:Ae}),property:g({children:Le,name:ne,parent:Ne,document:e=>Ee(e).dom,isText:ie,isComment:le,isElement:ce,isSpecial:e=>{const t=ne(e);return D(["script","noscript","iframe","noframes","noembed","title","style","textarea","xmp"],t)},getLanguage:e=>ce(e)?we(e,"lang"):C.none(),getText:hr,setText:wr,isBoundary:e=>!!ce(e)&&("body"===ne(e)||D(gs,ne(e))),isEmptyTag:e=>!!ce(e)&&D(["br","img","hr","input"],ne(e)),isNonEditable:e=>ce(e)&&"false"===pe(e,"contenteditable")}),eq:Re,is:Oe});const ps=(e,t,o,n)=>{const r=t(e,o);return z(n,((o,n)=>{const r=t(e,n);return ws(e,o,r)}),r)},ws=(e,t,o)=>t.bind((t=>o.filter(w(e.eq,t)))),bs=hs(),vs=(e,t)=>((e,t,o)=>o.length>0?((e,t,o,n)=>n(e,t,o[0],o.slice(1)))(e,t,o,ps):C.none())(bs,((t,o)=>e(o)),t),ys=e=>ht(e,"table"),xs=(e,t,o)=>{const n=e=>t=>void 0!==o&&o(t)||Re(t,e);return Re(e,t)?C.some({boxes:C.some([e]),start:e,finish:t}):ys(e).bind((r=>ys(t).bind((s=>{if(Re(r,s))return C.some({boxes:ds(r,e,t),start:e,finish:t});if(De(r,s)){const o=it(t,"td,th",n(r)),l=o.length>0?o[o.length-1]:t;return C.some({boxes:us(r,e,r,t,s),start:e,finish:l})}if(De(s,r)){const o=it(e,"td,th",n(s)),l=o.length>0?o[o.length-1]:e;return C.some({boxes:us(s,e,r,t,s),start:e,finish:l})}return((e,t,o)=>((e,t,o,n=y)=>{const r=[t].concat(e.up().all(t)),s=[o].concat(e.up().all(o)),l=e=>W(e,n).fold((()=>e),(t=>e.slice(0,t+1))),a=l(r),c=l(s),i=L(a,(t=>O(c,((e,t)=>w(e.eq,t))(e,t))));return{firstpath:a,secondpath:c,shared:i}})(bs,e,t,void 0))(e,t).shared.bind((l=>bt(l,"table",o).bind((o=>{const l=it(t,"td,th",n(o)),a=l.length>0?l[l.length-1]:t,c=it(e,"td,th",n(o)),i=c.length>0?c[c.length-1]:e;return C.some({boxes:us(o,e,r,t,s),start:i,finish:a})}))))}))))},Cs=(e,t)=>{const o=dt(e,t);return o.length>0?C.some(o):C.none()},Ss=(e,t,o)=>wt(e,t).bind((t=>wt(e,o).bind((e=>vs(ys,[t,e]).map((o=>({first:t,last:e,table:o}))))))),Ts=(e,t,o,n,r)=>((e,t)=>L(e,(e=>Ce(e,t))))(e,r).bind((e=>((e,t,o)=>Kt(e).bind((n=>((e,t,o,n)=>on(e,t,Re).bind((t=>{const r=o>0?t.row+t.rowspan-1:t.row,s=n>0?t.column+t.colspan-1:t.column;return tn(e,r+o,s+n).map((e=>e.element))})))(fs(n),e,t,o))))(e,t,o).bind((e=>((e,t)=>ht(e,"table").bind((o=>wt(o,t).bind((t=>xs(t,e).bind((e=>e.boxes.map((t=>({boxes:t,start:e.start,finish:e.finish}))))))))))(e,n))))),Rs=(e,t)=>Cs(e,t),Ds=(e,t,o)=>Ss(e,t,o).bind((t=>{const o=t=>Re(e,t),n="thead,tfoot,tbody,table",r=ht(t.first,n,o),s=ht(t.last,n,o);return r.bind((e=>s.bind((o=>Re(e,o)?((e,t,o)=>((e,t,o)=>cs(e,t,o).bind((t=>((e,t)=>{let o=!0;const n=w(as,t);for(let r=t.startRow;r<=t.finishRow;r++)for(let s=t.startCol;s<=t.finishCol;s++)o=o&&tn(e,r,s).exists(n);return o?C.some(t):C.none()})(e,t))))(fs(e),t,o))(t.table,t.first,t.last):C.none()))))})),Os=h,ks=e=>{const t=(e,t)=>we(e,t).exists((e=>parseInt(e,10)>1));return e.length>0&&P(e,(e=>t(e,"rowspan")||t(e,"colspan")))?C.some(e):C.none()},Es=(e,t,o)=>t.length<=1?C.none():Ds(e,o.firstSelectedSelector,o.lastSelectedSelector).map((e=>({bounds:e,cells:t}))),Ns="data-mce-selected",Bs="data-mce-first-selected",_s="data-mce-last-selected",zs="["+Ns+"]",As={selected:Ns,selectedSelector:"td["+Ns+"],th["+Ns+"]",firstSelected:Bs,firstSelectedSelector:"td["+Bs+"],th["+Bs+"]",lastSelected:_s,lastSelectedSelector:"td["+_s+"],th["+_s+"]"},Ls=(e,t,o)=>({element:o,mergable:Es(t,e,As),unmergable:ks(e),selection:Os(e)}),Ws=e=>(t,o)=>{const n=ne(t),r="col"===n||"colgroup"===n?Kt(s=t).bind((e=>Rs(e,As.firstSelectedSelector))).fold(g(s),(e=>e[0])):t;var s;return bt(r,e,o)},Ms=Ws("th,td,caption"),js=Ws("th,td"),Ps=e=>{return t=e.model.table.getSelectedCells(),E(t,xe.fromDom);var t},Is=(e,t)=>{e.on("BeforeGetContent",(t=>{const o=o=>{t.preventDefault(),(e=>Kt(e[0]).map((e=>{const t=((e,t)=>{const o=e=>Ce(e.element,t),n=Ye(e),r=Zt(n),s=mr(e),l=en(r),a=((e,t)=>{const o=e.grid.columns;let n=e.grid.rows,r=o,s=0,l=0;const a=[],c=[];return G(e.access,(e=>{if(a.push(e),t(e)){c.push(e);const t=e.row,o=t+e.rowspan-1,a=e.column,i=a+e.colspan-1;ts&&(s=o),al&&(l=i)}})),((e,t,o,n,r,s)=>({minRow:e,minCol:t,maxRow:o,maxCol:n,allCells:r,selectedCells:s}))(n,r,s,l,a,c)})(l,o),c="th:not("+t+"),td:not("+t+")",i=Vt(n,"th,td",(e=>Ce(e,c)));N(i,qe),((e,t,o,n)=>{const r=_(e,(e=>"colgroup"!==e.section)),s=t.grid.columns,l=t.grid.rows;for(let e=0;eo.maxRow||ao.maxCol||(tn(t,e,a).filter(n).isNone()?fr(r,l,e):l=!0)}})(r,l,a,o);const m=((e,t,o,n)=>{if(0===n.minCol&&t.grid.columns===n.maxCol+1)return 0;const r=sr(t,e,o),s=A(r,((e,t)=>e+t),0),l=A(r.slice(n.minCol,n.maxCol+1),((e,t)=>e+t),0),a=l/s*o.pixelWidth()-o.pixelWidth();return o.getCellDelta(a)})(e,Zo(e),s,a);return((e,t,o,n)=>{G(o.columns,(e=>{(e.columnt.maxCol)&&qe(e.element)}));const r=_($t(e,"tr"),(e=>0===e.dom.childElementCount));N(r,qe),t.minCol!==t.maxCol&&t.minRow!==t.maxRow||N($t(e,"th,td"),(e=>{be(e,"rowspan"),be(e,"colspan")})),be(e,Go),be(e,"data-snooker-col-series"),mr(e).adjustTableWidth(n)})(n,a,l,m),n})(e,zs);return ts(t),[t]})))(o).each((o=>{t.content="text"===t.format?(e=>E(e,(e=>e.dom.innerText)).join(""))(o):((e,t)=>E(t,(t=>e.selection.serializer.serialize(t.dom,{}))).join(""))(e,o)}))};if(!0===t.selection){const t=(e=>_(Ps(e),(e=>Ce(e,As.selectedSelector))))(e);t.length>=1&&o(t)}})),e.on("BeforeSetContent",(o=>{if(!0===o.selection&&!0===o.paste){const n=Ps(e);H(n).each((n=>{Kt(n).each((r=>{const s=_(((e,t)=>{const o=document.createElement("div");return o.innerHTML=e,Le(xe.fromDom(o))})(o.content),(e=>"meta"!==ne(e))),l=ue("table");if(Gr(e)&&1===s.length&&l(s[0])){o.preventDefault();const l=xe.fromDom(e.getDoc()),a=_r(l),c=((e,t,o)=>({element:e,clipboard:t,generators:o}))(n,s[0],a);t.pasteCells(r,c).each((()=>{e.focus()}))}}))}))}}))},Fs=(e,t)=>({element:e,offset:t}),Hs=(e,t,o)=>e.property().isText(t)&&0===e.property().getText(t).trim().length||e.property().isComment(t)?o(t).bind((t=>Hs(e,t,o).orThunk((()=>C.some(t))))):C.none(),$s=(e,t)=>e.property().isText(t)?e.property().getText(t).length:e.property().children(t).length,Vs=(e,t)=>{const o=Hs(e,t,e.query().prevSibling).getOr(t);if(e.property().isText(o))return Fs(o,$s(e,o));const n=e.property().children(o);return n.length>0?Vs(e,n[n.length-1]):Fs(o,$s(e,o))},qs=Vs,Us=hs(),Gs=(e,t)=>{if(!jt(e)){const o=(e=>Un(e).bind((e=>{return t=e,o=["fixed","relative","empty"],C.from(Wn.exec(t)).bind((e=>{const t=Number(e[1]),n=e[2];return((e,t)=>O(t,(t=>O(Ln[t],(t=>e===t)))))(n,o)?C.some({value:t,unit:n}):C.none()}));var t,o})))(e);o.each((o=>{const n=o.value/2;Jn(e,n,o.unit),Jn(t,n,o.unit)}))}},Ks=e=>E(e,g(0)),Ys=(e,t,o,n,r)=>r(e.slice(0,t)).concat(n).concat(r(e.slice(o))),Js=e=>(t,o,n,r)=>{if(e(n)){const e=Math.max(r,t[o]-Math.abs(n)),s=Math.abs(e-t[o]);return n>=0?s:-s}return n},Qs=Js((e=>e<0)),Xs=Js(x),Zs=()=>{const e=(e,t,o,n)=>{const r=(100+o)/100,s=Math.max(n,(e[t]+o)/r);return E(e,((e,o)=>(o===t?s:e/r)-e))},t=(t,o,n,r,s,l)=>l?e(t,o,r,s):((e,t,o,n,r)=>{const s=Qs(e,t,n,r);return Ys(e,t,o+1,[s,0],Ks)})(t,o,n,r,s);return{resizeTable:(e,t)=>e(t),clampTableDelta:Qs,calcLeftEdgeDeltas:t,calcMiddleDeltas:(e,o,n,r,s,l,a)=>t(e,n,r,s,l,a),calcRightEdgeDeltas:(t,o,n,r,s,l)=>{if(l)return e(t,n,r,s);{const e=Qs(t,n,r,s);return Ks(t.slice(0,n)).concat([e])}},calcRedestributedWidths:(e,t,o,n)=>{if(n){const n=(t+o)/t,r=E(e,(e=>e/n));return{delta:100*n-100,newSizes:r}}return{delta:o,newSizes:e}}}},el=()=>{const e=(e,t,o,n,r)=>{const s=Xs(e,n>=0?o:t,n,r);return Ys(e,t,o+1,[s,-s],Ks)};return{resizeTable:(e,t,o)=>{o&&e(t)},clampTableDelta:(e,t,o,n,r)=>{if(r){if(o>=0)return o;{const t=A(e,((e,t)=>e+t-n),0);return Math.max(-t,o)}}return Qs(e,t,o,n)},calcLeftEdgeDeltas:e,calcMiddleDeltas:(t,o,n,r,s,l)=>e(t,n,r,s,l),calcRightEdgeDeltas:(e,t,o,n,r,s)=>{if(s)return Ks(e);{const t=n/e.length;return E(e,g(t))}},calcRedestributedWidths:(e,t,o,n)=>({delta:0,newSizes:e})}},tl=e=>Zo(e).grid,ol=ue("th"),nl=e=>P(e,(e=>ol(e.element))),rl=(e,t)=>e&&t?"sectionCells":e?"section":"cells",sl=e=>{const t="thead"===e.section,o=vt(ll(e.cells),"th");return"tfoot"===e.section?{type:"footer"}:t||o?{type:"header",subType:rl(t,o)}:{type:"body"}},ll=e=>{const t=_(e,(e=>ol(e.element)));return 0===t.length?C.some("td"):t.length===e.length?C.some("th"):C.none()},al=(e,t,o)=>et(o(e.element,t),!0,e.isLocked),cl=(e,t)=>e.section!==t?tt(e.element,e.cells,t,e.isNew):e,il=()=>({transformRow:cl,transformCell:(e,t,o)=>{const n=o(e.element,t),r="td"!==ne(n)?((e,t)=>{const o=Je(e,"td");je(e,o);const n=Le(e);return $e(o,n),qe(e),o})(n):n;return et(r,e.isNew,e.isLocked)}}),ml=()=>({transformRow:cl,transformCell:al}),dl=()=>({transformRow:(e,t)=>cl(e,"thead"===t?"tbody":t),transformCell:al}),ul=il,fl=ml,gl=dl,hl=()=>({transformRow:h,transformCell:al}),pl=(e,t,o,n)=>{o===n?be(e,t):ge(e,t,o)},wl=(e,t,o)=>{$(mt(e,t)).fold((()=>Pe(e,o)),(e=>je(e,o)))},bl=(e,t)=>{const o=[],n=[],r=e=>E(e,(e=>{e.isNew&&o.push(e.element);const t=e.element;return Ve(t),N(e.cells,(e=>{e.isNew&&n.push(e.element),pl(e.element,"colspan",e.colspan,1),pl(e.element,"rowspan",e.rowspan,1),Ie(t,e.element)})),t})),s=e=>j(e,(e=>E(e.cells,(e=>(pl(e.element,"span",e.colspan,1),e.element))))),l=(t,o)=>{const n=((e,t)=>{const o=pt(e,t).getOrThunk((()=>{const o=xe.fromTag(t,ke(e).dom);return"thead"===t?wl(e,"caption,colgroup",o):"colgroup"===t?wl(e,"caption",o):Ie(e,o),o}));return Ve(o),o})(e,o),l=("colgroup"===o?s:r)(t);$e(n,l)},a=(t,o)=>{t.length>0?l(t,o):(t=>{pt(e,t).each(qe)})(o)},c=[],i=[],m=[],d=[];return N(t,(e=>{switch(e.section){case"thead":c.push(e);break;case"tbody":i.push(e);break;case"tfoot":m.push(e);break;case"colgroup":d.push(e)}})),a(d,"colgroup"),a(c,"thead"),a(i,"tbody"),a(m,"tfoot"),{newRows:o,newCells:n}},vl=(e,t)=>{if(0===e.length)return 0;const o=e[0];return W(e,(e=>!t(o.element,e.element))).getOr(e.length)},yl=(e,t)=>{const o=E(e,(e=>E(e.cells,y)));return E(e,((n,r)=>{const s=j(n.cells,((n,s)=>{if(!1===o[r][s]){const m=((e,t,o,n)=>{const r=((e,t)=>e[t])(e,t),s="colgroup"===r.section,l=vl(r.cells.slice(o),n),a=s?1:vl(((e,t)=>E(e,(e=>Ho(e,t))))(e.slice(t),o),n);return{colspan:l,rowspan:a}})(e,r,s,t);return((e,t,n,r)=>{for(let s=e;s({element:e,cells:t,section:o,isNew:n}))(n.element,s,n.section,n.isNew)}))},xl=(e,t,o)=>{const n=[];N(e.colgroups,(r=>{const s=[];for(let n=0;net(e.element,o,!1))).getOrThunk((()=>et(t.colGap(),!0,!1)));s.push(r)}n.push(tt(r.element,s,"colgroup",o))}));for(let r=0;ret(e.element,o,e.isLocked))).getOrThunk((()=>et(t.gap(),!0,!1)));s.push(l)}const l=e.all[r],a=tt(l.element,s,l.section,o);n.push(a)}return n},Cl=e=>yl(e,Re),Sl=(e,t)=>V(e.all,(e=>L(e.cells,(e=>Re(t,e.element))))),Tl=(e,t,o)=>{const n=E(t.selection,(t=>qt(t).bind((t=>Sl(e,t))).filter(o))),r=yt(n);return xt(r.length>0,r)},Rl=(e,t,o,n,r)=>(s,l,a,c)=>{const i=Zo(s),m=C.from(null==c?void 0:c.section).getOrThunk(hl);return t(i,l).map((t=>{const o=((e,t)=>xl(e,t,!1))(i,a),n=e(o,t,Re,r(a),m),s=Yo(n.grid);return{info:t,grid:Cl(n.grid),cursor:n.cursor,lockedColumns:s}})).bind((e=>{const t=bl(s,e.grid),r=C.from(null==c?void 0:c.sizing).getOrThunk((()=>mr(s))),l=C.from(null==c?void 0:c.resize).getOrThunk(el);return o(s,e.grid,e.info,{sizing:r,resize:l,section:m}),n(s),be(s,Go),e.lockedColumns.length>0&&ge(s,Go,e.lockedColumns.join(",")),C.some({cursor:e.cursor,newRows:t.newRows,newCells:t.newCells})}))},Dl=(e,t)=>Tl(e,t,x).map((e=>({cells:e,generators:t.generators,clipboard:t.clipboard}))),Ol=(e,t)=>Tl(e,t,x),kl=(e,t)=>Tl(e,t,(e=>!e.isLocked)),El=(e,t)=>P(t,(t=>((e,t)=>Sl(e,t).exists((e=>!e.isLocked)))(e,t))),Nl=(e,t,o,n)=>{const r=qo(e).rows;let s=!0;for(let e=0;e{const t=t=>t(e),o=g(e),n=()=>r,r={tag:!0,inner:e,fold:(t,o)=>o(e),isValue:x,isError:y,map:t=>zl.value(t(e)),mapError:n,bind:t,exists:t,forall:t,getOr:o,or:n,getOrThunk:o,orThunk:n,getOrDie:o,each:t=>{t(e)},toOptional:()=>C.some(e)};return r},_l=e=>{const t=()=>o,o={tag:!1,inner:e,fold:(t,o)=>t(e),isValue:y,isError:x,map:t,mapError:t=>zl.error(t(e)),bind:t,exists:y,forall:x,getOr:h,or:h,getOrThunk:v,orThunk:v,getOrDie:(n=String(e),()=>{throw new Error(n)}),each:f,toOptional:C.none};var n;return o},zl={value:Bl,error:_l,fromOption:(e,t)=>e.fold((()=>_l(t)),Bl)},Al=(e,t)=>({rowDelta:0,colDelta:Vo(e[0])-Vo(t[0])}),Ll=(e,t)=>({rowDelta:e.length-t.length,colDelta:0}),Wl=(e,t,o,n)=>{const r="colgroup"===t.section?o.col:o.cell;return k(e,(e=>et(r(),!0,n(e))))},Ml=(e,t,o,n)=>{const r=e[e.length-1];return e.concat(k(t,(()=>{const e="colgroup"===r.section?o.colgroup:o.row,t=Uo(r,e,h),s=Wl(t.cells.length,t,o,(e=>X(n,e.toString())));return Fo(t,s)})))},jl=(e,t,o,n)=>E(e,(e=>{const r=Wl(t,e,o,y);return jo(e,n,r)})),Pl=(e,t,o)=>{const n=t.colDelta<0?jl:h,r=t.rowDelta<0?Ml:h,s=Yo(e),l=Vo(e[0]),a=O(s,(e=>e===l-1)),c=n(e,Math.abs(t.colDelta),o,a?l-1:l),i=Yo(c);return r(c,Math.abs(t.rowDelta),o,I(i,x))},Il=(e,t,o,n)=>{const r=w(n,Ho(e[t],o).element),s=e[t];return e.length>1&&Vo(s)>1&&(o>0&&r($o(s,o-1))||o0&&r($o(e[t-1],o))||t_(o,(o=>o>=e.column&&o<=Vo(t[0])+e.column)),Hl=(e,t,o,n,r)=>{((e,t,o,n)=>{t>0&&t{const r=e.cells[t-1];let s=0;const l=n();for(;e.cells.length>t+s&&o(r.element,e.cells[t+s].element);)Io(e,t+s,et(l,!0,e.cells[t+s].isLocked)),s++}))})(t,e,r,n.cell);const s=Ll(o,t),l=Pl(o,s,n),a=Ll(t,l),c=Pl(t,a,n);return E(c,((t,o)=>jo(t,e,l[o].cells)))},$l=(e,t,o,n,r)=>{((e,t,o,n)=>{const r=qo(e).rows;if(t>0&&tA(e,((e,o)=>O(e,(e=>t(e.element,o.element)))?e:e.concat([o])),[]))(r[t-1].cells,o);N(e,(e=>{let s=C.none();for(let l=t;l{Io(a,t,et(e,!0,c.isLocked))})))}}))}})(t,e,r,n.cell);const s=Yo(t),l=Al(t,o),a={...l,colDelta:l.colDelta-s.length},c=Pl(t,a,n),{cols:i,rows:m}=qo(c),d=Yo(c),u=Al(o,t),f={...u,colDelta:u.colDelta+d.length},g=(p=n,w=d,E(o,(e=>A(w,((t,o)=>{const n=Wl(1,e,p,x)[0];return Po(t,o,n)}),e)))),h=Pl(g,f,n);var p,w;return[...i,...m.slice(0,e),...h,...m.slice(e,m.length)]},Vl=(e,t,o,n,r)=>{const{rows:s,cols:l}=qo(e),a=s.slice(0,t),c=s.slice(t);return[...l,...a,((e,t,o,n)=>Uo(e,(e=>n(e,o)),t))(s[o],((e,o)=>t>0&&tE(e,(e=>{const s=t>0&&t{if("colgroup"!==o&&n)return Ho(e,t);{const t=Ho(e,r);return et(l(t.element,s),!0,!1)}})(e,t,e.section,s,o,n,r);return Po(e,t,l)})),Ul=(e,t,o,n)=>((e,t,o,n)=>void 0!==$o(e[t],o)&&t>0&&n($o(e[t-1],o),$o(e[t],o)))(e,t,o,n)||((e,t,o)=>t>0&&o($o(e,t-1),$o(e,t)))(e[t],o,n),Gl=(e,t,o,n)=>{const r=e=>(e=>"row"===e?Pt(t):jt(t))(e)?`${e}group`:e;return e?ol(t)?r(o):null:n&&ol(t)?r("row"===o?"col":"row"):null},Kl=(e,t,o)=>et(o(e.element,t),!0,e.isLocked),Yl=(e,t,o,n,r,s,l)=>E(e,((e,a)=>((e,c)=>{const i=e.cells,m=E(i,((e,c)=>{if((e=>O(t,(t=>o(e.element,t.element))))(e)){const t=l(e,a,c)?r(e,o,n):e;return s(t,a,c).each((e=>{var o,n;o=t.element,n={scope:C.from(e)},G(n,((e,t)=>{e.fold((()=>{be(o,t)}),(e=>{fe(o.dom,t,e)}))}))})),t}return e}));return tt(e.element,m,e.section,e.isNew)})(e))),Jl=(e,t,o)=>j(e,((n,r)=>Ul(e,r,t,o)?[]:[Ho(n,t)])),Ql=(e,t,o,n,r)=>{const s=qo(e).rows,l=j(t,(e=>Jl(s,e,n))),a=E(s,(e=>nl(e.cells))),c=((e,t)=>P(t,h)&&nl(e)?x:(e,o,n)=>!("th"===ne(e.element)&&t[o]))(l,a),i=((e,t)=>(o,n)=>C.some(Gl(e,o.element,"row",t[n])))(o,a);return Yl(e,l,n,r,Kl,i,c)},Xl=(e,t,o,n)=>{const r=qo(e).rows,s=E(t,(e=>Ho(r[e.row],e.column)));return Yl(e,s,o,n,Kl,C.none,x)},Zl=e=>{if(!l(e))throw new Error("cases must be an array");if(0===e.length)throw new Error("there must be at least one case");const t=[],o={};return N(e,((n,r)=>{const s=q(n);if(1!==s.length)throw new Error("one and only one name per case");const a=s[0],c=n[a];if(void 0!==o[a])throw new Error("duplicate key detected:"+a);if("cata"===a)throw new Error("cannot have a case named cata (sorry)");if(!l(c))throw new Error("case arguments must be an array");t.push(a),o[a]=(...o)=>{const n=o.length;if(n!==c.length)throw new Error("Wrong number of arguments to case "+a+". Expected "+c.length+" ("+c+"), got "+n);return{fold:(...t)=>{if(t.length!==e.length)throw new Error("Wrong number of arguments to fold. Expected "+e.length+", got "+t.length);return t[r].apply(null,o)},match:e=>{const n=q(e);if(t.length!==n.length)throw new Error("Wrong number of arguments to match. Expected: "+t.join(",")+"\nActual: "+n.join(","));if(!P(t,(e=>D(n,e))))throw new Error("Not all branches were specified when using match. Specified: "+n.join(", ")+"\nRequired: "+t.join(", "));return e[a].apply(null,o)},log:e=>{console.log(e,{constructors:t,constructor:a,params:o})}}}})),o},ea={...Zl([{none:[]},{only:["index"]},{left:["index","next"]},{middle:["prev","index","next"]},{right:["prev","index"]}])},ta=(e,t,o)=>{let n=0;for(let r=e;r{const o=rn(e);return E(o,(e=>{const o=ta(e.row,e.row+e.rowspan,t);return{element:e.element,height:o,rowspan:e.rowspan}}))},na=(e,t,o)=>{const n=((e,t)=>ln(e)?((e,t)=>{const o=sn(e);return E(o,((e,o)=>({element:e.element,width:t[o],colspan:e.colspan})))})(e,t):((e,t)=>{const o=rn(e);return E(o,(e=>{const o=ta(e.column,e.column+e.colspan,t);return{element:e.element,width:o,colspan:e.colspan}}))})(e,t))(e,t);N(n,(e=>{o.setElementWidth(e.element,e.width)}))},ra=(e,t,o,n,r)=>{const s=Zo(e),l=r.getCellDelta(t),a=r.getWidths(s,r),c=o===s.grid.columns-1,i=n.clampTableDelta(a,o,l,r.minCellWidth(),c),m=((e,t,o,n,r)=>{const s=e.slice(0),l=((e,t)=>0===e.length?ea.none():1===e.length?ea.only(0):0===t?ea.left(0,1):t===e.length-1?ea.right(t-1,t):t>0&&tn.singleColumnWidth(s[e],o)),((e,t)=>r.calcLeftEdgeDeltas(s,e,t,o,n.minCellWidth(),n.isRelative)),((e,t,l)=>r.calcMiddleDeltas(s,e,t,l,o,n.minCellWidth(),n.isRelative)),((e,t)=>r.calcRightEdgeDeltas(s,e,t,o,n.minCellWidth(),n.isRelative)))})(a,o,i,r,n),d=E(m,((e,t)=>e+a[t]));na(s,d,r),n.resizeTable(r.adjustTableWidth,i,c)},sa=e=>A(e,((e,t)=>O(e,(e=>e.column===t.column))?e:e.concat([t])),[]).sort(((e,t)=>e.column-t.column)),la=ue("col"),aa=ue("colgroup"),ca=e=>"tr"===ne(e)||aa(e),ia=e=>({element:e,colspan:Wt(e,"colspan",1),rowspan:Wt(e,"rowspan",1)}),ma=e=>we(e,"scope").map((e=>e.substr(0,3))),da=(e,t=ia)=>{const o=o=>{if(ca(o))return aa((r={element:o}).element)?e.colgroup(r):e.row(r);{const r=o,s=(t=>la(t.element)?e.col(t):e.cell(t))(t(r));return n=C.some({item:r,replacement:s}),s}var r};let n=C.none();return{getOrInit:(e,t)=>n.fold((()=>o(e)),(n=>t(e,n.item)?n.replacement:o(e)))}},ua=e=>t=>{const o=[],n=n=>{const r="td"===e?{scope:null}:{},s=t.replace(n,e,r);return o.push({item:n,sub:s}),s};return{replaceOrInit:(e,t)=>{if(ca(e)||la(e))return e;{const r=e;return((e,t)=>L(o,(o=>t(o.item,e))))(r,t).fold((()=>n(r)),(o=>t(e,o.item)?o.sub:n(r)))}}}},fa=e=>({unmerge:t=>{const o=ma(t);return o.each((e=>ge(t,"scope",e))),()=>{const n=e.cell({element:t,colspan:1,rowspan:1});return Lt(n,"width"),Lt(t,"width"),o.each((e=>ge(n,"scope",e))),n}},merge:e=>(Lt(e[0],"width"),(()=>{const t=yt(E(e,ma));if(0===t.length)return C.none();{const e=t[0],o=["row","col"];return O(t,(t=>t!==e&&D(o,t)))?C.none():C.from(e)}})().fold((()=>be(e[0],"scope")),(t=>ge(e[0],"scope",t+"group"))),g(e[0]))}),ga=["body","p","div","article","aside","figcaption","figure","footer","header","nav","section","ol","ul","table","thead","tfoot","tbody","caption","tr","td","th","h1","h2","h3","h4","h5","h6","blockquote","pre","address"],ha=hs(),pa=e=>((e,t)=>{const o=e.property().name(t);return D(ga,o)})(ha,e),wa=e=>((e,t)=>{const o=e.property().name(t);return D(["ol","ul"],o)})(ha,e),ba=e=>{const t=ue("br"),o=e=>Cr(e).bind((o=>{const n=Ae(o).map((e=>!!pa(e)||!!((e,t)=>D(["br","img","hr","input"],e.property().name(t)))(ha,e)&&"img"!==ne(e))).getOr(!1);return Ne(o).map((r=>{return!0===n||("li"===ne(s=r)||ft(s,wa).isSome())||t(o)||pa(r)&&!Re(e,r)?[]:[xe.fromTag("br")];var s}))})).getOr([]),n=(()=>{const n=j(e,(e=>{const n=Le(e);return(e=>P(e,(e=>t(e)||ie(e)&&0===hr(e).trim().length)))(n)?[]:n.concat(o(e))}));return 0===n.length?[xe.fromTag("br")]:n})();Ve(e[0]),$e(e[0],n)},va=e=>Qr(e,!0),ya=e=>{0===Ut(e).length&&qe(e)},xa=(e,t)=>({grid:e,cursor:t}),Ca=(e,t,o)=>{const n=((e,t,o)=>{var n,r;const s=qo(e).rows;return C.from(null===(r=null===(n=s[t])||void 0===n?void 0:n.cells[o])||void 0===r?void 0:r.element).filter(va).orThunk((()=>(e=>V(e,(e=>V(e.cells,(e=>{const t=e.element;return xt(va(t),t)})))))(s)))})(e,t,o);return xa(e,n)},Sa=e=>A(e,((e,t)=>O(e,(e=>e.row===t.row))?e:e.concat([t])),[]).sort(((e,t)=>e.row-t.row)),Ta=(e,t)=>(o,n,r,s,l)=>{const a=Sa(n),c=E(a,(e=>e.row)),i=((e,t,o,n,r,s,l)=>{const{cols:a,rows:c}=qo(e),i=c[t[0]],m=j(t,(e=>((e,t,o)=>{const n=e[t];return j(n.cells,((n,r)=>Ul(e,t,r,o)?[]:[n]))})(c,e,r))),d=E(i.cells,((e,t)=>nl(Jl(c,t,r)))),u=[...c];N(t,(e=>{u[e]=l.transformRow(c[e],o)}));const f=[...a,...u],g=((e,t)=>P(t,h)&&nl(e.cells)?x:(e,o,n)=>!("th"===ne(e.element)&&t[n]))(i,d),p=((e,t)=>(o,n,r)=>C.some(Gl(e,o.element,"col",t[r])))(n,d);return Yl(f,m,r,s,l.transformCell,p,g)})(o,c,e,t,r,s.replaceOrInit,l);return Ca(i,n[0].row,n[0].column)},Ra=Ta("thead",!0),Da=Ta("tbody",!1),Oa=Ta("tfoot",!1),ka=(e,t,o)=>{const n=((e,t)=>Qt(e,(()=>t)))(e,o.section),r=en(n);return xl(r,t,!0)},Ea=(e,t,o,n)=>((e,t,o,n)=>{const r=en(t),s=n.getWidths(r,n);na(r,s,n)})(0,t,0,n.sizing),Na=(e,t,o,n)=>((e,t,o,n,r)=>{const s=en(t),l=n.getWidths(s,n),a=n.pixelWidth(),{newSizes:c,delta:i}=r.calcRedestributedWidths(l,a,o.pixelDelta,n.isRelative);na(s,c,n),n.adjustTableWidth(i)})(0,t,o,n.sizing,n.resize),Ba=(e,t)=>O(t,(e=>0===e.column&&e.isLocked)),_a=(e,t)=>O(t,(t=>t.column+t.colspan>=e.grid.columns&&t.isLocked)),za=(e,t)=>{const o=cn(e),n=sa(t);return A(n,((e,t)=>e+o[t.column].map(Wo).getOr(0)),0)},Aa=e=>(t,o)=>Ol(t,o).filter((o=>!(e?Ba:_a)(t,o))).map((e=>({details:e,pixelDelta:za(t,e)}))),La=e=>(t,o)=>Dl(t,o).filter((o=>!(e?Ba:_a)(t,o.cells))),Wa=ua("th"),Ma=ua("td"),ja=Rl(((e,t,o,n)=>{const r=t[0].row,s=Sa(t),l=z(s,((e,t)=>({grid:Vl(e.grid,r,t.row+e.delta,o,n.getOrInit),delta:e.delta+1})),{grid:e,delta:0}).grid;return Ca(l,r,t[0].column)}),Ol,f,f,da),Pa=Rl(((e,t,o,n)=>{const r=Sa(t),s=r[r.length-1],l=s.row+s.rowspan,a=z(r,((e,t)=>Vl(e,l,t.row,o,n.getOrInit)),e);return Ca(a,l,t[0].column)}),Ol,f,f,da),Ia=Rl(((e,t,o,n)=>{const r=t.details,s=sa(r),l=s[0].column,a=z(s,((e,t)=>({grid:ql(e.grid,l,t.column+e.delta,o,n.getOrInit),delta:e.delta+1})),{grid:e,delta:0}).grid;return Ca(a,r[0].row,l)}),Aa(!0),Na,f,da),Fa=Rl(((e,t,o,n)=>{const r=t.details,s=r[r.length-1],l=s.column+s.colspan,a=sa(r),c=z(a,((e,t)=>ql(e,l,t.column,o,n.getOrInit)),e);return Ca(c,r[0].row,l)}),Aa(!1),Na,f,da),Ha=Rl(((e,t,o,n)=>{const r=sa(t.details),s=((e,t)=>j(e,(e=>{const o=e.cells,n=z(t,((e,t)=>t>=0&&t0?[tt(e.element,n,e.section,e.isNew)]:[]})))(e,E(r,(e=>e.column))),l=s.length>0?s[0].cells.length-1:0;return Ca(s,r[0].row,Math.min(r[0].column,l))}),((e,t)=>kl(e,t).map((t=>({details:t,pixelDelta:-za(e,t)})))),Na,ya,da),$a=Rl(((e,t,o,n)=>{const r=Sa(t),s=((e,t,o)=>{const{rows:n,cols:r}=qo(e);return[...r,...n.slice(0,t),...n.slice(o+1)]})(e,r[0].row,r[r.length-1].row),l=s.length>0?s.length-1:0;return Ca(s,Math.min(t[0].row,l),t[0].column)}),Ol,f,ya,da),Va=Rl(((e,t,o,n)=>{const r=sa(t),s=E(r,(e=>e.column)),l=Ql(e,s,!0,o,n.replaceOrInit);return Ca(l,t[0].row,t[0].column)}),kl,f,f,Wa),qa=Rl(((e,t,o,n)=>{const r=sa(t),s=E(r,(e=>e.column)),l=Ql(e,s,!1,o,n.replaceOrInit);return Ca(l,t[0].row,t[0].column)}),kl,f,f,Ma),Ua=Rl(Ra,kl,f,f,Wa),Ga=Rl(Da,kl,f,f,Ma),Ka=Rl(Oa,kl,f,f,Ma),Ya=Rl(((e,t,o,n)=>{const r=Xl(e,t,o,n.replaceOrInit);return Ca(r,t[0].row,t[0].column)}),kl,f,f,Wa),Ja=Rl(((e,t,o,n)=>{const r=Xl(e,t,o,n.replaceOrInit);return Ca(r,t[0].row,t[0].column)}),kl,f,f,Ma),Qa=Rl(((e,t,o,n)=>{const r=t.cells;ba(r);const s=((e,t,o,n)=>{const r=qo(e).rows;if(0===r.length)return e;for(let e=t.startRow;e<=t.finishRow;e++)for(let o=t.startCol;o<=t.finishCol;o++){const t=r[e],s=Ho(t,o).isLocked;Io(t,o,et(n(),!1,s))}return e})(e,t.bounds,0,n.merge(r));return xa(s,C.from(r[0]))}),((e,t)=>((e,t)=>t.mergable)(0,t).filter((t=>El(e,t.cells)))),Ea,f,fa),Xa=Rl(((e,t,o,n)=>{const r=z(t,((e,t)=>Nl(e,t,o,n.unmerge(t))),e);return xa(r,C.from(t[0]))}),((e,t)=>((e,t)=>t.unmergable)(0,t).filter((t=>El(e,t)))),Ea,f,fa),Za=Rl(((e,t,o,n)=>{const r=((e,t)=>{const o=Zo(e);return xl(o,t,!0)})(t.clipboard,t.generators);var s,l;return((e,t,o,n,r)=>{const s=Yo(t),l=((e,t,o)=>{const n=Vo(t[0]),r=qo(t).cols.length+e.row,s=k(n-e.column,(t=>t+e.column));return{row:r,column:L(s,(e=>P(o,(t=>t!==e)))).getOr(n-1)}})(e,t,s),a=qo(o).rows,c=Fl(l,a,s),i=((e,t,o)=>{if(e.row>=t.length||e.column>Vo(t[0]))return zl.error("invalid start address out of table bounds, row: "+e.row+", column: "+e.column);const n=t.slice(e.row),r=n[0].cells.slice(e.column),s=Vo(o[0]),l=o.length;return zl.value({rowDelta:n.length-l,colDelta:r.length-s})})(l,t,a);return i.map((e=>{const o={...e,colDelta:e.colDelta-c.length},s=Pl(t,o,n),i=Yo(s),m=Fl(l,a,i);return((e,t,o,n,r,s)=>{const l=e.row,a=e.column,c=l+o.length,i=a+Vo(o[0])+s.length,m=I(s,x);for(let e=l;exa(e,C.some(t.element))),(e=>Ca(e,t.row,t.column)))}),((e,t)=>qt(t.element).bind((o=>Sl(e,o).map((e=>({...e,generators:t.generators,clipboard:t.clipboard})))))),Ea,f,da),ec=Rl(((e,t,o,n)=>{const r=qo(e).rows,s=t.cells[0].column,l=r[t.cells[0].row],a=ka(t.clipboard,t.generators,l),c=Hl(s,e,a,t.generators,o);return Ca(c,t.cells[0].row,t.cells[0].column)}),La(!0),f,f,da),tc=Rl(((e,t,o,n)=>{const r=qo(e).rows,s=t.cells[t.cells.length-1].column+t.cells[t.cells.length-1].colspan,l=r[t.cells[0].row],a=ka(t.clipboard,t.generators,l),c=Hl(s,e,a,t.generators,o);return Ca(c,t.cells[0].row,t.cells[0].column)}),La(!1),f,f,da),oc=Rl(((e,t,o,n)=>{const r=qo(e).rows,s=t.cells[0].row,l=r[s],a=ka(t.clipboard,t.generators,l),c=$l(s,e,a,t.generators,o);return Ca(c,t.cells[0].row,t.cells[0].column)}),Dl,f,f,da),nc=Rl(((e,t,o,n)=>{const r=qo(e).rows,s=t.cells[t.cells.length-1].row+t.cells[t.cells.length-1].rowspan,l=r[t.cells[0].row],a=ka(t.clipboard,t.generators,l),c=$l(s,e,a,t.generators,o);return Ca(c,t.cells[0].row,t.cells[0].column)}),Dl,f,f,da),rc=(e,t)=>{const o=Zo(e);return Ol(o,t).bind((e=>{const t=e[e.length-1],n=e[0].column,r=t.column+t.colspan,s=M(E(o.all,(e=>_(e.cells,(e=>e.column>=n&&e.column{const o=Zo(e);return Ol(o,t).bind(ll).getOr("")},lc=(e,t)=>{const o=Zo(e);return Ol(o,t).bind((e=>{const t=e[e.length-1],n=e[0].row,r=t.row+t.rowspan;return(e=>{const t=E(e,(e=>sl(e).type)),o=D(t,"header"),n=D(t,"footer");if(o||n){const e=D(t,"body");return!o||e||n?o||e||!n?C.none():C.some("footer"):C.some("header")}return C.some("body")})(o.all.slice(n,r))})).getOr("")},ac=(e,t)=>e.dispatch("NewRow",{node:t}),cc=(e,t)=>e.dispatch("NewCell",{node:t}),ic=(e,t,o)=>{e.dispatch("TableModified",{...o,table:t})},mc={structure:!1,style:!0},dc={structure:!0,style:!1},uc={structure:!0,style:!0},fc=(e,t)=>Hr(e)?ur(t):$r(e)?dr(t):mr(t),gc=(e,t,o)=>{const n=e=>"table"===ne(Zr(e)),r=Wr(e),s=Ir(e)?f:Gs,l=t=>{switch(Mr(e)){case"section":return ul();case"sectionCells":return fl();case"cells":return gl();default:return((e,t)=>{var o;switch((o=Zo(e),V(o.all,(e=>{const t=sl(e);return"header"===t.type?C.from(t.subType):C.none()}))).getOr(t)){case"section":return il();case"sectionCells":return ml();case"cells":return dl()}})(t,"section")}},a=(n,s,a,c)=>(i,m,d=!1)=>{ts(i);const u=xe.fromDom(e.getDoc()),f=Br(a,u,r),g={sizing:fc(e,i),resize:Ir(e)?Zs():el(),section:l(i)};return s(i)?n(i,m,f,g).bind((n=>{t.refresh(i.dom),N(n.newRows,(t=>{ac(e,t.dom)})),N(n.newCells,(t=>{cc(e,t.dom)}));const r=((t,n)=>n.cursor.fold((()=>{const n=Ut(t);return H(n).filter(lt).map((n=>{o.clearSelectedCells(t.dom);const r=e.dom.createRng();return r.selectNode(n.dom),e.selection.setRng(r),ge(n,"data-mce-selected","1"),r}))}),(n=>{const r=qs(Us,n),s=e.dom.createRng();return s.setStart(r.element.dom,r.offset),s.setEnd(r.element.dom,r.offset),e.selection.setRng(s),o.clearSelectedCells(t.dom),C.some(s)})))(i,n);return lt(i)&&(ts(i),d||ic(e,i.dom,c)),r.map((e=>({rng:e,effect:c})))})):C.none()},c=a($a,(t=>!n(e)||tl(t).rows>1),f,dc),i=a(Ha,(t=>!n(e)||tl(t).columns>1),f,dc);return{deleteRow:c,deleteColumn:i,insertRowsBefore:a(ja,x,f,dc),insertRowsAfter:a(Pa,x,f,dc),insertColumnsBefore:a(Ia,x,s,dc),insertColumnsAfter:a(Fa,x,s,dc),mergeCells:a(Qa,x,f,dc),unmergeCells:a(Xa,x,f,dc),pasteColsBefore:a(ec,x,f,dc),pasteColsAfter:a(tc,x,f,dc),pasteRowsBefore:a(oc,x,f,dc),pasteRowsAfter:a(nc,x,f,dc),pasteCells:a(Za,x,f,uc),makeCellsHeader:a(Ya,x,f,dc),unmakeCellsHeader:a(Ja,x,f,dc),makeColumnsHeader:a(Va,x,f,dc),unmakeColumnsHeader:a(qa,x,f,dc),makeRowsHeader:a(Ua,x,f,dc),makeRowsBody:a(Ga,x,f,dc),makeRowsFooter:a(Ka,x,f,dc),getTableRowType:lc,getTableCellType:sc,getTableColType:rc}},hc=(e,t,o)=>{const n=Wt(e,t,1);1===o||n<=1?be(e,t):ge(e,t,Math.min(o,n))},pc=(e,t)=>o=>{const n=o.column+o.colspan-1,r=o.column;return n>=e&&r{const n=o.substring(0,o.length-e.length),r=parseFloat(n);return n===r.toString()?t(r):wc.invalid(o)},vc={...wc,from:e=>Rt(e,"%")?bc("%",wc.percent,e):Rt(e,"px")?bc("px",wc.pixels,e):wc.invalid(e)},yc=(e,t,o)=>{const n=vc.from(o),r=P(e,(e=>"0px"===e))?((e,t)=>{const o=e.fold((()=>g("")),(e=>g(e/t+"px")),(()=>g(100/t+"%")));return k(t,o)})(n,e.length):((e,t,o)=>e.fold((()=>t),(e=>((e,t,o)=>{const n=o/t;return E(e,(e=>vc.from(e).fold((()=>e),(e=>e*n+"px"),(e=>e/100*o+"px"))))})(t,o,e)),(e=>((e,t)=>E(e,(e=>vc.from(e).fold((()=>e),(e=>e/t*100+"%"),(e=>e+"%")))))(t,o))))(n,e,t);return Sc(r)},xc=(e,t)=>0===e.length?t:z(e,((e,t)=>vc.from(t).fold(g(0),h,h)+e),0),Cc=(e,t)=>vc.from(e).fold(g(e),(e=>e+t+"px"),(e=>e+t+"%")),Sc=e=>{if(0===e.length)return e;const t=z(e,((e,t)=>{const o=vc.from(t).fold((()=>({value:t,remainder:0})),(e=>((e,t)=>{const o=Math.floor(e);return{value:o+"px",remainder:e-o}})(e)),(e=>({value:e+"%",remainder:0})));return{output:[o.value].concat(e.output),remainder:e.remainder+o.remainder}}),{output:[],remainder:0}),o=t.output;return o.slice(0,o.length-1).concat([Cc(o[o.length-1],Math.round(t.remainder))])},Tc=vc.from,Rc=e=>Tc(e).fold(g("px"),g("px"),g("%")),Dc=(e,t,o)=>{const n=Zo(e),r=n.all,s=rn(n),l=sn(n);t.each((t=>{const o=Rc(t),r=Lo(e),a=((e,t)=>nr(e,t,er,rr))(n,e),c=yc(a,r,t);ln(n)?((e,t,o)=>{N(t,((t,n)=>{const r=xc([e[n]],Ft());Nt(t.element,"width",r+o)}))})(c,l,o):((e,t,o)=>{N(t,(t=>{const n=e.slice(t.column,t.colspan+t.column),r=xc(n,Ft());Nt(t.element,"width",r+o)}))})(c,s,o),Nt(e,"width",t)})),o.each((t=>{const o=Rc(t),l=hn(e),a=((e,t,o)=>lr(e,t,o,tr,rr))(n,e,_n);((e,t,o,n)=>{N(o,(t=>{const o=e.slice(t.row,t.rowspan+t.row),r=xc(o,Ht());Nt(t.element,"height",r+n)})),N(t,((t,o)=>{Nt(t.element,"height",e[o])}))})(yc(a,l,t),r,s,o),Nt(e,"height",t)}))},Oc=e=>Un(e).exists((e=>Mn.test(e))),kc=e=>Un(e).exists((e=>jn.test(e))),Ec=e=>Un(e).isNone(),Nc=e=>{be(e,"width")},Bc=e=>{const t=Qn(e);Dc(e,C.some(t),C.none()),Nc(e)},_c=e=>{const t=(e=>Lo(e)+"px")(e);Dc(e,C.some(t),C.none()),Nc(e)},zc=e=>{Lt(e,"width");const t=Gt(e),o=t.length>0?t:Ut(e);N(o,(e=>{Lt(e,"width"),Nc(e)})),Nc(e)},Ac={styles:{"border-collapse":"collapse",width:"100%"},attributes:{border:"1"},colGroups:!1},Lc=(e,t,o,n)=>k(e,(e=>((e,t,o,n)=>{const r=xe.fromTag("tr");for(let s=0;s{e.selection.select(t.dom,!0),e.selection.collapse(!0)},Mc=(e,t,o,n,s)=>{const l=(e=>{const t=e.options,o=t.get("table_default_styles");return t.isSet("table_default_styles")?o:((e,t)=>Vr(e)||!Ur(e)?t:$r(e)?{...t,width:Lr(e)}:{...t,width:Ar})(e,o)})(e),a={styles:l,attributes:Kr(e),colGroups:Yr(e)};return e.undoManager.ignore((()=>{const r=((e,t,o,n,r,s=Ac)=>{const l=xe.fromTag("table"),a="cells"!==r;Bt(l,s.styles),he(l,s.attributes),s.colGroups&&Ie(l,(e=>{const t=xe.fromTag("colgroup");return k(e,(()=>Ie(t,xe.fromTag("col")))),t})(t));const c=Math.min(e,o);if(a&&o>0){const e=xe.fromTag("thead");Ie(l,e);const s=Lc(o,t,"sectionCells"===r?c:0,n);$e(e,s)}const i=xe.fromTag("tbody");Ie(l,i);const m=Lc(a?e-c:e,t,a?0:o,n);return $e(i,m),l})(o,t,s,n,Mr(e),a);ge(r,"data-mce-id","__mce");const l=(e=>{const t=xe.fromTag("div"),o=xe.fromDom(e.dom.cloneNode(!0));return Ie(t,o),(e=>e.dom.innerHTML)(t)})(r);e.insertContent(l),e.addVisual()})),wt(Zr(e),'table[data-mce-id="__mce"]').map((t=>($r(e)?_c(t):Vr(e)?zc(t):(Hr(e)||(e=>r(e)&&-1!==e.indexOf("%"))(l.width))&&Bc(t),ts(t),be(t,"data-mce-id"),((e,t)=>{N(dt(t,"tr"),(t=>{ac(e,t.dom),N(dt(t,"th,td"),(t=>{cc(e,t.dom)}))}))})(e,t),((e,t)=>{wt(t,"td,th").each(w(Wc,e))})(e,t),t.dom))).getOrNull()};var jc=tinymce.util.Tools.resolve("tinymce.FakeClipboard");const Pc="x-tinymce/dom-table-",Ic=Pc+"rows",Fc=Pc+"columns",Hc=e=>{const t=jc.FakeClipboardItem(e);jc.write([t])},$c=e=>{var t;const o=null!==(t=jc.read())&&void 0!==t?t:[];return V(o,(t=>C.from(t.getType(e))))},Vc=e=>{$c(e).isSome()&&jc.clear()},qc=e=>{e.fold(Gc,(e=>Hc({[Ic]:e})))},Uc=()=>$c(Ic),Gc=()=>Vc(Ic),Kc=e=>{e.fold(Jc,(e=>Hc({[Fc]:e})))},Yc=()=>$c(Fc),Jc=()=>Vc(Fc),Qc=e=>Ms(os(e),es(e)).filter(ss),Xc=(e,t)=>{const o=es(e),n=e=>Kt(e,o),l=t=>(e=>js(os(e),es(e)).filter(ss))(e).bind((e=>n(e).map((o=>t(o,e))))),a=t=>{e.focus()},c=(t,o=!1)=>l(((n,r)=>{const s=Ls(Ps(e),n,r);t(n,s,o).each(a)})),i=()=>l(((t,o)=>((e,t,o)=>{const n=Zo(e);return Ol(n,t).bind((e=>{const t=xl(n,o,!1),r=qo(t).rows.slice(e[0].row,e[e.length-1].row+e[e.length-1].rowspan),s=j(r,(e=>{const t=_(e.cells,(e=>!e.isLocked));return t.length>0?[{...e,cells:t}]:[]})),l=Cl(s);return xt(l.length>0,l)})).map((e=>E(e,(e=>{const t=Ke(e.element);return N(e.cells,(e=>{const o=Ye(e.element);pl(o,"colspan",e.colspan,1),pl(o,"rowspan",e.rowspan,1),Ie(t,o)})),t}))))})(t,Ls(Ps(e),t,o),Br(f,xe.fromDom(e.getDoc()),C.none())))),m=()=>l(((t,o)=>((e,t)=>{const o=Zo(e);return kl(o,t).map((e=>{const t=e[e.length-1],n=e[0].column,r=t.column+t.colspan,s=((e,t,o)=>{if(ln(e)){const n=_(sn(e),pc(t,o)),r=E(n,(e=>{const n=Ye(e.element);return hc(n,"span",o-t),n})),s=xe.fromTag("colgroup");return $e(s,r),[s]}return[]})(o,n,r),l=((e,t,o)=>E(e.all,(e=>{const n=_(e.cells,pc(t,o)),r=E(n,(e=>{const n=Ye(e.element);return hc(n,"colspan",o-t),n})),s=xe.fromTag("tr");return $e(s,r),s})))(o,n,r);return[...s,...l]}))})(t,Ls(Ps(e),t,o)))),d=(t,o)=>o().each((o=>{const n=E(o,(e=>Ye(e)));l(((o,r)=>{const s=_r(xe.fromDom(e.getDoc())),l=((e,t,o,n)=>({selection:Os(e),clipboard:o,generators:n}))(Ps(e),0,n,s);t(o,l).each(a)}))})),g=e=>(t,o)=>((e,t)=>X(e,t)?C.from(e[t]):C.none())(o,"type").each((t=>{c(e(t),o.no_events)}));G({mceTableSplitCells:()=>c(t.unmergeCells),mceTableMergeCells:()=>c(t.mergeCells),mceTableInsertRowBefore:()=>c(t.insertRowsBefore),mceTableInsertRowAfter:()=>c(t.insertRowsAfter),mceTableInsertColBefore:()=>c(t.insertColumnsBefore),mceTableInsertColAfter:()=>c(t.insertColumnsAfter),mceTableDeleteCol:()=>c(t.deleteColumn),mceTableDeleteRow:()=>c(t.deleteRow),mceTableCutCol:()=>m().each((e=>{Kc(e),c(t.deleteColumn)})),mceTableCutRow:()=>i().each((e=>{qc(e),c(t.deleteRow)})),mceTableCopyCol:()=>m().each((e=>Kc(e))),mceTableCopyRow:()=>i().each((e=>qc(e))),mceTablePasteColBefore:()=>d(t.pasteColsBefore,Yc),mceTablePasteColAfter:()=>d(t.pasteColsAfter,Yc),mceTablePasteRowBefore:()=>d(t.pasteRowsBefore,Uc),mceTablePasteRowAfter:()=>d(t.pasteRowsAfter,Uc),mceTableDelete:()=>Qc(e).each((t=>{Kt(t,o).filter(b(o)).each((t=>{const o=xe.fromText("");if(je(t,o),qe(t),e.dom.isEmpty(e.getBody()))e.setContent(""),e.selection.setCursorLocation();else{const t=e.dom.createRng();t.setStart(o.dom,0),t.setEnd(o.dom,0),e.selection.setRng(t),e.nodeChanged()}}))})),mceTableCellToggleClass:(t,o)=>{l((t=>{const n=Ps(e),r=P(n,(t=>e.formatter.match("tablecellclass",{value:o},t.dom))),s=r?e.formatter.remove:e.formatter.apply;N(n,(e=>s("tablecellclass",{value:o},e.dom))),ic(e,t.dom,mc)}))},mceTableToggleClass:(t,o)=>{l((t=>{e.formatter.toggle("tableclass",{value:o},t.dom),ic(e,t.dom,mc)}))},mceTableToggleCaption:()=>{Qc(e).each((t=>{Kt(t,o).each((o=>{pt(o,"caption").fold((()=>{const t=xe.fromTag("caption");Ie(t,xe.fromText("Caption")),((e,t,o)=>{We(e,0).fold((()=>{Ie(e,t)}),(e=>{Me(e,t)}))})(o,t),e.selection.setCursorLocation(t.dom,0)}),(n=>{ue("caption")(t)&&Te("td",o).each((t=>e.selection.setCursorLocation(t.dom,0))),qe(n)})),ic(e,o.dom,dc)}))}))},mceTableSizingMode:(t,n)=>(t=>Qc(e).each((n=>{Vr(e)||$r(e)||Hr(e)||Kt(n,o).each((o=>{"relative"!==t||Oc(o)?"fixed"!==t||kc(o)?"responsive"!==t||Ec(o)||zc(o):_c(o):Bc(o),ts(o),ic(e,o.dom,dc)}))})))(n),mceTableCellType:g((e=>"th"===e?t.makeCellsHeader:t.unmakeCellsHeader)),mceTableColType:g((e=>"th"===e?t.makeColumnsHeader:t.unmakeColumnsHeader)),mceTableRowType:g((e=>{switch(e){case"header":return t.makeRowsHeader;case"footer":return t.makeRowsFooter;default:return t.makeRowsBody}}))},((t,o)=>e.addCommand(o,t))),e.addCommand("mceInsertTable",((t,o)=>{((e,t,o,n={})=>{const r=e=>u(e)&&e>0;if(r(t)&&r(o)){const r=n.headerRows||0,s=n.headerColumns||0;return Mc(e,o,t,s,r)}console.error("Invalid values for mceInsertTable - rows and columns values are required to insert a table.")})(e,o.rows,o.columns,o.options)})),e.addCommand("mceTableApplyCellStyle",((t,o)=>{const l=e=>"tablecell"+e.toLowerCase().replace("-","");if(!s(o))return;const a=_(Ps(e),ss);if(0===a.length)return;const c=((e,t)=>{const o={};return((e,t,o,n)=>{G(e,((e,r)=>{(t(e,r)?o:n)(e,r)}))})(e,t,(e=>(t,o)=>{e[o]=t})(o),f),o})(o,((t,o)=>e.formatter.has(l(o))&&r(t)));(e=>{for(const t in e)if(U.call(e,t))return!1;return!0})(c)||(G(c,((t,o)=>{const n=l(o);N(a,(o=>{""===t?e.formatter.remove(n,{value:null},o.dom,!0):e.formatter.apply(n,{value:t},o.dom)}))})),n(a[0]).each((t=>ic(e,t.dom,mc))))}))},Zc=Zl([{before:["element"]},{on:["element","offset"]},{after:["element"]}]),ei={before:Zc.before,on:Zc.on,after:Zc.after,cata:(e,t,o,n)=>e.fold(t,o,n),getStart:e=>e.fold(h,h,h)},ti=(e,t)=>({selection:e,kill:t}),oi=(e,t)=>{const o=e.document.createRange();return o.selectNode(t.dom),o},ni=(e,t)=>{const o=e.document.createRange();return ri(o,t),o},ri=(e,t)=>e.selectNodeContents(t.dom),si=(e,t,o)=>{const n=e.document.createRange();var r;return r=n,t.fold((e=>{r.setStartBefore(e.dom)}),((e,t)=>{r.setStart(e.dom,t)}),(e=>{r.setStartAfter(e.dom)})),((e,t)=>{t.fold((t=>{e.setEndBefore(t.dom)}),((t,o)=>{e.setEnd(t.dom,o)}),(t=>{e.setEndAfter(t.dom)}))})(n,o),n},li=(e,t,o,n,r)=>{const s=e.document.createRange();return s.setStart(t.dom,o),s.setEnd(n.dom,r),s},ai=e=>({left:e.left,top:e.top,right:e.right,bottom:e.bottom,width:e.width,height:e.height}),ci=Zl([{ltr:["start","soffset","finish","foffset"]},{rtl:["start","soffset","finish","foffset"]}]),ii=(e,t,o)=>t(xe.fromDom(o.startContainer),o.startOffset,xe.fromDom(o.endContainer),o.endOffset),mi=(e,t)=>{const o=((e,t)=>t.match({domRange:e=>({ltr:g(e),rtl:C.none}),relative:(t,o)=>({ltr:eo((()=>si(e,t,o))),rtl:eo((()=>C.some(si(e,o,t))))}),exact:(t,o,n,r)=>({ltr:eo((()=>li(e,t,o,n,r))),rtl:eo((()=>C.some(li(e,n,r,t,o))))})}))(e,t);return((e,t)=>{const o=t.ltr();return o.collapsed?t.rtl().filter((e=>!1===e.collapsed)).map((e=>ci.rtl(xe.fromDom(e.endContainer),e.endOffset,xe.fromDom(e.startContainer),e.startOffset))).getOrThunk((()=>ii(0,ci.ltr,o))):ii(0,ci.ltr,o)})(0,o)},di=(e,t)=>mi(e,t).match({ltr:(t,o,n,r)=>{const s=e.document.createRange();return s.setStart(t.dom,o),s.setEnd(n.dom,r),s},rtl:(t,o,n,r)=>{const s=e.document.createRange();return s.setStart(n.dom,r),s.setEnd(t.dom,o),s}});ci.ltr,ci.rtl;const ui=(e,t,o,n)=>({start:e,soffset:t,finish:o,foffset:n}),fi=(e,t,o,n)=>({start:ei.on(e,t),finish:ei.on(o,n)}),gi=(e,t)=>{const o=di(e,t);return ui(xe.fromDom(o.startContainer),o.startOffset,xe.fromDom(o.endContainer),o.endOffset)},hi=fi,pi=(e,t,o,n,r)=>Re(o,n)?C.none():xs(o,n,t).bind((t=>{const n=t.boxes.getOr([]);return n.length>1?(r(e,n,t.start,t.finish),C.some(ti(C.some(hi(o,0,o,br(o))),!0))):C.none()})),wi=(e,t)=>({item:e,mode:t}),bi=(e,t,o,n=vi)=>e.property().parent(t).map((e=>wi(e,n))),vi=(e,t,o,n=yi)=>o.sibling(e,t).map((e=>wi(e,n))),yi=(e,t,o,n=yi)=>{const r=e.property().children(t);return o.first(r).map((e=>wi(e,n)))},xi=[{current:bi,next:vi,fallback:C.none()},{current:vi,next:yi,fallback:C.some(bi)},{current:yi,next:yi,fallback:C.some(vi)}],Ci=(e,t,o,n,r=xi)=>L(r,(e=>e.current===o)).bind((o=>o.current(e,t,n,o.next).orThunk((()=>o.fallback.bind((o=>Ci(e,t,o,n))))))),Si=(e,t,o,n,r,s)=>Ci(e,t,n,r).bind((t=>s(t.item)?C.none():o(t.item)?C.some(t.item):Si(e,t.item,o,t.mode,r,s))),Ti=e=>t=>0===e.property().children(t).length,Ri=(e,t,o,n)=>Si(e,t,o,vi,{sibling:(e,t)=>e.query().prevSibling(t),first:e=>e.length>0?C.some(e[e.length-1]):C.none()},n),Di=(e,t,o,n)=>Si(e,t,o,vi,{sibling:(e,t)=>e.query().nextSibling(t),first:e=>e.length>0?C.some(e[0]):C.none()},n),Oi=hs(),ki=(e,t)=>((e,t,o)=>Ri(e,t,Ti(e),o))(Oi,e,t),Ei=(e,t)=>((e,t,o)=>Di(e,t,Ti(e),o))(Oi,e,t),Ni=Zl([{none:["message"]},{success:[]},{failedUp:["cell"]},{failedDown:["cell"]}]),Bi=e=>bt(e,"tr"),_i={...Ni,verify:(e,t,o,n,r,s,l)=>bt(n,"td,th",l).bind((o=>bt(t,"td,th",l).map((t=>Re(o,t)?Re(n,o)&&br(o)===r?s(t):Ni.none("in same cell"):vs(Bi,[o,t]).fold((()=>((e,t,o)=>{const n=e.getRect(t),r=e.getRect(o);return r.right>n.left&&r.lefts(t))))))).getOr(Ni.none("default")),cata:(e,t,o,n,r)=>e.fold(t,o,n,r)},zi=ue("br"),Ai=(e,t,o)=>t(e,o).bind((e=>ie(e)&&0===hr(e).trim().length?Ai(e,t,o):C.some(e))),Li=(e,t,o,n)=>((e,t)=>We(e,t).filter(zi).orThunk((()=>We(e,t-1).filter(zi))))(t,o).bind((t=>n.traverse(t).fold((()=>Ai(t,n.gather,e).map(n.relative)),(e=>(e=>Ne(e).bind((t=>{const o=Le(t);return((e,t)=>W(e,w(Re,t)))(o,e).map((n=>((e,t,o,n)=>({parent:e,children:t,element:o,index:n}))(t,o,e,n)))})))(e).map((e=>ei.on(e.parent,e.index))))))),Wi=(e,t)=>({left:e.left,top:e.top+t,right:e.right,bottom:e.bottom+t}),Mi=(e,t)=>({left:e.left,top:e.top-t,right:e.right,bottom:e.bottom-t}),ji=(e,t,o)=>({left:e.left+t,top:e.top+o,right:e.right+t,bottom:e.bottom+o}),Pi=e=>({left:e.left,top:e.top,right:e.right,bottom:e.bottom}),Ii=(e,t)=>C.some(e.getRect(t)),Fi=(e,t,o)=>ce(t)?Ii(e,t).map(Pi):ie(t)?((e,t,o)=>o>=0&&o0?e.getRangedRect(t,o-1,t,o):C.none())(e,t,o).map(Pi):C.none(),Hi=(e,t)=>ce(t)?Ii(e,t).map(Pi):ie(t)?e.getRangedRect(t,0,t,br(t)).map(Pi):C.none(),$i=Zl([{none:[]},{retry:["caret"]}]),Vi=(e,t,o)=>gt(t,pa).fold(y,(t=>Hi(e,t).exists((e=>((e,t)=>e.leftt.right)(o,e))))),qi={point:e=>e.bottom,adjuster:(e,t,o,n,r)=>{const s=Wi(r,5);return Math.abs(o.bottom-n.bottom)<1||o.top>r.bottom?$i.retry(s):o.top===r.bottom?$i.retry(Wi(r,1)):Vi(e,t,r)?$i.retry(ji(s,5,0)):$i.none()},move:Wi,gather:Ei},Ui=(e,t,o,n,r)=>0===r?C.some(n):((e,t,o)=>e.elementFromPoint(t,o).filter((e=>"table"===ne(e))).isSome())(e,n.left,t.point(n))?((e,t,o,n,r)=>Ui(e,t,o,t.move(n,5),r))(e,t,o,n,r-1):e.situsFromPoint(n.left,t.point(n)).bind((s=>s.start.fold(C.none,(s=>Hi(e,s).bind((l=>t.adjuster(e,s,l,o,n).fold(C.none,(n=>Ui(e,t,o,n,r-1))))).orThunk((()=>C.some(n)))),C.none))),Gi=(e,t,o)=>{const n=e.move(o,5),r=Ui(t,e,o,n,100).getOr(n);return((e,t,o)=>e.point(t)>o.getInnerHeight()?C.some(e.point(t)-o.getInnerHeight()):e.point(t)<0?C.some(-e.point(t)):C.none())(e,r,t).fold((()=>t.situsFromPoint(r.left,e.point(r))),(o=>(t.scrollBy(0,o),t.situsFromPoint(r.left,e.point(r)-o))))},Ki={tryUp:w(Gi,{point:e=>e.top,adjuster:(e,t,o,n,r)=>{const s=Mi(r,5);return Math.abs(o.top-n.top)<1||o.bottome.getSelection().bind((n=>((e,t,o,n)=>{const r=zi(t)?((e,t,o)=>o.traverse(t).orThunk((()=>Ai(t,o.gather,e))).map(o.relative))(e,t,n):Li(e,t,o,n);return r.map((e=>({start:e,finish:e})))})(t,n.finish,n.foffset,o).fold((()=>C.some(Fs(n.finish,n.foffset))),(r=>{const s=e.fromSitus(r);return l=_i.verify(e,n.finish,n.foffset,s.finish,s.foffset,o.failure,t),_i.cata(l,(e=>C.none()),(()=>C.none()),(e=>C.some(Fs(e,0))),(e=>C.some(Fs(e,br(e)))));var l})))),Ji=(e,t,o,n,r,s)=>0===s?C.none():Zi(e,t,o,n,r).bind((l=>{const a=e.fromSitus(l),c=_i.verify(e,o,n,a.finish,a.foffset,r.failure,t);return _i.cata(c,(()=>C.none()),(()=>C.some(l)),(l=>Re(o,l)&&0===n?Qi(e,o,n,Mi,r):Ji(e,t,l,0,r,s-1)),(l=>Re(o,l)&&n===br(l)?Qi(e,o,n,Wi,r):Ji(e,t,l,br(l),r,s-1)))})),Qi=(e,t,o,n,r)=>Fi(e,t,o).bind((t=>Xi(e,r,n(t,Ki.getJumpSize())))),Xi=(e,t,o)=>{const n=Bo().browser;return n.isChromium()||n.isSafari()||n.isFirefox()?t.retry(e,o):C.none()},Zi=(e,t,o,n,r)=>Fi(e,o,n).bind((t=>Xi(e,r,t))),em=(e,t,o,n,r)=>bt(n,"td,th",t).bind((n=>bt(n,"table",t).bind((s=>((e,t)=>ft(e,(e=>Ne(e).exists((e=>Re(e,t)))),void 0).isSome())(r,s)?((e,t,o)=>Yi(e,t,o).bind((n=>Ji(e,t,n.element,n.offset,o,20).map(e.fromSitus))))(e,t,o).bind((e=>bt(e.finish,"td,th",t).map((t=>({start:n,finish:t,range:e}))))):C.none())))),tm=(e,t,o,n,r,s)=>s(n,t).orThunk((()=>em(e,t,o,n,r).map((e=>{const t=e.range;return ti(C.some(hi(t.start,t.soffset,t.finish,t.foffset)),!0)})))),om=(e,t)=>bt(e,"tr",t).bind((e=>bt(e,"table",t).bind((o=>{const n=dt(o,"tr");return Re(e,n[0])?((e,t,o)=>Ri(Oi,e,(e=>Cr(e).isSome()),o))(o,0,t).map((e=>{const t=br(e);return ti(C.some(hi(e,t,e,t)),!0)})):C.none()})))),nm=(e,t)=>bt(e,"tr",t).bind((e=>bt(e,"table",t).bind((o=>{const n=dt(o,"tr");return Re(e,n[n.length-1])?((e,t,o)=>Di(Oi,e,(e=>xr(e).isSome()),o))(o,0,t).map((e=>ti(C.some(hi(e,0,e,0)),!0))):C.none()})))),rm=(e,t,o,n,r,s,l)=>em(e,o,n,r,s).bind((e=>pi(t,o,e.start,e.finish,l))),sm=e=>{let t=e;return{get:()=>t,set:e=>{t=e}}},lm=()=>{const e=(e=>{const t=sm(C.none()),o=()=>t.get().each(e);return{clear:()=>{o(),t.set(C.none())},isSet:()=>t.get().isSome(),get:()=>t.get(),set:e=>{o(),t.set(C.some(e))}}})(f);return{...e,on:t=>e.get().each(t)}},am=(e,t)=>bt(e,"td,th",t),cm=e=>Be(e).exists(Qr),im={traverse:Ae,gather:Ei,relative:ei.before,retry:Ki.tryDown,failure:_i.failedDown},mm={traverse:ze,gather:ki,relative:ei.before,retry:Ki.tryUp,failure:_i.failedUp},dm=e=>t=>t===e,um=dm(38),fm=dm(40),gm=e=>e>=37&&e<=40,hm={isBackward:dm(37),isForward:dm(39)},pm={isBackward:dm(39),isForward:dm(37)},wm=Zl([{domRange:["rng"]},{relative:["startSitu","finishSitu"]},{exact:["start","soffset","finish","foffset"]}]),bm={domRange:wm.domRange,relative:wm.relative,exact:wm.exact,exactFromRange:e=>wm.exact(e.start,e.soffset,e.finish,e.foffset),getWin:e=>{const t=(e=>e.match({domRange:e=>xe.fromDom(e.startContainer),relative:(e,t)=>ei.getStart(e),exact:(e,t,o,n)=>e}))(e);return xe.fromDom(Ee(t).dom.defaultView)},range:ui},vm=document.caretPositionFromPoint?(e,t,o)=>{var n,r;return C.from(null===(r=(n=e.dom).caretPositionFromPoint)||void 0===r?void 0:r.call(n,t,o)).bind((t=>{if(null===t.offsetNode)return C.none();const o=e.dom.createRange();return o.setStart(t.offsetNode,t.offset),o.collapse(),C.some(o)}))}:document.caretRangeFromPoint?(e,t,o)=>{var n,r;return C.from(null===(r=(n=e.dom).caretRangeFromPoint)||void 0===r?void 0:r.call(n,t,o))}:C.none,ym=(e,t)=>{const o=ne(e);return"input"===o?ei.after(e):D(["br","img"],o)?0===t?ei.before(e):ei.after(e):ei.on(e,t)},xm=e=>C.from(e.getSelection()),Cm=(e,t)=>{xm(e).each((e=>{e.removeAllRanges(),e.addRange(t)}))},Sm=(e,t,o,n,r)=>{const s=li(e,t,o,n,r);Cm(e,s)},Tm=(e,t)=>mi(e,t).match({ltr:(t,o,n,r)=>{Sm(e,t,o,n,r)},rtl:(t,o,n,r)=>{xm(e).each((s=>{if(s.setBaseAndExtent)s.setBaseAndExtent(t.dom,o,n.dom,r);else if(s.extend)try{((e,t,o,n,r,s)=>{t.collapse(o.dom,n),t.extend(r.dom,s)})(0,s,t,o,n,r)}catch(s){Sm(e,n,r,t,o)}else Sm(e,n,r,t,o)}))}}),Rm=(e,t,o,n,r)=>{const s=((e,t,o,n)=>{const r=ym(e,t),s=ym(o,n);return bm.relative(r,s)})(t,o,n,r);Tm(e,s)},Dm=(e,t,o)=>{const n=((e,t)=>{const o=e.fold(ei.before,ym,ei.after),n=t.fold(ei.before,ym,ei.after);return bm.relative(o,n)})(t,o);Tm(e,n)},Om=e=>{if(e.rangeCount>0){const t=e.getRangeAt(0),o=e.getRangeAt(e.rangeCount-1);return C.some(ui(xe.fromDom(t.startContainer),t.startOffset,xe.fromDom(o.endContainer),o.endOffset))}return C.none()},km=e=>{if(null===e.anchorNode||null===e.focusNode)return Om(e);{const t=xe.fromDom(e.anchorNode),o=xe.fromDom(e.focusNode);return((e,t,o,n)=>{const r=((e,t,o,n)=>{const r=ke(e).dom.createRange();return r.setStart(e.dom,t),r.setEnd(o.dom,n),r})(e,t,o,n),s=Re(e,o)&&t===n;return r.collapsed&&!s})(t,e.anchorOffset,o,e.focusOffset)?C.some(ui(t,e.anchorOffset,o,e.focusOffset)):Om(e)}},Em=(e,t,o=!0)=>{const n=(o?ni:oi)(e,t);Cm(e,n)},Nm=e=>(e=>xm(e).filter((e=>e.rangeCount>0)).bind(km))(e).map((e=>bm.exact(e.start,e.soffset,e.finish,e.foffset))),Bm=e=>({elementFromPoint:(t,o)=>xe.fromPoint(xe.fromDom(e.document),t,o),getRect:e=>e.dom.getBoundingClientRect(),getRangedRect:(t,o,n,r)=>{const s=bm.exact(t,o,n,r);return((e,t)=>(e=>{const t=e.getClientRects(),o=t.length>0?t[0]:e.getBoundingClientRect();return o.width>0||o.height>0?C.some(o).map(ai):C.none()})(di(e,t)))(e,s)},getSelection:()=>Nm(e).map((t=>gi(e,t))),fromSitus:t=>{const o=bm.relative(t.start,t.finish);return gi(e,o)},situsFromPoint:(t,o)=>((e,t,o)=>((e,t,o)=>{const n=xe.fromDom(e.document);return vm(n,t,o).map((e=>ui(xe.fromDom(e.startContainer),e.startOffset,xe.fromDom(e.endContainer),e.endOffset)))})(e,t,o))(e,t,o).map((e=>fi(e.start,e.soffset,e.finish,e.foffset))),clearSelection:()=>{(e=>{xm(e).each((e=>e.removeAllRanges()))})(e)},collapseSelection:(t=!1)=>{Nm(e).each((o=>o.fold((e=>e.collapse(t)),((o,n)=>{const r=t?o:n;Dm(e,r,r)}),((o,n,r,s)=>{const l=t?o:r,a=t?n:s;Rm(e,l,a,l,a)}))))},setSelection:t=>{Rm(e,t.start,t.soffset,t.finish,t.foffset)},setRelativeSelection:(t,o)=>{Dm(e,t,o)},selectNode:t=>{Em(e,t,!1)},selectContents:t=>{Em(e,t)},getInnerHeight:()=>e.innerHeight,getScrollY:()=>(e=>{const t=void 0!==e?e.dom:document,o=t.body.scrollLeft||t.documentElement.scrollLeft,n=t.body.scrollTop||t.documentElement.scrollTop;return bn(o,n)})(xe.fromDom(e.document)).top,scrollBy:(t,o)=>{((e,t,o)=>{const n=(void 0!==o?o.dom:document).defaultView;n&&n.scrollBy(e,t)})(t,o,xe.fromDom(e.document))}}),_m=(e,t)=>({rows:e,cols:t}),zm=e=>gt(e,ae).exists(Qr),Am=(e,t)=>zm(e)||zm(t),Lm=e=>void 0!==e.dom.classList,Wm=(e,t)=>((e,t,o)=>{const n=((e,t)=>{const o=pe(e,t);return void 0===o||""===o?[]:o.split(" ")})(e,t).concat([o]);return ge(e,t,n.join(" ")),!0})(e,"class",t),Mm=(e,t)=>{Lm(e)?e.dom.classList.add(t):Wm(e,t)},jm=(e,t)=>Lm(e)&&e.dom.classList.contains(t),Pm=()=>({tag:"none"}),Im=e=>({tag:"multiple",elements:e}),Fm=e=>({tag:"single",element:e}),Hm=e=>{const t=xe.fromDom((e=>{if(nt()&&m(e.target)){const t=xe.fromDom(e.target);if(ce(t)&&m(t.dom.shadowRoot)&&e.composed&&e.composedPath){const t=e.composedPath();if(t)return H(t)}}return C.from(e.target)})(e).getOr(e.target)),o=()=>e.stopPropagation(),n=()=>e.preventDefault(),r=(s=n,l=o,(...e)=>s(l.apply(null,e)));var s,l;return((e,t,o,n,r,s,l)=>({target:e,x:t,y:o,stop:n,prevent:r,kill:s,raw:l}))(t,e.clientX,e.clientY,o,n,r,e)},$m=(e,t,o,n)=>{e.dom.removeEventListener(t,o,n)},Vm=x,qm=(e,t,o)=>((e,t,o,n)=>((e,t,o,n,r)=>{const s=((e,t)=>o=>{e(o)&&t(Hm(o))})(o,n);return e.dom.addEventListener(t,s,r),{unbind:w($m,e,t,s,r)}})(e,t,o,n,!1))(e,t,Vm,o),Um=Hm,Gm=e=>!jm(xe.fromDom(e.target),"ephox-snooker-resizer-bar"),Km=(e,t)=>{const o=(r=As.selectedSelector,{get:()=>Rs(xe.fromDom(e.getBody()),r).fold((()=>js(os(e),es(e)).fold(Pm,Fm)),Im)}),n=((e,t,o)=>{const n=t=>{be(t,e.selected),be(t,e.firstSelected),be(t,e.lastSelected)},r=t=>{ge(t,e.selected,"1")},s=e=>{l(e),o()},l=t=>{const o=dt(t,`${e.selectedSelector},${e.firstSelectedSelector},${e.lastSelectedSelector}`);N(o,n)};return{clearBeforeUpdate:l,clear:s,selectRange:(o,n,l,a)=>{s(o),N(n,r),ge(l,e.firstSelected,"1"),ge(a,e.lastSelected,"1"),t(n,l,a)},selectedSelector:e.selectedSelector,firstSelectedSelector:e.firstSelectedSelector,lastSelectedSelector:e.lastSelectedSelector}})(As,((t,o,n)=>{Kt(o).each((r=>{const s=Wr(e),l=Br(f,xe.fromDom(e.getDoc()),s),a=((e,t,o)=>{const n=Zo(e);return Ol(n,t).map((e=>{const t=xl(n,o,!1),{rows:r}=qo(t),s=((e,t)=>{const o=e.slice(0,t[t.length-1].row+1),n=Cl(o);return j(n,(e=>{const o=e.cells.slice(0,t[t.length-1].column+1);return E(o,(e=>e.element))}))})(r,e),l=((e,t)=>{const o=e.slice(t[0].row+t[0].rowspan-1,e.length),n=Cl(o);return j(n,(e=>{const o=e.cells.slice(t[0].column+t[0].colspan-1,e.cells.length);return E(o,(e=>e.element))}))})(r,e);return{upOrLeftCells:s,downOrRightCells:l}}))})(r,{selection:Ps(e)},l);((e,t,o,n,r)=>{e.dispatch("TableSelectionChange",{cells:t,start:o,finish:n,otherCells:r})})(e,t,o,n,a)}))}),(()=>(e=>{e.dispatch("TableSelectionClear")})(e)));var r;return e.on("init",(o=>{const r=e.getWin(),s=Zr(e),l=es(e),a=((e,t,o,n)=>{const r=((e,t,o,n)=>{const r=lm(),s=r.clear,l=s=>{r.on((r=>{n.clearBeforeUpdate(t),am(s.target,o).each((l=>{xs(r,l,o).each((o=>{const r=o.boxes.getOr([]);if(1===r.length){const o=r[0],l="false"===Xr(o),a=vt(Jr(s.target),o,Re);l&&a&&(n.selectRange(t,r,o,o),e.selectContents(o))}else r.length>1&&(n.selectRange(t,r,o.start,o.finish),e.selectContents(l))}))}))}))};return{clearstate:s,mousedown:e=>{n.clear(t),am(e.target,o).filter(cm).each(r.set)},mouseover:e=>{l(e)},mouseup:e=>{l(e),s()}}})(Bm(e),t,o,n);return{clearstate:r.clearstate,mousedown:r.mousedown,mouseover:r.mouseover,mouseup:r.mouseup}})(r,s,l,n),c=((e,t,o,n)=>{const r=Bm(e),s=()=>(n.clear(t),C.none());return{keydown:(e,l,a,c,i,m)=>{const d=e.raw,u=d.which,f=!0===d.shiftKey,g=Cs(t,n.selectedSelector).fold((()=>(gm(u)&&!f&&n.clearBeforeUpdate(t),gm(u)&&f&&!Am(l,c)?C.none:fm(u)&&f?w(rm,r,t,o,im,c,l,n.selectRange):um(u)&&f?w(rm,r,t,o,mm,c,l,n.selectRange):fm(u)?w(tm,r,o,im,c,l,nm):um(u)?w(tm,r,o,mm,c,l,om):C.none)),(e=>{const o=o=>()=>{const s=V(o,(o=>((e,t,o,n,r)=>Ts(n,e,t,r.firstSelectedSelector,r.lastSelectedSelector).map((e=>(r.clearBeforeUpdate(o),r.selectRange(o,e.boxes,e.start,e.finish),e.boxes))))(o.rows,o.cols,t,e,n)));return s.fold((()=>Ss(t,n.firstSelectedSelector,n.lastSelectedSelector).map((e=>{const o=fm(u)||m.isForward(u)?ei.after:ei.before;return r.setRelativeSelection(ei.on(e.first,0),o(e.table)),n.clear(t),ti(C.none(),!0)}))),(e=>C.some(ti(C.none(),!0))))};return gm(u)&&f&&!Am(l,c)?C.none:fm(u)&&f?o([_m(1,0)]):um(u)&&f?o([_m(-1,0)]):m.isBackward(u)&&f?o([_m(0,-1),_m(-1,0)]):m.isForward(u)&&f?o([_m(0,1),_m(1,0)]):gm(u)&&!f?s:C.none}));return g()},keyup:(e,r,s,l,a)=>Cs(t,n.selectedSelector).fold((()=>{const c=e.raw,i=c.which;return!0===c.shiftKey&&gm(i)&&Am(r,l)?((e,t,o,n,r,s,l)=>Re(o,r)&&n===s?C.none():bt(o,"td,th",t).bind((o=>bt(r,"td,th",t).bind((n=>pi(e,t,o,n,l))))))(t,o,r,s,l,a,n.selectRange):C.none()}),C.none)}})(r,s,l,n),i=((e,t,o,n)=>{const r=Bm(e);return(e,s)=>{n.clearBeforeUpdate(t),xs(e,s,o).each((e=>{const o=e.boxes.getOr([]);n.selectRange(t,o,e.start,e.finish),r.selectContents(s),r.collapseSelection()}))}})(r,s,l,n);e.on("TableSelectorChange",(e=>i(e.start,e.finish)));const m=(t,o)=>{(e=>!0===e.raw.shiftKey)(t)&&(o.kill&&t.kill(),o.selection.each((t=>{const o=bm.relative(t.start,t.finish),n=di(r,o);e.selection.setRng(n)})))},d=e=>0===e.button,u=(()=>{const e=sm(xe.fromDom(s)),t=sm(0);return{touchEnd:o=>{const n=xe.fromDom(o.target);if(ue("td")(n)||ue("th")(n)){const r=e.get(),s=t.get();Re(r,n)&&o.timeStamp-s<300&&(o.preventDefault(),i(n,n))}e.set(n),t.set(o.timeStamp)}}})();e.on("dragstart",(e=>{a.clearstate()})),e.on("mousedown",(e=>{d(e)&&Gm(e)&&a.mousedown(Um(e))})),e.on("mouseover",(e=>{var t;void 0!==(t=e).buttons&&0==(1&t.buttons)||!Gm(e)||a.mouseover(Um(e))})),e.on("mouseup",(e=>{d(e)&&Gm(e)&&a.mouseup(Um(e))})),e.on("touchend",u.touchEnd),e.on("keyup",(t=>{const o=Um(t);if(o.raw.shiftKey&&gm(o.raw.which)){const t=e.selection.getRng(),n=xe.fromDom(t.startContainer),r=xe.fromDom(t.endContainer);c.keyup(o,n,t.startOffset,r,t.endOffset).each((e=>{m(o,e)}))}})),e.on("keydown",(o=>{const n=Um(o);t.hide();const r=e.selection.getRng(),s=xe.fromDom(r.startContainer),l=xe.fromDom(r.endContainer),a=un(hm,pm)(xe.fromDom(e.selection.getStart()));c.keydown(n,s,r.startOffset,l,r.endOffset,a).each((e=>{m(n,e)})),t.show()})),e.on("NodeChange",(()=>{const t=e.selection,o=xe.fromDom(t.getStart()),r=xe.fromDom(t.getEnd());vs(Kt,[o,r]).fold((()=>n.clear(s)),f)}))})),e.on("PreInit",(()=>{e.serializer.addTempAttr(As.firstSelected),e.serializer.addTempAttr(As.lastSelected)})),{getSelectedCells:()=>((e,t,o,n)=>{switch(e.tag){case"none":return t();case"single":return(e=>[e.dom])(e.element);case"multiple":return(e=>E(e,(e=>e.dom)))(e.elements)}})(o.get(),g([])),clearSelectedCells:e=>n.clear(xe.fromDom(e))}},Ym=e=>{let t=[];return{bind:e=>{if(void 0===e)throw new Error("Event bind error: undefined handler");t.push(e)},unbind:e=>{t=_(t,(t=>t!==e))},trigger:(...o)=>{const n={};N(e,((e,t)=>{n[e]=o[t]})),N(t,(e=>{e(n)}))}}},Jm=e=>({registry:K(e,(e=>({bind:e.bind,unbind:e.unbind}))),trigger:K(e,(e=>e.trigger))}),Qm=e=>e.slice(0).sort(),Xm=(e,t)=>{const o=_(t,(t=>!D(e,t)));o.length>0&&(e=>{throw new Error("Unsupported keys for object: "+Qm(e).join(", "))})(o)},Zm=e=>((e,t)=>((e,t,o)=>{if(0===t.length)throw new Error("You must specify at least one required field.");return((e,t)=>{if(!l(t))throw new Error("The "+e+" fields must be an array. Was: "+t+".");N(t,(t=>{if(!r(t))throw new Error("The value "+t+" in the "+e+" fields was not a string.")}))})("required",t),(e=>{const t=Qm(e);L(t,((e,o)=>o{throw new Error("The field: "+e+" occurs more than once in the combined fields: ["+t.join(", ")+"].")}))})(t),n=>{const r=q(n);P(t,(e=>D(r,e)))||((e,t)=>{throw new Error("All required keys ("+Qm(e).join(", ")+") were not specified. Specified keys were: "+Qm(t).join(", ")+".")})(t,r),e(t,r);const s=_(t,(e=>!o.validate(n[e],e)));return s.length>0&&((e,t)=>{throw new Error("All values need to be of type: "+t+". Keys ("+Qm(e).join(", ")+") were not.")})(s,o.label),n}})(e,t,{validate:d,label:"function"}))(Xm,e),ed=Zm(["compare","extract","mutate","sink"]),td=Zm(["element","start","stop","destroy"]),od=Zm(["forceDrop","drop","move","delayDrop"]),nd=()=>{const e=(()=>{const e=Jm({move:Ym(["info"])});return{onEvent:f,reset:f,events:e.registry}})(),t=(()=>{let e=C.none();const t=Jm({move:Ym(["info"])});return{onEvent:(o,n)=>{n.extract(o).each((o=>{const r=((t,o)=>{const n=e.map((e=>t.compare(e,o)));return e=C.some(o),n})(n,o);r.each((e=>{t.trigger.move(e)}))}))},reset:()=>{e=C.none()},events:t.registry}})();let o=e;return{on:()=>{o.reset(),o=t},off:()=>{o.reset(),o=e},isOn:()=>o===t,onEvent:(e,t)=>{o.onEvent(e,t)},events:t.events}},rd=e=>{const t=e.replace(/\./g,"-");return{resolve:e=>t+"-"+e}},sd=rd("ephox-dragster").resolve;var ld=ed({compare:(e,t)=>bn(t.left-e.left,t.top-e.top),extract:e=>C.some(bn(e.x,e.y)),sink:(e,t)=>{const o=(e=>{const t={layerClass:sd("blocker"),...e},o=xe.fromTag("div");return ge(o,"role","presentation"),Bt(o,{position:"fixed",left:"0px",top:"0px",width:"100%",height:"100%"}),Mm(o,sd("blocker")),Mm(o,t.layerClass),{element:g(o),destroy:()=>{qe(o)}}})(t),n=qm(o.element(),"mousedown",e.forceDrop),r=qm(o.element(),"mouseup",e.drop),s=qm(o.element(),"mousemove",e.move),l=qm(o.element(),"mouseout",e.delayDrop);return td({element:o.element,start:e=>{Ie(e,o.element())},stop:()=>{qe(o.element())},destroy:()=>{o.destroy(),r.unbind(),s.unbind(),l.unbind(),n.unbind()}})},mutate:(e,t)=>{e.mutate(t.left,t.top)}});const ad=rd("ephox-snooker").resolve,cd=ad("resizer-bar"),id=ad("resizer-rows"),md=ad("resizer-cols"),dd=e=>{const t=dt(e.parent(),"."+cd);N(t,qe)},ud=(e,t,o)=>{const n=e.origin();N(t,(t=>{t.each((t=>{const r=o(n,t);Mm(r,cd),Ie(e.parent(),r)}))}))},fd=(e,t,o,n,r)=>{const s=yn(o),l=t.isResizable,a=n.length>0?_n.positions(n,o):[],c=a.length>0?((e,t)=>j(e.all,((e,o)=>t(e.element)?[o]:[])))(e,l):[];((e,t,o,n)=>{ud(e,t,((e,t)=>{const r=((e,t,o,n,r)=>{const s=xe.fromTag("div");return Bt(s,{position:"absolute",left:t+"px",top:o-3.5+"px",height:"7px",width:n+"px"}),he(s,{"data-row":e,role:"presentation"}),s})(t.row,o.left-e.left,t.y-e.top,n);return Mm(r,id),r}))})(t,_(a,((e,t)=>O(c,(e=>t===e)))),s,Wo(o));const i=r.length>0?An.positions(r,o):[],m=i.length>0?((e,t)=>{const o=[];return k(e.grid.columns,(n=>{an(e,n).map((e=>e.element)).forall(t)&&o.push(n)})),_(o,(o=>{const n=nn(e,(e=>e.column===o));return P(n,(e=>t(e.element)))}))})(e,l):[];((e,t,o,n)=>{ud(e,t,((e,t)=>{const r=((e,t,o,n,r)=>{const s=xe.fromTag("div");return Bt(s,{position:"absolute",left:t-3.5+"px",top:o+"px",height:r+"px",width:"7px"}),he(s,{"data-column":e,role:"presentation"}),s})(t.col,t.x-e.left,o.top-e.top,0,n);return Mm(r,md),r}))})(t,_(i,((e,t)=>O(m,(e=>t===e)))),s,pn(o))},gd=(e,t)=>{if(dd(e),e.isResizable(t)){const o=Zo(t),n=dn(o),r=cn(o);fd(o,e,t,n,r)}},hd=(e,t)=>{const o=dt(e.parent(),"."+cd);N(o,t)},pd=e=>{hd(e,(e=>{Nt(e,"display","none")}))},wd=e=>{hd(e,(e=>{Nt(e,"display","block")}))},bd=ad("resizer-bar-dragging"),vd=e=>{const t=(()=>{const e=Jm({drag:Ym(["xDelta","yDelta","target"])});let t=C.none();const o=(()=>{const e=Jm({drag:Ym(["xDelta","yDelta"])});return{mutate:(t,o)=>{e.trigger.drag(t,o)},events:e.registry}})();return o.events.drag.bind((o=>{t.each((t=>{e.trigger.drag(o.xDelta,o.yDelta,t)}))})),{assign:e=>{t=C.some(e)},get:()=>t,mutate:o.mutate,events:e.registry}})(),o=((e,t={})=>{var o;return((e,t,o)=>{let n=!1;const r=Jm({start:Ym([]),stop:Ym([])}),s=nd(),l=()=>{m.stop(),s.isOn()&&(s.off(),r.trigger.stop())},c=((e,t)=>{let o=null;const n=()=>{a(o)||(clearTimeout(o),o=null)};return{cancel:n,throttle:(...t)=>{n(),o=setTimeout((()=>{o=null,e.apply(null,t)}),200)}}})(l);s.events.move.bind((o=>{t.mutate(e,o.info)}));const i=e=>(...t)=>{n&&e.apply(null,t)},m=t.sink(od({forceDrop:l,drop:i(l),move:i((e=>{c.cancel(),s.onEvent(e,t)})),delayDrop:i(c.throttle)}),o);return{element:m.element,go:e=>{m.start(e),s.on(),r.trigger.start()},on:()=>{n=!0},off:()=>{n=!1},isActive:()=>n,destroy:()=>{m.destroy()},events:r.registry}})(e,null!==(o=t.mode)&&void 0!==o?o:ld,t)})(t,{});let n=C.none();const r=(e,t)=>C.from(pe(e,t));t.events.drag.bind((e=>{r(e.target,"data-row").each((t=>{const o=It(e.target,"top");Nt(e.target,"top",o+e.yDelta+"px")})),r(e.target,"data-column").each((t=>{const o=It(e.target,"left");Nt(e.target,"left",o+e.xDelta+"px")}))}));const s=(e,t)=>It(e,t)-Wt(e,"data-initial-"+t,0);o.events.stop.bind((()=>{t.get().each((t=>{n.each((o=>{r(t,"data-row").each((e=>{const n=s(t,"top");be(t,"data-initial-top"),d.trigger.adjustHeight(o,n,parseInt(e,10))})),r(t,"data-column").each((e=>{const n=s(t,"left");be(t,"data-initial-left"),d.trigger.adjustWidth(o,n,parseInt(e,10))})),gd(e,o)}))}))}));const l=(n,r)=>{d.trigger.startAdjust(),t.assign(n),ge(n,"data-initial-"+r,It(n,r)),Mm(n,bd),Nt(n,"opacity","0.2"),o.go(e.parent())},c=qm(e.parent(),"mousedown",(e=>{var t;t=e.target,jm(t,id)&&l(e.target,"top"),(e=>jm(e,md))(e.target)&&l(e.target,"left")})),i=t=>Re(t,e.view()),m=qm(e.view(),"mouseover",(t=>{var r;(r=t.target,bt(r,"table",i).filter(Qr)).fold((()=>{lt(t.target)&&dd(e)}),(t=>{o.isActive()&&(n=C.some(t),gd(e,t))}))})),d=Jm({adjustHeight:Ym(["table","delta","row"]),adjustWidth:Ym(["table","delta","column"]),startAdjust:Ym([])});return{destroy:()=>{c.unbind(),m.unbind(),o.destroy(),dd(e)},refresh:t=>{gd(e,t)},on:o.on,off:o.off,hideBars:w(pd,e),showBars:w(wd,e),events:d.registry}},yd=(e,t,o)=>{const n=_n,r=An,s=vd(e),l=Jm({beforeResize:Ym(["table","type"]),afterResize:Ym(["table","type"]),startDrag:Ym([])});return s.events.adjustHeight.bind((e=>{const t=e.table;l.trigger.beforeResize(t,"row");((e,t,o,n)=>{const r=Zo(e),s=((e,t,o)=>lr(e,t,o,Yn,(e=>e.getOrThunk(Ht))))(r,e,n),l=E(s,((e,n)=>o===n?Math.max(t+e,Ht()):e)),a=oa(r,l),c=((e,t)=>E(e.all,((e,o)=>({element:e.element,height:t[o]}))))(r,l);N(c,(e=>{$n(e.element,e.height)})),N(a,(e=>{$n(e.element,e.height)}));const i=z(l,((e,t)=>e+t),0);$n(e,i)})(t,n.delta(e.delta,t),e.row,n),l.trigger.afterResize(t,"row")})),s.events.startAdjust.bind((e=>{l.trigger.startDrag()})),s.events.adjustWidth.bind((e=>{const n=e.table;l.trigger.beforeResize(n,"col");const s=r.delta(e.delta,n),a=o(n);ra(n,s,e.column,t,a),l.trigger.afterResize(n,"col")})),{on:s.on,off:s.off,refreshBars:s.refresh,hideBars:s.hideBars,showBars:s.showBars,destroy:s.destroy,events:l.registry}},xd=e=>m(e)&&"TABLE"===e.nodeName,Cd="bar-",Sd=e=>"false"!==pe(e,"data-mce-resize"),Td=e=>{const t=lm(),o=lm(),n=lm();let r,s;const l=t=>fc(e,t),a=()=>Pr(e)?el():Zs();return e.on("init",(()=>{const r=((e,t)=>e.inline?((e,t,o)=>({parent:g(t),view:g(e),origin:g(bn(0,0)),isResizable:o}))(xe.fromDom(e.getBody()),(()=>{const e=xe.fromTag("div");return Bt(e,{position:"static",height:"0",width:"0",padding:"0",margin:"0",border:"0"}),Ie(at(xe.fromDom(document)),e),e})(),t):((e,t)=>{const o=me(e)?(e=>xe.fromDom(Ee(e).dom.documentElement))(e):e;return{parent:g(o),view:g(e),origin:g(bn(0,0)),isResizable:t}})(xe.fromDom(e.getDoc()),t))(e,Sd);if(n.set(r),(e=>{const t=e.options.get("object_resizing");return D(t.split(","),"table")})(e)&&qr(e)){const n=a(),s=yd(r,n,l);s.on(),s.events.startDrag.bind((o=>{t.set(e.selection.getRng())})),s.events.beforeResize.bind((t=>{const o=t.table.dom;((e,t,o,n,r)=>{e.dispatch("ObjectResizeStart",{target:t,width:o,height:n,origin:r})})(e,o,ns(o),rs(o),Cd+t.type)})),s.events.afterResize.bind((o=>{const n=o.table,r=n.dom;ts(n),t.on((t=>{e.selection.setRng(t),e.focus()})),((e,t,o,n,r)=>{e.dispatch("ObjectResized",{target:t,width:o,height:n,origin:r})})(e,r,ns(r),rs(r),Cd+o.type),e.undoManager.add()})),o.set(s)}})),e.on("ObjectResizeStart",(t=>{const o=t.target;if(xd(o)){const n=xe.fromDom(o);N(e.dom.select(".mce-clonedresizable"),(t=>{e.dom.addClass(t,"mce-"+jr(e)+"-columns")})),!kc(n)&&$r(e)?_c(n):!Oc(n)&&Hr(e)&&Bc(n),Ec(n)&&Tt(t.origin,Cd)&&Bc(n),r=t.width,s=Vr(e)?"":((e,t)=>{const o=e.dom.getStyle(t,"width")||e.dom.getAttrib(t,"width");return C.from(o).filter(Ot)})(e,o).getOr("")}})),e.on("ObjectResized",(t=>{const o=t.target;if(xd(o)){const n=xe.fromDom(o),c=t.origin;Tt(c,"corner-")&&((t,o,n)=>{const c=Rt(o,"e");if(""===s&&Bc(t),n!==r&&""!==s){Nt(t,"width",s);const o=a(),i=l(t),m=Pr(e)||c?(e=>tl(e).columns)(t)-1:0;ra(t,n-r,m,o,i)}else if((e=>/^(\d+(\.\d+)?)%$/.test(e))(s)){const e=parseFloat(s.replace("%",""));Nt(t,"width",n*e/r+"%")}(e=>/^(\d+(\.\d+)?)px$/.test(e))(s)&&(e=>{const t=Zo(e);ln(t)||N(Ut(e),(e=>{const t=_t(e,"width");Nt(e,"width",t),be(e,"width")}))})(t)})(n,c,t.width),ts(n),ic(e,n.dom,mc)}})),e.on("SwitchMode",(()=>{o.on((t=>{e.mode.isReadOnly()?t.hideBars():t.showBars()}))})),e.on("dragstart dragend",(e=>{o.on((t=>{"dragstart"===e.type?(t.hideBars(),t.off()):(t.on(),t.showBars())}))})),e.on("remove",(()=>{o.on((e=>{e.destroy()})),n.on((t=>{((e,t)=>{e.inline&&qe(t.parent())})(e,t)}))})),{refresh:e=>{o.on((t=>t.refreshBars(xe.fromDom(e))))},hide:()=>{o.on((e=>e.hideBars()))},show:()=>{o.on((e=>e.showBars()))}}},Rd=e=>{(e=>{const t=e.options.register;t("table_clone_elements",{processor:"string[]"}),t("table_use_colgroups",{processor:"boolean",default:!0}),t("table_header_type",{processor:e=>{const t=D(["section","cells","sectionCells","auto"],e);return t?{value:e,valid:t}:{valid:!1,message:"Must be one of: section, cells, sectionCells or auto."}},default:"section"}),t("table_sizing_mode",{processor:"string",default:"auto"}),t("table_default_attributes",{processor:"object",default:{border:"1"}}),t("table_default_styles",{processor:"object",default:{"border-collapse":"collapse"}}),t("table_column_resizing",{processor:e=>{const t=D(["preservetable","resizetable"],e);return t?{value:e,valid:t}:{valid:!1,message:"Must be preservetable, or resizetable."}},default:"preservetable"}),t("table_resize_bars",{processor:"boolean",default:!0}),t("table_style_by_css",{processor:"boolean",default:!0}),t("table_merge_content_on_paste",{processor:"boolean",default:!0})})(e);const t=Td(e),o=Km(e,t),n=gc(e,t,o);return Xc(e,n),((e,t)=>{const o=es(e),n=t=>js(os(e)).bind((n=>Kt(n,o).map((o=>{const r=Ls(Ps(e),o,n);return t(o,r)})))).getOr("");G({mceTableRowType:()=>n(t.getTableRowType),mceTableCellType:()=>n(t.getTableCellType),mceTableColType:()=>n(t.getTableColType)},((t,o)=>e.addQueryValueHandler(o,t)))})(e,n),Is(e,n),{getSelectedCells:o.getSelectedCells,clearSelectedCells:o.clearSelectedCells}};e.add("dom",(e=>({table:Rd(e)})))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/accordion/plugin.min.js b/static/js/tinymce/plugins/accordion/plugin.min.js new file mode 100644 index 0000000..8a39423 --- /dev/null +++ b/static/js/tinymce/plugins/accordion/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");let t=0;const o=e=>t=>typeof t===e,n=e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(o=n=e,(r=String).prototype.isPrototypeOf(o)||(null===(s=n.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var o,n,r,s})(e),r=o("boolean"),s=e=>null==e,i=e=>!s(e),a=o("function"),d=o("number"),l=e=>()=>e,c=(e,t)=>e===t,m=l(!1);class u{constructor(e,t){this.tag=e,this.value=t}static some(e){return new u(!0,e)}static none(){return u.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?u.some(e(this.value)):u.none()}bind(e){return this.tag?e(this.value):u.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:u.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return i(e)?u.some(e):u.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}u.singletonNone=new u(!1);const p=Array.prototype.indexOf,g=(e,t)=>{return o=e,n=t,p.call(o,n)>-1;var o,n},h=(e,t)=>{const o=e.length,n=new Array(o);for(let r=0;r{for(let o=0,n=e.length;oe.dom.nodeName.toLowerCase(),w=e=>e.dom.nodeType,b=e=>t=>w(t)===e,N=b(1),T=b(3),A=b(9),C=b(11),S=(e,t,o)=>{if(!(n(o)||r(o)||d(o)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",o,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,o+"")},x=(e,t)=>{const o=e.dom.getAttribute(t);return null===o?void 0:o},D=(e,t)=>u.from(x(e,t)),E=(e,t)=>{e.dom.removeAttribute(t)},M=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},P={fromHtml:(e,t)=>{const o=(t||document).createElement("div");if(o.innerHTML=e,!o.hasChildNodes()||o.childNodes.length>1){const t="HTML does not have a single root node";throw console.error(t,e),new Error(t)}return M(o.childNodes[0])},fromTag:(e,t)=>{const o=(t||document).createElement(e);return M(o)},fromText:(e,t)=>{const o=(t||document).createTextNode(e);return M(o)},fromDom:M,fromPoint:(e,t,o)=>u.from(e.dom.elementFromPoint(t,o)).map(M)},k=(e,t)=>{const o=e.dom;if(1!==o.nodeType)return!1;{const e=o;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},B=e=>1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType||0===e.childElementCount,O=k,$=(L=/^\s+|\s+$/g,e=>e.replace(L,""));var L;const R=e=>void 0!==e.style&&a(e.style.getPropertyValue),I=e=>A(e)?e:P.fromDom(e.dom.ownerDocument),V=e=>u.from(e.dom.parentNode).map(P.fromDom),j=e=>u.from(e.dom.nextSibling).map(P.fromDom),q=e=>h(e.dom.childNodes,P.fromDom),F=a(Element.prototype.attachShadow)&&a(Node.prototype.getRootNode)?e=>P.fromDom(e.dom.getRootNode()):I,H=e=>P.fromDom(e.dom.host),z=e=>{const t=T(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const o=t.ownerDocument;return(e=>{const t=F(e);return C(o=t)&&i(o.dom.host)?u.some(t):u.none();var o})(P.fromDom(t)).fold((()=>o.body.contains(t)),(n=z,r=H,e=>n(r(e))));var n,r},K=(e,t)=>R(e)?e.style.getPropertyValue(t):"",U=(e,t)=>{V(e).each((o=>{o.dom.insertBefore(t.dom,e.dom)}))},Y=(e,t)=>{j(e).fold((()=>{V(e).each((e=>{_(e,t)}))}),(e=>{U(e,t)}))},_=(e,t)=>{e.dom.appendChild(t.dom)},G=(e,t)=>{f(t,((o,n)=>{const r=0===n?e:t[n-1];Y(r,o)}))},J=(e,t)=>{let o=[];return f(q(e),(e=>{t(e)&&(o=o.concat([e])),o=o.concat(J(e,t))})),o},Q=(e,t,o)=>{let n=e.dom;const r=a(o)?o:m;for(;n.parentNode;){n=n.parentNode;const e=P.fromDom(n);if(t(e))return u.some(e);if(r(e))break}return u.none()},W=e=>{const t=e.dom;null!==t.parentNode&&t.parentNode.removeChild(t)},X=(e,t,o)=>Q(e,(e=>k(e,t)),o),Z=(e,t)=>((e,t)=>{const o=void 0===t?document:t.dom;return B(o)?u.none():u.from(o.querySelector(e)).map(P.fromDom)})(t,e),ee=((e,t)=>{const o=t=>e(t)?u.from(t.dom.nodeValue):u.none();return{get:t=>{if(!e(t))throw new Error("Can only get text value of a text node");return o(t).getOr("")},getOption:o,set:(t,o)=>{if(!e(t))throw new Error("Can only set raw text value of a text node");t.dom.nodeValue=o}}})(T);var te=["body","p","div","article","aside","figcaption","figure","footer","header","nav","section","ol","ul","li","table","thead","tbody","tfoot","caption","tr","td","th","h1","h2","h3","h4","h5","h6","blockquote","pre","address"];const oe=(e,t)=>({element:e,offset:t}),ne=(e,t,o)=>e.property().isText(t)&&0===e.property().getText(t).trim().length||e.property().isComment(t)?o(t).bind((t=>ne(e,t,o).orThunk((()=>u.some(t))))):u.none(),re=(e,t)=>e.property().isText(t)?e.property().getText(t).length:e.property().children(t).length,se=(e,t)=>{const o=ne(e,t,e.query().prevSibling).getOr(t);if(e.property().isText(o))return oe(o,re(e,o));const n=e.property().children(o);return n.length>0?se(e,n[n.length-1]):oe(o,re(e,o))},ie=se,ae={up:l({selector:X,closest:(e,t,o)=>((e,t,o,n,r)=>((e,t)=>k(e,t))(o,n)?u.some(o):a(r)&&r(o)?u.none():t(o,n,r))(0,X,e,t,o),predicate:Q,all:(e,t)=>{const o=a(t)?t:m;let n=e.dom;const r=[];for(;null!==n.parentNode&&void 0!==n.parentNode;){const e=n.parentNode,t=P.fromDom(e);if(r.push(t),!0===o(t))break;n=e}return r}}),down:l({selector:(e,t)=>((e,t)=>{const o=void 0===t?document:t.dom;return B(o)?[]:h(o.querySelectorAll(e),P.fromDom)})(t,e),predicate:J}),styles:l({get:(e,t)=>{const o=e.dom,n=window.getComputedStyle(o).getPropertyValue(t);return""!==n||z(e)?n:K(o,t)},getRaw:(e,t)=>{const o=e.dom,n=K(o,t);return u.from(n).filter((e=>e.length>0))},set:(e,t,o)=>{((e,t,o)=>{if(!n(o))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",o,":: Element ",e),new Error("CSS value must be a string: "+o);R(e)&&e.style.setProperty(t,o)})(e.dom,t,o)},remove:(e,t)=>{((e,t)=>{R(e)&&e.style.removeProperty(t)})(e.dom,t),((e,t,o=c)=>e.exists((e=>o(e,t))))(D(e,"style").map($),"")&&E(e,"style")}}),attrs:l({get:x,set:(e,t,o)=>{S(e.dom,t,o)},remove:E,copyTo:(e,t)=>{const o=(n=e.dom.attributes,r=(e,t)=>(e[t.name]=t.value,e),s={},f(n,((e,t)=>{s=r(s,e)})),s);var n,r,s;((e,t)=>{const o=e.dom;((e,t)=>{const o=y(e);for(let n=0,r=o.length;n{S(o,t,e)}))})(t,o)}}),insert:l({before:U,after:Y,afterAll:G,append:_,appendAll:(e,t)=>{f(t,(t=>{_(e,t)}))},prepend:(e,t)=>{(e=>((e,t)=>{const o=e.dom.childNodes;return u.from(o[0]).map(P.fromDom)})(e))(e).fold((()=>{_(e,t)}),(o=>{e.dom.insertBefore(t.dom,o.dom)}))},wrap:(e,t)=>{U(e,t),_(t,e)}}),remove:l({unwrap:e=>{const t=q(e);t.length>0&&G(e,t),W(e)},remove:W}),create:l({nu:P.fromTag,clone:e=>P.fromDom(e.dom.cloneNode(!1)),text:P.fromText}),query:l({comparePosition:(e,t)=>e.dom.compareDocumentPosition(t.dom),prevSibling:e=>u.from(e.dom.previousSibling).map(P.fromDom),nextSibling:j}),property:l({children:q,name:v,parent:V,document:e=>I(e).dom,isText:T,isComment:e=>8===w(e)||"#comment"===v(e),isElement:N,isSpecial:e=>{const t=v(e);return g(["script","noscript","iframe","noframes","noembed","title","style","textarea","xmp"],t)},getLanguage:e=>N(e)?D(e,"lang"):u.none(),getText:e=>ee.get(e),setText:(e,t)=>ee.set(e,t),isBoundary:e=>!!N(e)&&("body"===v(e)||g(te,v(e))),isEmptyTag:e=>!!N(e)&&g(["br","img","hr","input"],v(e)),isNonEditable:e=>N(e)&&"false"===x(e,"contenteditable")}),eq:(e,t)=>e.dom===t.dom,is:O},de="details",le="mce-accordion",ce="mce-accordion-summary",me="mce-accordion-body",ue="div";var pe=tinymce.util.Tools.resolve("tinymce.util.Tools");const ge=e=>"SUMMARY"===(null==e?void 0:e.nodeName),he=e=>"DETAILS"===(null==e?void 0:e.nodeName),fe=e=>e.hasAttribute("open"),ye=e=>{const t=e.selection.getNode();return ge(t)||Boolean(e.dom.getParent(t,ge))},ve=e=>!ye(e)&&e.dom.isEditable(e.selection.getNode()),we=e=>u.from(e.dom.getParent(e.selection.getNode(),he)),be=e=>(e.innerHTML='
      ',e),Ne=e=>be(e.dom.create("p")),Te=e=>t=>{((e,t)=>{if(ge(null==t?void 0:t.lastChild)){const o=Ne(e);t.appendChild(o),e.selection.setCursorLocation(o,0)}})(e,t),((e,t)=>{if(!ge(null==t?void 0:t.firstChild)){const o=(e=>be(e.dom.create("summary")))(e);t.prepend(o),e.selection.setCursorLocation(o,0)}})(e,t)},Ae=(e,t)=>{const o=null!=t?t:!fe(e);return o?e.setAttribute("open","open"):e.removeAttribute("open"),o},Ce=e=>{e.addCommand("InsertAccordion",(()=>(e=>{if(!ve(e))return;const o=P.fromDom(e.getBody()),n=(e=>{const o=(new Date).getTime(),n=Math.floor(1e9*Math.random());return t++,"acc_"+n+t+String(o)})(),r=e.dom.encode(e.selection.getRng().toString()||e.translate("Accordion summary...")),s=e.dom.encode(e.translate("Accordion body...")),i=`${r}`,a=`<${ue} class="${me}">

      ${s}

      `;e.undoManager.transact((()=>{e.insertContent([`
      `,i,a,"
      "].join("")),Z(o,`[data-mce-id="${n}"]`).each((t=>{E(t,"data-mce-id"),Z(t,"summary").each((t=>{const o=e.dom.createRng(),n=ie(ae,t);o.setStart(n.element.dom,n.offset),o.setEnd(n.element.dom,n.offset),e.selection.setRng(o)}))}))}))})(e))),e.addCommand("ToggleAccordion",((t,o)=>((e,t)=>{we(e).each((o=>{((e,t,o)=>{e.dispatch("ToggledAccordion",{element:t,state:o})})(e,o,Ae(o,t))}))})(e,o))),e.addCommand("ToggleAllAccordions",((t,o)=>((e,t)=>{const o=Array.from(e.getBody().querySelectorAll("details"));0!==o.length&&(f(o,(e=>Ae(e,null!=t?t:!fe(e)))),((e,t,o)=>{e.dispatch("ToggledAllAccordions",{elements:t,state:o})})(e,o,t))})(e,o))),e.addCommand("RemoveAccordion",(()=>(e=>{we(e).each((t=>{const{nextSibling:o}=t;o?(e.selection.select(o,!0),e.selection.collapse(!0)):((e,t)=>{const o=Ne(e);t.insertAdjacentElement("afterend",o),e.selection.setCursorLocation(o,0)})(e,t),t.remove()}))})(e)))};var Se=tinymce.util.Tools.resolve("tinymce.html.Node");const xe=e=>{var t,o;return null!==(o=null===(t=e.attr("class"))||void 0===t?void 0:t.split(" "))&&void 0!==o?o:[]},De=(e,t)=>{const o=new Set([...xe(e),...t]),n=Array.from(o);n.length>0&&e.attr("class",n.join(" "))},Ee=(e,t)=>{const o=((e,o)=>{const n=[];for(let o=0,s=e.length;o0?o.join(" "):null)},Me=e=>e.name===de&&g(xe(e),le),Pe=e=>{const t=e.children();let o,n;const r=[];for(let e=0;e{const t=new Se("br",1);t.attr("data-mce-bogus","1"),e.empty(),e.append(t)};var Be=tinymce.util.Tools.resolve("tinymce.util.VK");const Oe=e=>t=>{const o=()=>t.setEnabled(ve(e));return e.on("NodeChange",o),()=>e.off("NodeChange",o)};e.add("accordion",(e=>{(e=>{const t=()=>e.execCommand("InsertAccordion");e.ui.registry.addButton("accordion",{icon:"accordion",tooltip:"Insert accordion",onSetup:Oe(e),onAction:t}),e.ui.registry.addMenuItem("accordion",{icon:"accordion",text:"Accordion",onSetup:Oe(e),onAction:t}),e.ui.registry.addToggleButton("accordiontoggle",{icon:"accordion-toggle",tooltip:"Toggle accordion",onAction:()=>e.execCommand("ToggleAccordion")}),e.ui.registry.addToggleButton("accordionremove",{icon:"remove",tooltip:"Delete accordion",onAction:()=>e.execCommand("RemoveAccordion")}),e.ui.registry.addContextToolbar("accordion",{predicate:t=>e.dom.is(t,"details")&&e.getBody().contains(t)&&e.dom.isEditable(t.parentNode),items:"accordiontoggle accordionremove",scope:"node",position:"node"})})(e),Ce(e),(e=>{(e=>{e.on("keydown",(t=>{!t.shiftKey&&t.keyCode===Be.ENTER&&ye(e)&&(t.preventDefault(),e.execCommand("ToggleAccordion"))}))})(e),e.on("ExecCommand",(t=>{const o=t.command.toLowerCase();"delete"!==o&&"forwarddelete"!==o||!(e=>we(e).isSome())(e)||(e=>{pe.each(pe.grep(e.dom.select("details",e.getBody())),Te(e))})(e)}))})(e),(e=>{e.on("PreInit",(()=>{const{serializer:t,parser:o}=e;o.addNodeFilter(de,(e=>{for(let t=0;t0)for(let e=0;e{const t=new Set([ce]);for(let o=0;o{const r="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(r,!1,!1===s?null:{"list-style-type":s})},s=t=>e=>e.options.get(t),r=s("advlist_number_styles"),n=s("advlist_bullet_styles"),i=t=>null==t,l=t=>!i(t);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return l(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=t=>e=>l(e)&&t.test(e.nodeName),d=u(/^(OL|UL|DL)$/),g=u(/^(TH|TD)$/),c=t=>i(t)||"default"===t?"":t,h=(t,e)=>s=>((t,e)=>{const s=t.selection.getNode();return e({parents:t.dom.getParents(s),element:s}),t.on("NodeChange",e),()=>t.off("NodeChange",e)})(t,(r=>((t,r)=>{const n=t.selection.getStart(!0);s.setActive(((t,e,s)=>((t,e,s)=>{for(let e=0,n=t.length;ee.nodeName===s&&((t,e)=>t.dom.isChildOf(e,t.getBody()))(t,e))))(t,r,e)),s.setEnabled(!((t,e)=>{const s=t.dom.getParent(e,"ol,ul,dl");return((t,e)=>null!==e&&!t.dom.isEditable(e))(t,s)&&t.selection.isEditable()})(t,n)&&t.selection.isEditable())})(t,r.parents))),m=(t,s,r,n,i,l)=>{l.length>1?((t,s,r,n,i,l)=>{t.ui.registry.addSplitButton(s,{tooltip:r,icon:"OL"===i?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:t=>{t(o.map(l,(t=>{const e="OL"===i?"num":"bull",s="disc"===t||"decimal"===t?"default":t,r=c(t),n=(t=>t.replace(/\-/g," ").replace(/\b\w/g,(t=>t.toUpperCase())))(t);return{type:"choiceitem",value:r,icon:"list-"+e+"-"+s,text:n}})))},onAction:()=>t.execCommand(n),onItemAction:(s,r)=>{e(t,i,r)},select:e=>{const s=(t=>{const e=t.dom.getParent(t.selection.getNode(),"ol,ul"),s=t.dom.getStyle(e,"listStyleType");return a.from(s)})(t);return s.map((t=>e===t)).getOr(!1)},onSetup:h(t,i)})})(t,s,r,n,i,l):((t,s,r,n,i,l)=>{t.ui.registry.addToggleButton(s,{active:!1,tooltip:r,icon:"OL"===i?"ordered-list":"unordered-list",onSetup:h(t,i),onAction:()=>t.queryCommandState(n)||""===l?t.execCommand(n):e(t,i,l)})})(t,s,r,n,i,c(l[0]))};t.add("advlist",(t=>{t.hasPlugin("lists")?((t=>{const e=t.options.register;e("advlist_number_styles",{processor:"string[]",default:"default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman".split(",")}),e("advlist_bullet_styles",{processor:"string[]",default:"default,circle,square".split(",")})})(t),(t=>{m(t,"numlist","Numbered list","InsertOrderedList","OL",r(t)),m(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))})(t),(t=>{t.addCommand("ApplyUnorderedListStyle",((s,r)=>{e(t,"UL",r["list-style-type"])})),t.addCommand("ApplyOrderedListStyle",((s,r)=>{e(t,"OL",r["list-style-type"])}))})(t)):console.error("Please use the Lists plugin together with the Advanced List plugin.")}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/anchor/plugin.min.js b/static/js/tinymce/plugins/anchor/plugin.min.js new file mode 100644 index 0000000..e1c650f --- /dev/null +++ b/static/js/tinymce/plugins/anchor/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;ot=>{const o=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",o),o(),()=>{e.off("NodeChange",o)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>{const o=e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind,n=u(e)(t);return()=>{o(),n()}}}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t,onSetup:u(e)})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/autolink/plugin.min.js b/static/js/tinymce/plugins/autolink/plugin.min.js new file mode 100644 index 0000000..220664d --- /dev/null +++ b/static/js/tinymce/plugins/autolink/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e));const l=(void 0,e=>undefined===e);const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]"))return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),w=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),h=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),w);if(!h)return null;let v=h.container;const _=k.backwards(h.container,h.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),w),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(h.container,h.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=4&&b.substr(0,4)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/autoresize/plugin.min.js b/static/js/tinymce/plugins/autoresize/plugin.min.js new file mode 100644 index 0000000..0432684 --- /dev/null +++ b/static/js/tinymce/plugins/autoresize/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),s=o("min_height"),i=o("max_height"),n=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),l=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},g=(e,t,o,s)=>{var i;const n=parseInt(null!==(i=e.getStyle(t,o,s))&&void 0!==i?i:"",10);return isNaN(n)?0:n},a=(e,o,r,c)=>{var d;const f=e.dom,u=e.getDoc();if(!u)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void l(e,!0);const m=u.documentElement,h=c?c():n(e),p=null!==(d=s(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=g(f,m,"margin-top",!0),v=g(f,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+h;C<0&&(C=0);const b=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+b>p&&(y=C+b);const w=i(e);if(w&&y>w?(y=w,l(e,!0)):l(e,!1),y!==o.get()){const s=y-o.get();if(f.setStyle(e.getContainer(),"height",y+"px"),o.set(y),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&s<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(e=>{let t=0;return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{let s,i,l=()=>r(e);e.on("init",(i=>{s=0;const r=n(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,i,l),s+=1})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{if(1===s)i=e.getContainer().offsetHeight,a(e,o,t,l),s+=1;else if(2===s){const t=i0):l,s+=1}else a(e,o,t,l)}))})(e,o)}}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/autosave/plugin.min.js b/static/js/tinymce/plugins/autosave/plugin.min.js new file mode 100644 index 0000000..362325f --- /dev/null +++ b/static/js/tinymce/plugins/autosave/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=("string",t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(r=o=t,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":e;var r,o,a,s})(t));const r=(void 0,t=>undefined===t);var o=tinymce.util.Tools.resolve("tinymce.util.Delay"),a=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),s=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=t=>{const e=/^(\d+)([ms]?)$/.exec(t);return(e&&e[2]?{s:1e3,m:6e4}[e[2]]:1)*parseInt(t,10)},i=t=>e=>e.options.get(t),u=i("autosave_ask_before_unload"),l=i("autosave_restore_when_empty"),c=i("autosave_interval"),d=i("autosave_retention"),m=t=>{const e=document.location;return t.options.get("autosave_prefix").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)},v=(t,e)=>{if(r(e))return t.dom.isEmpty(t.getBody());{const r=s.trim(e);if(""===r)return!0;{const e=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(e)}}},f=t=>{var e;const r=parseInt(null!==(e=a.getItem(m(t)+"time"))&&void 0!==e?e:"0",10)||0;return!((new Date).getTime()-r>d(t)&&(p(t,!1),1))},p=(t,e)=>{const r=m(t);a.removeItem(r+"draft"),a.removeItem(r+"time"),!1!==e&&(t=>{t.dispatch("RemoveDraft")})(t)},g=t=>{const e=m(t);!v(t)&&t.isDirty()&&(a.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),a.setItem(e+"time",(new Date).getTime().toString()),(t=>{t.dispatch("StoreDraft")})(t))},y=t=>{var e;const r=m(t);f(t)&&(t.setContent(null!==(e=a.getItem(r+"draft"))&&void 0!==e?e:"",{format:"raw"}),(t=>{t.dispatch("RestoreDraft")})(t))};var D=tinymce.util.Tools.resolve("tinymce.EditorManager");const h=t=>e=>{e.setEnabled(f(t));const r=()=>e.setEnabled(f(t));return t.on("StoreDraft RestoreDraft RemoveDraft",r),()=>t.off("StoreDraft RestoreDraft RemoveDraft",r)};t.add("autosave",(t=>((t=>{const r=t.options.register,o=t=>{const r=e(t);return r?{value:n(t),valid:r}:{valid:!1,message:"Must be a string."}};r("autosave_ask_before_unload",{processor:"boolean",default:!0}),r("autosave_prefix",{processor:"string",default:"tinymce-autosave-{path}{query}{hash}-{id}-"}),r("autosave_restore_when_empty",{processor:"boolean",default:!1}),r("autosave_interval",{processor:o,default:"30s"}),r("autosave_retention",{processor:o,default:"20m"})})(t),(t=>{t.editorManager.on("BeforeUnload",(t=>{let e;s.each(D.get(),(t=>{t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&u(t)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))})),e&&(t.preventDefault(),t.returnValue=e)}))})(t),(t=>{(t=>{const e=c(t);o.setEditorInterval(t,(()=>{g(t)}),e)})(t);const e=()=>{(t=>{t.undoManager.transact((()=>{y(t),p(t)})),t.focus()})(t)};t.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)}),t.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)})})(t),t.on("init",(()=>{l(t)&&t.dom.isEmpty(t.getBody())&&y(t)})),(t=>({hasDraft:()=>f(t),storeDraft:()=>g(t),restoreDraft:()=>y(t),removeDraft:e=>p(t,e),isEmpty:e=>v(t,e)}))(t))))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/charmap/plugin.min.js b/static/js/tinymce/plugins/charmap/plugin.min.js new file mode 100644 index 0000000..9660ebd --- /dev/null +++ b/static/js/tinymce/plugins/charmap/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=(e,t)=>{const r=((e,t)=>e.dispatch("insertCustomChar",{chr:t}))(e,t).chr;e.execCommand("mceInsertContent",!1,r)},r=e=>t=>e===t,a=("array",e=>"array"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(r=a=e,(n=String).prototype.isPrototypeOf(r)||(null===(i=a.constructor)||void 0===i?void 0:i.name)===n.name)?"string":t;var r,a,n,i})(e));const n=r(null),i=r(void 0),o=e=>"function"==typeof e,s=(!1,()=>false);class l{constructor(e,t){this.tag=e,this.value=t}static some(e){return new l(!0,e)}static none(){return l.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?l.some(e(this.value)):l.none()}bind(e){return this.tag?e(this.value):l.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:l.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?l.none():l.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}l.singletonNone=new l(!1);const c=Array.prototype.push,u=(e,t)=>{const r=e.length,a=new Array(r);for(let n=0;nt=>t.options.get(e),m=h("charmap"),p=h("charmap_append"),d=g.isArray,f="User Defined",y=e=>{return d(e)?(t=e,g.grep(t,(e=>d(e)&&2===e.length))):"function"==typeof e?e():[];var t},b=e=>{const t=((e,t)=>{const r=m(e);r&&(t=[{name:f,characters:y(r)}]);const a=p(e);if(a){const e=g.grep(t,(e=>e.name===f));return e.length?(e[0].characters=[...e[0].characters,...y(a)],t):t.concat({name:f,characters:y(a)})}return t})(e,[{name:"Currency",characters:[[36,"dollar sign"],[162,"cent sign"],[8364,"euro sign"],[163,"pound sign"],[165,"yen sign"],[164,"currency sign"],[8352,"euro-currency sign"],[8353,"colon sign"],[8354,"cruzeiro sign"],[8355,"french franc sign"],[8356,"lira sign"],[8357,"mill sign"],[8358,"naira sign"],[8359,"peseta sign"],[8360,"rupee sign"],[8361,"won sign"],[8362,"new sheqel sign"],[8363,"dong sign"],[8365,"kip sign"],[8366,"tugrik sign"],[8367,"drachma sign"],[8368,"german penny symbol"],[8369,"peso sign"],[8370,"guarani sign"],[8371,"austral sign"],[8372,"hryvnia sign"],[8373,"cedi sign"],[8374,"livre tournois sign"],[8375,"spesmilo sign"],[8376,"tenge sign"],[8377,"indian rupee sign"],[8378,"turkish lira sign"],[8379,"nordic mark sign"],[8380,"manat sign"],[8381,"ruble sign"],[20870,"yen character"],[20803,"yuan character"],[22291,"yuan character, in hong kong and taiwan"],[22278,"yen/yuan character variant one"]]},{name:"Text",characters:[[169,"copyright sign"],[174,"registered sign"],[8482,"trade mark sign"],[8240,"per mille sign"],[181,"micro sign"],[183,"middle dot"],[8226,"bullet"],[8230,"three dot leader"],[8242,"minutes / feet"],[8243,"seconds / inches"],[167,"section sign"],[182,"paragraph sign"],[223,"sharp s / ess-zed"]]},{name:"Quotations",characters:[[8249,"single left-pointing angle quotation mark"],[8250,"single right-pointing angle quotation mark"],[171,"left pointing guillemet"],[187,"right pointing guillemet"],[8216,"left single quotation mark"],[8217,"right single quotation mark"],[8220,"left double quotation mark"],[8221,"right double quotation mark"],[8218,"single low-9 quotation mark"],[8222,"double low-9 quotation mark"],[60,"less-than sign"],[62,"greater-than sign"],[8804,"less-than or equal to"],[8805,"greater-than or equal to"],[8211,"en dash"],[8212,"em dash"],[175,"macron"],[8254,"overline"],[164,"currency sign"],[166,"broken bar"],[168,"diaeresis"],[161,"inverted exclamation mark"],[191,"turned question mark"],[710,"circumflex accent"],[732,"small tilde"],[176,"degree sign"],[8722,"minus sign"],[177,"plus-minus sign"],[247,"division sign"],[8260,"fraction slash"],[215,"multiplication sign"],[185,"superscript one"],[178,"superscript two"],[179,"superscript three"],[188,"fraction one quarter"],[189,"fraction one half"],[190,"fraction three quarters"]]},{name:"Mathematical",characters:[[402,"function / florin"],[8747,"integral"],[8721,"n-ary sumation"],[8734,"infinity"],[8730,"square root"],[8764,"similar to"],[8773,"approximately equal to"],[8776,"almost equal to"],[8800,"not equal to"],[8801,"identical to"],[8712,"element of"],[8713,"not an element of"],[8715,"contains as member"],[8719,"n-ary product"],[8743,"logical and"],[8744,"logical or"],[172,"not sign"],[8745,"intersection"],[8746,"union"],[8706,"partial differential"],[8704,"for all"],[8707,"there exists"],[8709,"diameter"],[8711,"backward difference"],[8727,"asterisk operator"],[8733,"proportional to"],[8736,"angle"]]},{name:"Extended Latin",characters:[[192,"A - grave"],[193,"A - acute"],[194,"A - circumflex"],[195,"A - tilde"],[196,"A - diaeresis"],[197,"A - ring above"],[256,"A - macron"],[198,"ligature AE"],[199,"C - cedilla"],[200,"E - grave"],[201,"E - acute"],[202,"E - circumflex"],[203,"E - diaeresis"],[274,"E - macron"],[204,"I - grave"],[205,"I - acute"],[206,"I - circumflex"],[207,"I - diaeresis"],[298,"I - macron"],[208,"ETH"],[209,"N - tilde"],[210,"O - grave"],[211,"O - acute"],[212,"O - circumflex"],[213,"O - tilde"],[214,"O - diaeresis"],[216,"O - slash"],[332,"O - macron"],[338,"ligature OE"],[352,"S - caron"],[217,"U - grave"],[218,"U - acute"],[219,"U - circumflex"],[220,"U - diaeresis"],[362,"U - macron"],[221,"Y - acute"],[376,"Y - diaeresis"],[562,"Y - macron"],[222,"THORN"],[224,"a - grave"],[225,"a - acute"],[226,"a - circumflex"],[227,"a - tilde"],[228,"a - diaeresis"],[229,"a - ring above"],[257,"a - macron"],[230,"ligature ae"],[231,"c - cedilla"],[232,"e - grave"],[233,"e - acute"],[234,"e - circumflex"],[235,"e - diaeresis"],[275,"e - macron"],[236,"i - grave"],[237,"i - acute"],[238,"i - circumflex"],[239,"i - diaeresis"],[299,"i - macron"],[240,"eth"],[241,"n - tilde"],[242,"o - grave"],[243,"o - acute"],[244,"o - circumflex"],[245,"o - tilde"],[246,"o - diaeresis"],[248,"o slash"],[333,"o macron"],[339,"ligature oe"],[353,"s - caron"],[249,"u - grave"],[250,"u - acute"],[251,"u - circumflex"],[252,"u - diaeresis"],[363,"u - macron"],[253,"y - acute"],[254,"thorn"],[255,"y - diaeresis"],[563,"y - macron"],[913,"Alpha"],[914,"Beta"],[915,"Gamma"],[916,"Delta"],[917,"Epsilon"],[918,"Zeta"],[919,"Eta"],[920,"Theta"],[921,"Iota"],[922,"Kappa"],[923,"Lambda"],[924,"Mu"],[925,"Nu"],[926,"Xi"],[927,"Omicron"],[928,"Pi"],[929,"Rho"],[931,"Sigma"],[932,"Tau"],[933,"Upsilon"],[934,"Phi"],[935,"Chi"],[936,"Psi"],[937,"Omega"],[945,"alpha"],[946,"beta"],[947,"gamma"],[948,"delta"],[949,"epsilon"],[950,"zeta"],[951,"eta"],[952,"theta"],[953,"iota"],[954,"kappa"],[955,"lambda"],[956,"mu"],[957,"nu"],[958,"xi"],[959,"omicron"],[960,"pi"],[961,"rho"],[962,"final sigma"],[963,"sigma"],[964,"tau"],[965,"upsilon"],[966,"phi"],[967,"chi"],[968,"psi"],[969,"omega"]]},{name:"Symbols",characters:[[8501,"alef symbol"],[982,"pi symbol"],[8476,"real part symbol"],[978,"upsilon - hook symbol"],[8472,"Weierstrass p"],[8465,"imaginary part"]]},{name:"Arrows",characters:[[8592,"leftwards arrow"],[8593,"upwards arrow"],[8594,"rightwards arrow"],[8595,"downwards arrow"],[8596,"left right arrow"],[8629,"carriage return"],[8656,"leftwards double arrow"],[8657,"upwards double arrow"],[8658,"rightwards double arrow"],[8659,"downwards double arrow"],[8660,"left right double arrow"],[8756,"therefore"],[8834,"subset of"],[8835,"superset of"],[8836,"not a subset of"],[8838,"subset of or equal to"],[8839,"superset of or equal to"],[8853,"circled plus"],[8855,"circled times"],[8869,"perpendicular"],[8901,"dot operator"],[8968,"left ceiling"],[8969,"right ceiling"],[8970,"left floor"],[8971,"right floor"],[9001,"left-pointing angle bracket"],[9002,"right-pointing angle bracket"],[9674,"lozenge"],[9824,"black spade suit"],[9827,"black club suit"],[9829,"black heart suit"],[9830,"black diamond suit"],[8194,"en space"],[8195,"em space"],[8201,"thin space"],[8204,"zero width non-joiner"],[8205,"zero width joiner"],[8206,"left-to-right mark"],[8207,"right-to-left mark"]]}]);return t.length>1?[{name:"All",characters:(r=t,n=e=>e.characters,(e=>{const t=[];for(let r=0,n=e.length;r{let t=e;return{get:()=>t,set:e=>{t=e}}},v=(e,t,r=0,a)=>{const n=e.indexOf(t,r);return-1!==n&&(!!i(a)||n+t.length<=a)},k=String.fromCodePoint,C=(e,t)=>{const r=[],a=t.toLowerCase();return((e,t)=>{for(let t=0,i=e.length;t!!v(k(e).toLowerCase(),r)||v(t.toLowerCase(),r)||v(t.toLowerCase().replace(/\s+/g,""),r))((n=e[t])[0],n[1],a)&&r.push(n);var n})(e.characters),u(r,(e=>({text:e[1],value:k(e[0]),icon:k(e[0])})))},x="pattern",A=(e,r)=>{const a=()=>[{label:"Search",type:"input",name:x},{type:"collection",name:"results"}],i=1===r.length?w(f):w("All"),o=((e,t)=>{let r=null;const a=()=>{n(r)||(clearTimeout(r),r=null)};return{cancel:a,throttle:(...t)=>{a(),r=setTimeout((()=>{r=null,e.apply(null,t)}),40)}}})((e=>{const t=e.getData().pattern;((e,t)=>{var a,n;(a=r,n=e=>e.name===i.get(),((e,t,r)=>{for(let a=0,n=e.length;a{const a=C(r,t);e.setData({results:a})}))})(e,t)})),c={title:"Special Character",size:"normal",body:1===r.length?{type:"panel",items:a()}:{type:"tabpanel",tabs:u(r,(e=>({title:e.name,name:e.name,items:a()})))},buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:{pattern:"",results:C(r[0],"")},onAction:(r,a)=>{"results"===a.name&&(t(e,a.value),r.close())},onTabChange:(e,t)=>{i.set(t.newTabName),o.throttle(e)},onChange:(e,t)=>{t.name===x&&o.throttle(e)}};e.windowManager.open(c).focus(x)},q=e=>t=>{const r=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",r),r(),()=>{e.off("NodeChange",r)}};e.add("charmap",(e=>{(e=>{const t=e.options.register,r=e=>o(e)||a(e);t("charmap",{processor:r}),t("charmap_append",{processor:r})})(e);const r=b(e);return((e,t)=>{e.addCommand("mceShowCharmap",(()=>{A(e,t)}))})(e,r),(e=>{const t=()=>e.execCommand("mceShowCharmap");e.ui.registry.addButton("charmap",{icon:"insert-character",tooltip:"Special character",onAction:t,onSetup:q(e)}),e.ui.registry.addMenuItem("charmap",{icon:"insert-character",text:"Special character...",onAction:t,onSetup:q(e)})})(e),((e,t)=>{e.ui.registry.addAutocompleter("charmap",{trigger:":",columns:"auto",minChars:2,fetch:(e,r)=>new Promise(((r,a)=>{r(C(t,e))})),onAction:(t,r,a)=>{e.selection.setRng(r),e.insertContent(a),t.hide()}})})(e,r[0]),(e=>({getCharMap:()=>b(e),insertChar:r=>{t(e,r)}}))(e)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/code/plugin.min.js b/static/js/tinymce/plugins/code/plugin.min.js new file mode 100644 index 0000000..644eee5 --- /dev/null +++ b/static/js/tinymce/plugins/code/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/codesample/plugin.min.js b/static/js/tinymce/plugins/codesample/plugin.min.js new file mode 100644 index 0000000..71d3e4f --- /dev/null +++ b/static/js/tinymce/plugins/codesample/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>!(e=>null==e)(e),n=()=>{};class a{constructor(e,t){this.tag=e,this.value=t}static some(e){return new a(!0,e)}static none(){return a.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?a.some(e(this.value)):a.none()}bind(e){return this.tag?e(this.value):a.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:a.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return t(e)?a.some(e):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);var s=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils");const r="undefined"!=typeof window?window:Function("return this;")(),i=function(e,t,n){const a=window.Prism;window.Prism={manual:!0};var s=function(e){var t=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,a={},s={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof r?new r(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=d.reach);x+=_.value.length,_=_.next){var F=_.value;if(t.length>e.length)return;if(!(F instanceof r)){var A,S=1;if(y){if(!(A=i(v,x,e,m))||A.index>=e.length)break;var $=A.index,z=A.index+A[0].length,E=x;for(E+=_.value.length;$>=E;)E+=(_=_.next).value.length;if(x=E-=_.value.length,_.value instanceof r)continue;for(var C=_;C!==t.tail&&(Ed.reach&&(d.reach=O);var P=_.prev;if(B&&(P=u(t,P,B),x+=B.length),c(t,P,S),_=u(t,P,new r(g,f?s.tokenize(j,f):j,w,j)),T&&u(t,_,T),S>1){var N={cause:g+","+b,reach:O};o(e,t,n,_.prev,x,N),d&&N.reach>d.reach&&(d.reach=N.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function u(e,t,n){var a=t.next,s={value:n,prev:t,next:a};return t.next=s,a.prev=s,e.length++,s}function c(e,t,n){for(var a=t.next,s=0;s"+r.content+""},!e.document)return e.addEventListener?(s.disableWorkerMessageHandler||e.addEventListener("message",(function(t){var n=JSON.parse(t.data),a=n.language,r=n.code,i=n.immediateClose;e.postMessage(s.highlight(r,s.languages[a],a)),i&&e.close()}),!1),s):s;var d=s.util.currentScript();function g(){s.manual||s.highlightAll()}if(d&&(s.filename=d.src,d.hasAttribute("data-manual")&&(s.manual=!0)),!s.manual){var p=document.readyState;"loading"===p||"interactive"===p&&d&&d.defer?document.addEventListener("DOMContentLoaded",g):window.requestAnimationFrame?window.requestAnimationFrame(g):window.setTimeout(g,16)}return s}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});return s.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,a,s,r){if(n.language===a){var i=n.tokenStack=[];n.code=n.code.replace(s,(function(e){if("function"==typeof r&&!r(e))return e;for(var s,o=i.length;-1!==n.code.indexOf(s=t(a,o));)++o;return i[o]=e,s})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,a){if(n.language===a&&n.tokenStack){n.grammar=e.languages[a];var s=0,r=Object.keys(n.tokenStack);!function i(o){for(var l=0;l=r.length);l++){var u=o[l];if("string"==typeof u||u.content&&"string"==typeof u.content){var c=r[s],d=n.tokenStack[c],g="string"==typeof u?u:u.content,p=t(a,c),b=g.indexOf(p);if(b>-1){++s;var h=g.substring(0,b),f=new e.Token(a,e.tokenize(d,n.grammar),"language-"+a,d),m=g.substring(b+p.length),y=[];h&&y.push.apply(y,i([h])),y.push(f),m&&y.push.apply(y,i([m])),"string"==typeof u?o.splice.apply(o,[l,1].concat(y)):u.content=y}}else u.content&&i(u.content)}return o}(n.tokens)}}}})}(s),s.languages.c=s.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),s.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),s.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},s.languages.c.string],char:s.languages.c.char,comment:s.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:s.languages.c}}}}),s.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete s.languages.c.boolean,function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(s),function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,(function(e,n){return"(?:"+t[+n]+")"}))}function n(e,n,a){return RegExp(t(e,n),a||"")}function a(e,t){for(var n=0;n>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var s="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",r="class enum interface record struct",i="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",o="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var u=l(r),c=RegExp(l(s+" "+r+" "+i+" "+o)),d=l(r+" "+i+" "+o),g=l(s+" "+r+" "+o),p=a(/<(?:[^<>;=+\-*/%&|^]|<>)*>/.source,2),b=a(/\((?:[^()]|<>)*\)/.source,2),h=/@?\b[A-Za-z_]\w*\b/.source,f=t(/<<0>>(?:\s*<<1>>)?/.source,[h,p]),m=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[d,f]),y=/\[\s*(?:,\s*)*\]/.source,w=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[m,y]),k=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[p,b,y]),v=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[k]),_=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[v,m,y]),x={keyword:c,punctuation:/[<>()?,.:[\]]/},F=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,A=/"(?:\\.|[^\\"\r\n])*"/.source,S=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[S]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[A]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[m]),lookbehind:!0,inside:x},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[h,_]),lookbehind:!0,inside:x},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[h]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[u,f]),lookbehind:!0,inside:x},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[m]),lookbehind:!0,inside:x},{pattern:n(/(\bwhere\s+)<<0>>/.source,[h]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[w]),lookbehind:!0,inside:x},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[_,g,h]),inside:x}],keyword:c,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[h]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[h]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[b]),lookbehind:!0,alias:"class-name",inside:x},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[_,m]),inside:x,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[_]),lookbehind:!0,inside:x,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[h,p]),inside:{function:n(/^<<0>>/.source,[h]),generic:{pattern:RegExp(p),alias:"class-name",inside:x}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[u,f,h,_,c.source,b,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:n(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[f,b]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:c,"class-name":{pattern:RegExp(_),greedy:!0,inside:x},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var $=A+"|"+F,z=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[$]),E=a(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[z]),2),C=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,j=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[m,E]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[C,j]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[C]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[E]),inside:e.languages.csharp},"class-name":{pattern:RegExp(m),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var B=/:[^}\r\n]+/.source,T=a(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[z]),2),O=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[T,B]),P=a(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<>*\)/.source,[$]),2),N=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[P,B]);function R(t,a){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[a,B]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[O]),lookbehind:!0,greedy:!0,inside:R(O,T)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[N]),lookbehind:!0,greedy:!0,inside:R(N,P)}],char:{pattern:RegExp(F),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(s),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(s),function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,a={pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[a,{pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:a.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+n+/[A-Z]\w*\b/.source),lookbehind:!0,inside:a.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":a,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+n+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:a.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+n+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:a.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(s),s.languages.javascript=s.languages.extend("clike",{"class-name":[s.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),s.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,s.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:s.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:s.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:s.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:s.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:s.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),s.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:s.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),s.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),s.languages.markup&&(s.languages.markup.tag.addInlined("script","javascript"),s.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),s.languages.js=s.languages.javascript,s.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},s.languages.markup.tag.inside["attr-value"].inside.entity=s.languages.markup.entity,s.languages.markup.doctype.inside["internal-subset"].inside=s.languages.markup,s.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(s.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:s.languages[t]},n.cdata=/^$/i;var a={"included-cdata":{pattern://i,inside:n}};a["language-"+t]={pattern:/[\s\S]+/,inside:s.languages[t]};var r={};r[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:a},s.languages.insertBefore("markup","cdata",r)}}),Object.defineProperty(s.languages.markup.tag,"addAttribute",{value:function(e,t){s.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:s.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),s.languages.html=s.languages.markup,s.languages.mathml=s.languages.markup,s.languages.svg=s.languages.markup,s.languages.xml=s.languages.extend("markup",{}),s.languages.ssml=s.languages.xml,s.languages.atom=s.languages.xml,s.languages.rss=s.languages.xml,function(e){var t=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],a=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,s=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,r=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:t,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:a,operator:s,punctuation:r};var i={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},o=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:i}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:i}}];e.languages.insertBefore("php","variable",{string:o,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:t,string:o,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:a,operator:s,punctuation:r}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(t){/<\?/.test(t.code)&&e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(s),s.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},s.languages.python["string-interpolation"].inside.interpolation.inside.rest=s.languages.python,s.languages.py=s.languages.python,function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var t={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var n="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",a=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+n+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+a),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+a+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+n),greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+n),greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(s),window.Prism=a,s}(),o=e=>t=>t.options.get(e),l=o("codesample_languages"),u=o("codesample_global_prismjs"),c=e=>r.Prism&&u(e)?r.Prism:i,d=e=>t(e)&&"PRE"===e.nodeName&&-1!==e.className.indexOf("language-"),g=e=>{const t=e.selection?e.selection.getNode():null;return d(t)?a.some(t):a.none()},p=e=>{const t=(e=>l(e)||[{text:"HTML/XML",value:"markup"},{text:"JavaScript",value:"javascript"},{text:"CSS",value:"css"},{text:"PHP",value:"php"},{text:"Ruby",value:"ruby"},{text:"Python",value:"python"},{text:"Java",value:"java"},{text:"C",value:"c"},{text:"C#",value:"csharp"},{text:"C++",value:"cpp"}])(e),n=(r=t,((e,t)=>0""),(e=>e.value));var r;const i=((e,t)=>g(e).fold((()=>t),(e=>{const n=e.className.match(/language-(\w+)/);return n?n[1]:t})))(e,n),o=(e=>g(e).bind((e=>a.from(e.textContent))).getOr(""))(e);e.windowManager.open({title:"Insert/Edit Code Sample",size:"large",body:{type:"panel",items:[{type:"listbox",name:"language",label:"Language",items:t},{type:"textarea",name:"code",label:"Code view"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{language:i,code:o},onSubmit:t=>{const n=t.getData();((e,t,n)=>{const a=e.dom;e.undoManager.transact((()=>{const r=g(e);return n=s.DOM.encode(n),r.fold((()=>{e.insertContent('
      '+n+"
      ");const s=a.select("#__new")[0];a.setAttrib(s,"id",null),e.selection.select(s)}),(s=>{a.setAttrib(s,"class","language-"+t),s.innerHTML=n,c(e).highlightElement(s),e.selection.select(s)}))}))})(e,n.language,n.code),t.close()}})},b=(h=/^\s+|\s+$/g,e=>e.replace(h,""));var h,f=tinymce.util.Tools.resolve("tinymce.util.Tools");const m=(e,t=n)=>n=>{const a=()=>{n.setEnabled(e.selection.isEditable()),t(n)};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("codesample",(e=>{(e=>{const t=e.options.register;t("codesample_languages",{processor:"object[]"}),t("codesample_global_prismjs",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreProcess",(t=>{const n=e.dom,a=n.select("pre[contenteditable=false]",t.node);f.each(f.grep(a,d),(e=>{const t=e.textContent;let a;for(n.setAttrib(e,"class",b(n.getAttrib(e,"class"))),n.setAttrib(e,"contentEditable",null),n.setAttrib(e,"data-mce-highlighted",null);a=e.firstChild;)e.removeChild(a);n.add(e,"code").textContent=t}))})),e.on("SetContent",(()=>{const t=e.dom,n=f.grep(t.select("pre"),(e=>d(e)&&"true"!==t.getAttrib(e,"data-mce-highlighted")));n.length&&e.undoManager.transact((()=>{f.each(n,(n=>{var a;f.each(t.select("br",n),(n=>{t.replace(e.getDoc().createTextNode("\n"),n)})),n.innerHTML=t.encode(null!==(a=n.textContent)&&void 0!==a?a:""),c(e).highlightElement(n),t.setAttrib(n,"data-mce-highlighted",!0),n.className=b(n.className)}))}))})),e.on("PreInit",(()=>{e.parser.addNodeFilter("pre",(e=>{var t;for(let n=0,a=e.length;n{const t=()=>e.execCommand("codesample");e.ui.registry.addToggleButton("codesample",{icon:"code-sample",tooltip:"Insert/edit code sample",onAction:t,onSetup:m(e,(t=>{t.setActive((e=>{const t=e.selection.getStart();return e.dom.is(t,'pre[class*="language-"]')})(e))}))}),e.ui.registry.addMenuItem("codesample",{text:"Code sample...",icon:"code-sample",onAction:t,onSetup:m(e)})})(e),(e=>{e.addCommand("codesample",(()=>{const t=e.selection.getNode();e.selection.isCollapsed()||d(t)?p(e):e.formatter.toggle("code")}))})(e),e.on("dblclick",(t=>{d(t.target)&&p(e)}))}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/directionality/plugin.min.js b/static/js/tinymce/plugins/directionality/plugin.min.js new file mode 100644 index 0000000..d6a2eac --- /dev/null +++ b/static/js/tinymce/plugins/directionality/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(9),y=m(11),p=(t,e)=>{t.dom.removeAttribute(e)},w=i(Element.prototype.attachShadow)&&i(Node.prototype.getRootNode)?t=>d(t.dom.getRootNode()):t=>v(t)?t:d(t.dom.ownerDocument),b=t=>d(t.dom.host),N=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=w(t);return y(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=N,i=b,t=>r(i(t))));var r,i},S=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||N(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",A=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r{const o=t.length,r=new Array(o);for(let n=0;nh(t,e))))(t),E=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const T=(t,e,n)=>{u(e,(e=>{const c=d(e),m=E(c),f=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(c,m);var v;(v=f,(t=>a.from(t.dom.parentNode).map(d))(v).filter(g)).each((e=>{if(t.setStyle(f.dom,"direction",null),S(e)===n?p(f,"dir"):((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(f,"dir",n),S(f)!==n&&t.setStyle(f.dom,"direction",n),m){const e=A(f,"li[dir],li[style]");u(e,(e=>{p(e,"dir"),t.setStyle(e.dom,"direction",null)}))}}))}))},C=(t,e)=>{t.selection.isEditable()&&(T(t.dom,t.selection.getSelectedBlocks(),e),t.nodeChanged())},D=(t,e)=>o=>{const r=r=>{const n=d(r.element);o.setActive(S(n)===e),o.setEnabled(t.selection.isEditable())};return t.on("NodeChange",r),o.setEnabled(t.selection.isEditable()),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{C(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{C(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:D(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:D(t,"rtl")})})(t)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/emoticons/js/emojiimages.js b/static/js/tinymce/plugins/emoticons/js/emojiimages.js new file mode 100644 index 0000000..6fcec71 --- /dev/null +++ b/static/js/tinymce/plugins/emoticons/js/emojiimages.js @@ -0,0 +1 @@ +window.tinymce.Resource.add("tinymce.plugins.emoticons",{100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:'💯',fitzpatrick_scale:false,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:'🔢',fitzpatrick_scale:false,category:"symbols"},grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:'😀',fitzpatrick_scale:false,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:'😬',fitzpatrick_scale:false,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:'ðŸ˜',fitzpatrick_scale:false,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:'😂',fitzpatrick_scale:false,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:'🤣',fitzpatrick_scale:false,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:'🥳',fitzpatrick_scale:false,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:'😃',fitzpatrick_scale:false,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:'😄',fitzpatrick_scale:false,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:'😅',fitzpatrick_scale:false,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:'😆',fitzpatrick_scale:false,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:'😇',fitzpatrick_scale:false,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:'😉',fitzpatrick_scale:false,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:'😊',fitzpatrick_scale:false,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:'🙂',fitzpatrick_scale:false,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:'🙃',fitzpatrick_scale:false,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:'☺ï¸',fitzpatrick_scale:false,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:'😋',fitzpatrick_scale:false,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:'😌',fitzpatrick_scale:false,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:'ðŸ˜',fitzpatrick_scale:false,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:'🥰',fitzpatrick_scale:false,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'😘',fitzpatrick_scale:false,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:'😗',fitzpatrick_scale:false,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:'😙',fitzpatrick_scale:false,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'😚',fitzpatrick_scale:false,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:'😜',fitzpatrick_scale:false,category:"people"},zany:{keywords:["face","goofy","crazy"],char:'🤪',fitzpatrick_scale:false,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:'🤨',fitzpatrick_scale:false,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:'ðŸ§',fitzpatrick_scale:false,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:'ðŸ˜',fitzpatrick_scale:false,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:'😛',fitzpatrick_scale:false,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:'🤑',fitzpatrick_scale:false,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:'🤓',fitzpatrick_scale:false,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:'😎',fitzpatrick_scale:false,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:'🤩',fitzpatrick_scale:false,category:"people"},clown_face:{keywords:["face"],char:'🤡',fitzpatrick_scale:false,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:'🤠',fitzpatrick_scale:false,category:"people"},hugs:{keywords:["face","smile","hug"],char:'🤗',fitzpatrick_scale:false,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:'ðŸ˜',fitzpatrick_scale:false,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:'😶',fitzpatrick_scale:false,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:'ðŸ˜',fitzpatrick_scale:false,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:'😑',fitzpatrick_scale:false,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:'😒',fitzpatrick_scale:false,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:'🙄',fitzpatrick_scale:false,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:'🤔',fitzpatrick_scale:false,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:'🤥',fitzpatrick_scale:false,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:'🤭',fitzpatrick_scale:false,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:'🤫',fitzpatrick_scale:false,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:'🤬',fitzpatrick_scale:false,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:'🤯',fitzpatrick_scale:false,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:'😳',fitzpatrick_scale:false,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:'😞',fitzpatrick_scale:false,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:'😟',fitzpatrick_scale:false,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:'😠',fitzpatrick_scale:false,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:'😡',fitzpatrick_scale:false,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:'😔',fitzpatrick_scale:false,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:'😕',fitzpatrick_scale:false,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:'ðŸ™',fitzpatrick_scale:false,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:'☹',fitzpatrick_scale:false,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:'😣',fitzpatrick_scale:false,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:'😖',fitzpatrick_scale:false,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:'😫',fitzpatrick_scale:false,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:'😩',fitzpatrick_scale:false,category:"people"},pleading:{keywords:["face","begging","mercy"],char:'🥺',fitzpatrick_scale:false,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:'😤',fitzpatrick_scale:false,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:'😮',fitzpatrick_scale:false,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:'😱',fitzpatrick_scale:false,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:'😨',fitzpatrick_scale:false,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:'😰',fitzpatrick_scale:false,category:"people"},hushed:{keywords:["face","woo","shh"],char:'😯',fitzpatrick_scale:false,category:"people"},frowning:{keywords:["face","aw","what"],char:'😦',fitzpatrick_scale:false,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:'😧',fitzpatrick_scale:false,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:'😢',fitzpatrick_scale:false,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:'😥',fitzpatrick_scale:false,category:"people"},drooling_face:{keywords:["face"],char:'🤤',fitzpatrick_scale:false,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:'😪',fitzpatrick_scale:false,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:'😓',fitzpatrick_scale:false,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:'🥵',fitzpatrick_scale:false,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:'🥶',fitzpatrick_scale:false,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:'😭',fitzpatrick_scale:false,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:'😵',fitzpatrick_scale:false,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:'😲',fitzpatrick_scale:false,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:'ðŸ¤',fitzpatrick_scale:false,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:'🤢',fitzpatrick_scale:false,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:'🤧',fitzpatrick_scale:false,category:"people"},vomiting:{keywords:["face","sick"],char:'🤮',fitzpatrick_scale:false,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:'😷',fitzpatrick_scale:false,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:'🤒',fitzpatrick_scale:false,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:'🤕',fitzpatrick_scale:false,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:'🥴',fitzpatrick_scale:false,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:'😴',fitzpatrick_scale:false,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:'💤',fitzpatrick_scale:false,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:'💩',fitzpatrick_scale:false,category:"people"},smiling_imp:{keywords:["devil","horns"],char:'😈',fitzpatrick_scale:false,category:"people"},imp:{keywords:["devil","angry","horns"],char:'👿',fitzpatrick_scale:false,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:'👹',fitzpatrick_scale:false,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:'👺',fitzpatrick_scale:false,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:'💀',fitzpatrick_scale:false,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:'👻',fitzpatrick_scale:false,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:'👽',fitzpatrick_scale:false,category:"people"},robot:{keywords:["computer","machine","bot"],char:'🤖',fitzpatrick_scale:false,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:'😺',fitzpatrick_scale:false,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:'😸',fitzpatrick_scale:false,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:'😹',fitzpatrick_scale:false,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:'😻',fitzpatrick_scale:false,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:'😼',fitzpatrick_scale:false,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:'😽',fitzpatrick_scale:false,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:'🙀',fitzpatrick_scale:false,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:'😿',fitzpatrick_scale:false,category:"people"},pouting_cat:{keywords:["animal","cats"],char:'😾',fitzpatrick_scale:false,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:'🤲',fitzpatrick_scale:true,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:'🙌',fitzpatrick_scale:true,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:'ðŸ‘',fitzpatrick_scale:true,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:'👋',fitzpatrick_scale:true,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:'🤙',fitzpatrick_scale:true,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:'ðŸ‘',fitzpatrick_scale:true,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:'👎',fitzpatrick_scale:true,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:'👊',fitzpatrick_scale:true,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:'✊',fitzpatrick_scale:true,category:"people"},fist_left:{keywords:["hand","fistbump"],char:'🤛',fitzpatrick_scale:true,category:"people"},fist_right:{keywords:["hand","fistbump"],char:'🤜',fitzpatrick_scale:true,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:'✌',fitzpatrick_scale:true,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:'👌',fitzpatrick_scale:true,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:'✋',fitzpatrick_scale:true,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:'🤚',fitzpatrick_scale:true,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:'ðŸ‘',fitzpatrick_scale:true,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:'💪',fitzpatrick_scale:true,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:'ðŸ™',fitzpatrick_scale:true,category:"people"},foot:{keywords:["kick","stomp"],char:'🦶',fitzpatrick_scale:true,category:"people"},leg:{keywords:["kick","limb"],char:'🦵',fitzpatrick_scale:true,category:"people"},handshake:{keywords:["agreement","shake"],char:'ðŸ¤',fitzpatrick_scale:false,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:'â˜',fitzpatrick_scale:true,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:'👆',fitzpatrick_scale:true,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:'👇',fitzpatrick_scale:true,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:'👈',fitzpatrick_scale:true,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:'👉',fitzpatrick_scale:true,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:'🖕',fitzpatrick_scale:true,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:'ðŸ–',fitzpatrick_scale:true,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:'🤟',fitzpatrick_scale:true,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:'🤘',fitzpatrick_scale:true,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:'🤞',fitzpatrick_scale:true,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:'🖖',fitzpatrick_scale:true,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:'âœ',fitzpatrick_scale:true,category:"people"},selfie:{keywords:["camera","phone"],char:'🤳',fitzpatrick_scale:true,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:'💅',fitzpatrick_scale:true,category:"people"},lips:{keywords:["mouth","kiss"],char:'👄',fitzpatrick_scale:false,category:"people"},tooth:{keywords:["teeth","dentist"],char:'🦷',fitzpatrick_scale:false,category:"people"},tongue:{keywords:["mouth","playful"],char:'👅',fitzpatrick_scale:false,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:'👂',fitzpatrick_scale:true,category:"people"},nose:{keywords:["smell","sniff"],char:'👃',fitzpatrick_scale:true,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:'ðŸ‘',fitzpatrick_scale:false,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:'👀',fitzpatrick_scale:false,category:"people"},brain:{keywords:["smart","intelligent"],char:'🧠',fitzpatrick_scale:false,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:'👤',fitzpatrick_scale:false,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:'👥',fitzpatrick_scale:false,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:'🗣',fitzpatrick_scale:false,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:'👶',fitzpatrick_scale:true,category:"people"},child:{keywords:["gender-neutral","young"],char:'🧒',fitzpatrick_scale:true,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:'👦',fitzpatrick_scale:true,category:"people"},girl:{keywords:["female","woman","teenager"],char:'👧',fitzpatrick_scale:true,category:"people"},adult:{keywords:["gender-neutral","person"],char:'🧑',fitzpatrick_scale:true,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:'👨',fitzpatrick_scale:true,category:"people"},woman:{keywords:["female","girls","lady"],char:'👩',fitzpatrick_scale:true,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:'👱â€â™€ï¸',fitzpatrick_scale:true,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:'👱',fitzpatrick_scale:true,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:'🧔',fitzpatrick_scale:true,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:'🧓',fitzpatrick_scale:true,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:'👴',fitzpatrick_scale:true,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:'👵',fitzpatrick_scale:true,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:'👲',fitzpatrick_scale:true,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:'🧕',fitzpatrick_scale:true,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:'👳â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:'👳',fitzpatrick_scale:true,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:'👮â€â™€ï¸',fitzpatrick_scale:true,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:'👮',fitzpatrick_scale:true,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:'👷â€â™€ï¸',fitzpatrick_scale:true,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:'👷',fitzpatrick_scale:true,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:'💂â€â™€ï¸',fitzpatrick_scale:true,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:'💂',fitzpatrick_scale:true,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:'🕵ï¸â€â™€ï¸',fitzpatrick_scale:true,category:"people"},male_detective:{keywords:["human","spy","detective"],char:'🕵',fitzpatrick_scale:true,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:'👩â€âš•ï¸',fitzpatrick_scale:true,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:'👨â€âš•ï¸',fitzpatrick_scale:true,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:'👩â€ðŸŒ¾',fitzpatrick_scale:true,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:'👨â€ðŸŒ¾',fitzpatrick_scale:true,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:'👩â€ðŸ³',fitzpatrick_scale:true,category:"people"},man_cook:{keywords:["chef","man","human"],char:'👨â€ðŸ³',fitzpatrick_scale:true,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:'👩â€ðŸŽ“',fitzpatrick_scale:true,category:"people"},man_student:{keywords:["graduate","man","human"],char:'👨â€ðŸŽ“',fitzpatrick_scale:true,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:'👩â€ðŸŽ¤',fitzpatrick_scale:true,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:'👨â€ðŸŽ¤',fitzpatrick_scale:true,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:'👩â€ðŸ«',fitzpatrick_scale:true,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:'👨â€ðŸ«',fitzpatrick_scale:true,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:'👩â€ðŸ­',fitzpatrick_scale:true,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:'👨â€ðŸ­',fitzpatrick_scale:true,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:'👩â€ðŸ’»',fitzpatrick_scale:true,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:'👨â€ðŸ’»',fitzpatrick_scale:true,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:'👩â€ðŸ’¼',fitzpatrick_scale:true,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:'👨â€ðŸ’¼',fitzpatrick_scale:true,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:'👩â€ðŸ”§',fitzpatrick_scale:true,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:'👨â€ðŸ”§',fitzpatrick_scale:true,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:'👩â€ðŸ”¬',fitzpatrick_scale:true,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:'👨â€ðŸ”¬',fitzpatrick_scale:true,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:'👩â€ðŸŽ¨',fitzpatrick_scale:true,category:"people"},man_artist:{keywords:["painter","man","human"],char:'👨â€ðŸŽ¨',fitzpatrick_scale:true,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:'👩â€ðŸš’',fitzpatrick_scale:true,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:'👨â€ðŸš’',fitzpatrick_scale:true,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:'👩â€âœˆï¸',fitzpatrick_scale:true,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:'👨â€âœˆï¸',fitzpatrick_scale:true,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:'👩â€ðŸš€',fitzpatrick_scale:true,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:'👨â€ðŸš€',fitzpatrick_scale:true,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:'👩â€âš–ï¸',fitzpatrick_scale:true,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:'👨â€âš–ï¸',fitzpatrick_scale:true,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:'🦸â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:'🦸â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:'🦹â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:'🦹â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:'🤶',fitzpatrick_scale:true,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:'🎅',fitzpatrick_scale:true,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:'🧙â€â™€ï¸',fitzpatrick_scale:true,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:'🧙â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_elf:{keywords:["woman","female"],char:'ðŸ§â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_elf:{keywords:["man","male"],char:'ðŸ§â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_vampire:{keywords:["woman","female"],char:'🧛â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:'🧛â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:'🧟â€â™€ï¸',fitzpatrick_scale:false,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:'🧟â€â™‚ï¸',fitzpatrick_scale:false,category:"people"},woman_genie:{keywords:["woman","female"],char:'🧞â€â™€ï¸',fitzpatrick_scale:false,category:"people"},man_genie:{keywords:["man","male"],char:'🧞â€â™‚ï¸',fitzpatrick_scale:false,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:'🧜â€â™€ï¸',fitzpatrick_scale:true,category:"people"},merman:{keywords:["man","male","triton"],char:'🧜â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_fairy:{keywords:["woman","female"],char:'🧚â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_fairy:{keywords:["man","male"],char:'🧚â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},angel:{keywords:["heaven","wings","halo"],char:'👼',fitzpatrick_scale:true,category:"people"},pregnant_woman:{keywords:["baby"],char:'🤰',fitzpatrick_scale:true,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:'🤱',fitzpatrick_scale:true,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:'👸',fitzpatrick_scale:true,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:'🤴',fitzpatrick_scale:true,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:'👰',fitzpatrick_scale:true,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:'🤵',fitzpatrick_scale:true,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:'ðŸƒâ€â™€ï¸',fitzpatrick_scale:true,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:'ðŸƒ',fitzpatrick_scale:true,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:'🚶â€â™€ï¸',fitzpatrick_scale:true,category:"people"},walking_man:{keywords:["human","feet","steps"],char:'🚶',fitzpatrick_scale:true,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:'💃',fitzpatrick_scale:true,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:'🕺',fitzpatrick_scale:true,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:'👯',fitzpatrick_scale:false,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:'👯â€â™‚ï¸',fitzpatrick_scale:false,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:'👫',fitzpatrick_scale:false,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:'👬',fitzpatrick_scale:false,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:'👭',fitzpatrick_scale:false,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:'🙇â€â™€ï¸',fitzpatrick_scale:true,category:"people"},bowing_man:{keywords:["man","male","boy"],char:'🙇',fitzpatrick_scale:true,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:'🤦â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:'🤦â€â™€ï¸',fitzpatrick_scale:true,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:'🤷',fitzpatrick_scale:true,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:'🤷â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:'ðŸ’',fitzpatrick_scale:true,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:'ðŸ’â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:'🙅',fitzpatrick_scale:true,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:'🙅â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:'🙆',fitzpatrick_scale:true,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:'🙆â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:'🙋',fitzpatrick_scale:true,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:'🙋â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:'🙎',fitzpatrick_scale:true,category:"people"},pouting_man:{keywords:["male","boy","man"],char:'🙎â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:'ðŸ™',fitzpatrick_scale:true,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:'ðŸ™â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:'💇',fitzpatrick_scale:true,category:"people"},haircut_man:{keywords:["male","boy","man"],char:'💇â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:'💆',fitzpatrick_scale:true,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:'💆â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:'🧖â€â™€ï¸',fitzpatrick_scale:true,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:'🧖â€â™‚ï¸',fitzpatrick_scale:true,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'💑',fitzpatrick_scale:false,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'👩â€â¤ï¸â€ðŸ‘©',fitzpatrick_scale:false,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'👨â€â¤ï¸â€ðŸ‘¨',fitzpatrick_scale:false,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'ðŸ’',fitzpatrick_scale:false,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©',fitzpatrick_scale:false,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:'👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨',fitzpatrick_scale:false,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:'👪',fitzpatrick_scale:false,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:'👨â€ðŸ‘©â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:'👩â€ðŸ‘©â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:'👩â€ðŸ‘©â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘¨â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘¨â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:'👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:'👩â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:'👩â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:'👩â€ðŸ‘§â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:'👩â€ðŸ‘¦â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:'👩â€ðŸ‘§â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:'👨â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:'👨â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:'👨â€ðŸ‘§â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:'👨â€ðŸ‘¦â€ðŸ‘¦',fitzpatrick_scale:false,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:'👨â€ðŸ‘§â€ðŸ‘§',fitzpatrick_scale:false,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:'🧶',fitzpatrick_scale:false,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:'🧵',fitzpatrick_scale:false,category:"people"},coat:{keywords:["jacket"],char:'🧥',fitzpatrick_scale:false,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:'🥼',fitzpatrick_scale:false,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:'👚',fitzpatrick_scale:false,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:'👕',fitzpatrick_scale:false,category:"people"},jeans:{keywords:["fashion","shopping"],char:'👖',fitzpatrick_scale:false,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:'👔',fitzpatrick_scale:false,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:'👗',fitzpatrick_scale:false,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:'👙',fitzpatrick_scale:false,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:'👘',fitzpatrick_scale:false,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:'💄',fitzpatrick_scale:false,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:'💋',fitzpatrick_scale:false,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:'👣',fitzpatrick_scale:false,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:'🥿',fitzpatrick_scale:false,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:'👠',fitzpatrick_scale:false,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:'👡',fitzpatrick_scale:false,category:"people"},boot:{keywords:["shoes","fashion"],char:'👢',fitzpatrick_scale:false,category:"people"},mans_shoe:{keywords:["fashion","male"],char:'👞',fitzpatrick_scale:false,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:'👟',fitzpatrick_scale:false,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:'🥾',fitzpatrick_scale:false,category:"people"},socks:{keywords:["stockings","clothes"],char:'🧦',fitzpatrick_scale:false,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:'🧤',fitzpatrick_scale:false,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:'🧣',fitzpatrick_scale:false,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:'👒',fitzpatrick_scale:false,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:'🎩',fitzpatrick_scale:false,category:"people"},billed_hat:{keywords:["cap","baseball"],char:'🧢',fitzpatrick_scale:false,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:'⛑',fitzpatrick_scale:false,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:'🎓',fitzpatrick_scale:false,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:'👑',fitzpatrick_scale:false,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:'🎒',fitzpatrick_scale:false,category:"people"},luggage:{keywords:["packing","travel"],char:'🧳',fitzpatrick_scale:false,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:'ðŸ‘',fitzpatrick_scale:false,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:'👛',fitzpatrick_scale:false,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:'👜',fitzpatrick_scale:false,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:'💼',fitzpatrick_scale:false,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:'👓',fitzpatrick_scale:false,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:'🕶',fitzpatrick_scale:false,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:'🥽',fitzpatrick_scale:false,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:'ðŸ’',fitzpatrick_scale:false,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:'🌂',fitzpatrick_scale:false,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:'ðŸ¶',fitzpatrick_scale:false,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:'ðŸ±',fitzpatrick_scale:false,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:'ðŸ­',fitzpatrick_scale:false,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:'ðŸ¹',fitzpatrick_scale:false,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:'ðŸ°',fitzpatrick_scale:false,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:'🦊',fitzpatrick_scale:false,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:'ðŸ»',fitzpatrick_scale:false,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:'ðŸ¼',fitzpatrick_scale:false,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:'ðŸ¨',fitzpatrick_scale:false,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:'ðŸ¯',fitzpatrick_scale:false,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:'ðŸ¦',fitzpatrick_scale:false,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:'ðŸ®',fitzpatrick_scale:false,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:'ðŸ·',fitzpatrick_scale:false,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:'ðŸ½',fitzpatrick_scale:false,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:'ðŸ¸',fitzpatrick_scale:false,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:'🦑',fitzpatrick_scale:false,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:'ðŸ™',fitzpatrick_scale:false,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:'ðŸ¦',fitzpatrick_scale:false,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:'ðŸµ',fitzpatrick_scale:false,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:'ðŸ¦',fitzpatrick_scale:false,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:'🙈',fitzpatrick_scale:false,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:'🙉',fitzpatrick_scale:false,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:'🙊',fitzpatrick_scale:false,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:'ðŸ’',fitzpatrick_scale:false,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:'ðŸ”',fitzpatrick_scale:false,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:'ðŸ§',fitzpatrick_scale:false,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:'ðŸ¦',fitzpatrick_scale:false,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:'ðŸ¤',fitzpatrick_scale:false,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:'ðŸ£',fitzpatrick_scale:false,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:'ðŸ¥',fitzpatrick_scale:false,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:'🦆',fitzpatrick_scale:false,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:'🦅',fitzpatrick_scale:false,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:'🦉',fitzpatrick_scale:false,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:'🦇',fitzpatrick_scale:false,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:'ðŸº',fitzpatrick_scale:false,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:'ðŸ—',fitzpatrick_scale:false,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:'ðŸ´',fitzpatrick_scale:false,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:'🦄',fitzpatrick_scale:false,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:'ðŸ',fitzpatrick_scale:false,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:'ðŸ›',fitzpatrick_scale:false,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:'🦋',fitzpatrick_scale:false,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:'ðŸŒ',fitzpatrick_scale:false,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:'ðŸž',fitzpatrick_scale:false,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:'ðŸœ',fitzpatrick_scale:false,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:'🦗',fitzpatrick_scale:false,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:'🕷',fitzpatrick_scale:false,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:'🦂',fitzpatrick_scale:false,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:'🦀',fitzpatrick_scale:false,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:'ðŸ',fitzpatrick_scale:false,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:'🦎',fitzpatrick_scale:false,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:'🦖',fitzpatrick_scale:false,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:'🦕',fitzpatrick_scale:false,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:'ðŸ¢',fitzpatrick_scale:false,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:'ðŸ ',fitzpatrick_scale:false,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:'ðŸŸ',fitzpatrick_scale:false,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:'ðŸ¡',fitzpatrick_scale:false,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:'ðŸ¬',fitzpatrick_scale:false,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:'🦈',fitzpatrick_scale:false,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:'ðŸ³',fitzpatrick_scale:false,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:'ðŸ‹',fitzpatrick_scale:false,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:'ðŸŠ',fitzpatrick_scale:false,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:'ðŸ†',fitzpatrick_scale:false,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:'🦓',fitzpatrick_scale:false,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:'ðŸ…',fitzpatrick_scale:false,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:'ðŸƒ',fitzpatrick_scale:false,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:'ðŸ‚',fitzpatrick_scale:false,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:'ðŸ„',fitzpatrick_scale:false,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:'🦌',fitzpatrick_scale:false,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:'ðŸª',fitzpatrick_scale:false,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:'ðŸ«',fitzpatrick_scale:false,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:'🦒',fitzpatrick_scale:false,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:'ðŸ˜',fitzpatrick_scale:false,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:'ðŸ¦',fitzpatrick_scale:false,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:'ðŸ',fitzpatrick_scale:false,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:'ðŸ',fitzpatrick_scale:false,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:'ðŸ‘',fitzpatrick_scale:false,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:'ðŸŽ',fitzpatrick_scale:false,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:'ðŸ–',fitzpatrick_scale:false,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:'ðŸ€',fitzpatrick_scale:false,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:'ðŸ',fitzpatrick_scale:false,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:'ðŸ“',fitzpatrick_scale:false,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:'🦃',fitzpatrick_scale:false,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:'🕊',fitzpatrick_scale:false,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:'ðŸ•',fitzpatrick_scale:false,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:'ðŸ©',fitzpatrick_scale:false,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:'ðŸˆ',fitzpatrick_scale:false,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:'ðŸ‡',fitzpatrick_scale:false,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:'ðŸ¿',fitzpatrick_scale:false,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:'🦔',fitzpatrick_scale:false,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:'ðŸ¦',fitzpatrick_scale:false,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:'🦙',fitzpatrick_scale:false,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:'🦛',fitzpatrick_scale:false,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:'🦘',fitzpatrick_scale:false,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:'🦡',fitzpatrick_scale:false,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:'🦢',fitzpatrick_scale:false,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:'🦚',fitzpatrick_scale:false,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:'🦜',fitzpatrick_scale:false,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:'🦞',fitzpatrick_scale:false,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:'🦟',fitzpatrick_scale:false,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:'ðŸ¾',fitzpatrick_scale:false,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:'ðŸ‰',fitzpatrick_scale:false,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:'ðŸ²',fitzpatrick_scale:false,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:'🌵',fitzpatrick_scale:false,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:'🎄',fitzpatrick_scale:false,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:'🌲',fitzpatrick_scale:false,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:'🌳',fitzpatrick_scale:false,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:'🌴',fitzpatrick_scale:false,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:'🌱',fitzpatrick_scale:false,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:'🌿',fitzpatrick_scale:false,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:'☘',fitzpatrick_scale:false,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:'ðŸ€',fitzpatrick_scale:false,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:'ðŸŽ',fitzpatrick_scale:false,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:'🎋',fitzpatrick_scale:false,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:'ðŸƒ',fitzpatrick_scale:false,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:'ðŸ‚',fitzpatrick_scale:false,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:'ðŸ',fitzpatrick_scale:false,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:'🌾',fitzpatrick_scale:false,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:'🌺',fitzpatrick_scale:false,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:'🌻',fitzpatrick_scale:false,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:'🌹',fitzpatrick_scale:false,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:'🥀',fitzpatrick_scale:false,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:'🌷',fitzpatrick_scale:false,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:'🌼',fitzpatrick_scale:false,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:'🌸',fitzpatrick_scale:false,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:'ðŸ’',fitzpatrick_scale:false,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:'ðŸ„',fitzpatrick_scale:false,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:'🌰',fitzpatrick_scale:false,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:'🎃',fitzpatrick_scale:false,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:'ðŸš',fitzpatrick_scale:false,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:'🕸',fitzpatrick_scale:false,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:'🌎',fitzpatrick_scale:false,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:'ðŸŒ',fitzpatrick_scale:false,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:'ðŸŒ',fitzpatrick_scale:false,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:'🌕',fitzpatrick_scale:false,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:'🌖',fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌗',fitzpatrick_scale:false,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌘',fitzpatrick_scale:false,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌑',fitzpatrick_scale:false,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌒',fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌓',fitzpatrick_scale:false,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:'🌔',fitzpatrick_scale:false,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌚',fitzpatrick_scale:false,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'ðŸŒ',fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌛',fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌜',fitzpatrick_scale:false,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:'🌞',fitzpatrick_scale:false,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:'🌙',fitzpatrick_scale:false,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:'â­',fitzpatrick_scale:false,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:'🌟',fitzpatrick_scale:false,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:'💫',fitzpatrick_scale:false,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:'✨',fitzpatrick_scale:false,category:"animals_and_nature"},comet:{keywords:["space"],char:'☄',fitzpatrick_scale:false,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:'☀ï¸',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:'🌤',fitzpatrick_scale:false,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:'â›…',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:'🌥',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:'🌦',fitzpatrick_scale:false,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:'â˜ï¸',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:'🌧',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:'⛈',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:'🌩',fitzpatrick_scale:false,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:'âš¡',fitzpatrick_scale:false,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:'🔥',fitzpatrick_scale:false,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:'💥',fitzpatrick_scale:false,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:'â„ï¸',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:'🌨',fitzpatrick_scale:false,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:'⛄',fitzpatrick_scale:false,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:'☃',fitzpatrick_scale:false,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:'🌬',fitzpatrick_scale:false,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:'💨',fitzpatrick_scale:false,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:'🌪',fitzpatrick_scale:false,category:"animals_and_nature"},fog:{keywords:["weather"],char:'🌫',fitzpatrick_scale:false,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:'☂',fitzpatrick_scale:false,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:'☔',fitzpatrick_scale:false,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:'💧',fitzpatrick_scale:false,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:'💦',fitzpatrick_scale:false,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:'🌊',fitzpatrick_scale:false,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:'ðŸ',fitzpatrick_scale:false,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:'ðŸŽ',fitzpatrick_scale:false,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:'ðŸ',fitzpatrick_scale:false,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:'ðŸŠ',fitzpatrick_scale:false,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:'ðŸ‹',fitzpatrick_scale:false,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:'ðŸŒ',fitzpatrick_scale:false,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:'ðŸ‰',fitzpatrick_scale:false,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:'ðŸ‡',fitzpatrick_scale:false,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:'ðŸ“',fitzpatrick_scale:false,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:'ðŸˆ',fitzpatrick_scale:false,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:'ðŸ’',fitzpatrick_scale:false,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:'ðŸ‘',fitzpatrick_scale:false,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:'ðŸ',fitzpatrick_scale:false,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:'🥥',fitzpatrick_scale:false,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:'ðŸ¥',fitzpatrick_scale:false,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:'🥭',fitzpatrick_scale:false,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:'🥑',fitzpatrick_scale:false,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:'🥦',fitzpatrick_scale:false,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:'ðŸ…',fitzpatrick_scale:false,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:'ðŸ†',fitzpatrick_scale:false,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:'🥒',fitzpatrick_scale:false,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:'🥕',fitzpatrick_scale:false,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:'🌶',fitzpatrick_scale:false,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:'🥔',fitzpatrick_scale:false,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:'🌽',fitzpatrick_scale:false,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:'🥬',fitzpatrick_scale:false,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:'ðŸ ',fitzpatrick_scale:false,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:'🥜',fitzpatrick_scale:false,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:'ðŸ¯',fitzpatrick_scale:false,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:'ðŸ¥',fitzpatrick_scale:false,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:'ðŸž',fitzpatrick_scale:false,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:'🥖',fitzpatrick_scale:false,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:'🥯',fitzpatrick_scale:false,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:'🥨',fitzpatrick_scale:false,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:'🧀',fitzpatrick_scale:false,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:'🥚',fitzpatrick_scale:false,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:'🥓',fitzpatrick_scale:false,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:'🥩',fitzpatrick_scale:false,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:'🥞',fitzpatrick_scale:false,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:'ðŸ—',fitzpatrick_scale:false,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:'ðŸ–',fitzpatrick_scale:false,category:"food_and_drink"},bone:{keywords:["skeleton"],char:'🦴',fitzpatrick_scale:false,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:'ðŸ¤',fitzpatrick_scale:false,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:'ðŸ³',fitzpatrick_scale:false,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:'ðŸ”',fitzpatrick_scale:false,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:'ðŸŸ',fitzpatrick_scale:false,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:'🥙',fitzpatrick_scale:false,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:'🌭',fitzpatrick_scale:false,category:"food_and_drink"},pizza:{keywords:["food","party"],char:'ðŸ•',fitzpatrick_scale:false,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:'🥪',fitzpatrick_scale:false,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:'🥫',fitzpatrick_scale:false,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:'ðŸ',fitzpatrick_scale:false,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:'🌮',fitzpatrick_scale:false,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:'🌯',fitzpatrick_scale:false,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:'🥗',fitzpatrick_scale:false,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:'🥘',fitzpatrick_scale:false,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:'ðŸœ',fitzpatrick_scale:false,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:'ðŸ²',fitzpatrick_scale:false,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:'ðŸ¥',fitzpatrick_scale:false,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:'🥠',fitzpatrick_scale:false,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:'ðŸ£',fitzpatrick_scale:false,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:'ðŸ±',fitzpatrick_scale:false,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:'ðŸ›',fitzpatrick_scale:false,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:'ðŸ™',fitzpatrick_scale:false,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:'ðŸš',fitzpatrick_scale:false,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:'ðŸ˜',fitzpatrick_scale:false,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:'ðŸ¢',fitzpatrick_scale:false,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:'ðŸ¡',fitzpatrick_scale:false,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:'ðŸ§',fitzpatrick_scale:false,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:'ðŸ¨',fitzpatrick_scale:false,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:'ðŸ¦',fitzpatrick_scale:false,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:'🥧',fitzpatrick_scale:false,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:'ðŸ°',fitzpatrick_scale:false,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:'ðŸ§',fitzpatrick_scale:false,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:'🥮',fitzpatrick_scale:false,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:'🎂',fitzpatrick_scale:false,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:'ðŸ®',fitzpatrick_scale:false,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:'ðŸ¬',fitzpatrick_scale:false,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:'ðŸ­',fitzpatrick_scale:false,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:'ðŸ«',fitzpatrick_scale:false,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:'ðŸ¿',fitzpatrick_scale:false,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:'🥟',fitzpatrick_scale:false,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:'ðŸ©',fitzpatrick_scale:false,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:'ðŸª',fitzpatrick_scale:false,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:'🥛',fitzpatrick_scale:false,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'ðŸº',fitzpatrick_scale:false,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'ðŸ»',fitzpatrick_scale:false,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:'🥂',fitzpatrick_scale:false,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:'ðŸ·',fitzpatrick_scale:false,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:'🥃',fitzpatrick_scale:false,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:'ðŸ¸',fitzpatrick_scale:false,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:'ðŸ¹',fitzpatrick_scale:false,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:'ðŸ¾',fitzpatrick_scale:false,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:'ðŸ¶',fitzpatrick_scale:false,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:'ðŸµ',fitzpatrick_scale:false,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:'🥤',fitzpatrick_scale:false,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:'☕',fitzpatrick_scale:false,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:'ðŸ¼',fitzpatrick_scale:false,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:'🧂',fitzpatrick_scale:false,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:'🥄',fitzpatrick_scale:false,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:'ðŸ´',fitzpatrick_scale:false,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:'ðŸ½',fitzpatrick_scale:false,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:'🥣',fitzpatrick_scale:false,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:'🥡',fitzpatrick_scale:false,category:"food_and_drink"},chopsticks:{keywords:["food"],char:'🥢',fitzpatrick_scale:false,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:'âš½',fitzpatrick_scale:false,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:'ðŸ€',fitzpatrick_scale:false,category:"activity"},football:{keywords:["sports","balls","NFL"],char:'ðŸˆ',fitzpatrick_scale:false,category:"activity"},baseball:{keywords:["sports","balls"],char:'âš¾',fitzpatrick_scale:false,category:"activity"},softball:{keywords:["sports","balls"],char:'🥎',fitzpatrick_scale:false,category:"activity"},tennis:{keywords:["sports","balls","green"],char:'🎾',fitzpatrick_scale:false,category:"activity"},volleyball:{keywords:["sports","balls"],char:'ðŸ',fitzpatrick_scale:false,category:"activity"},rugby_football:{keywords:["sports","team"],char:'ðŸ‰',fitzpatrick_scale:false,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:'ðŸ¥',fitzpatrick_scale:false,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:'🎱',fitzpatrick_scale:false,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:'⛳',fitzpatrick_scale:false,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:'ðŸŒï¸â€â™€ï¸',fitzpatrick_scale:false,category:"activity"},golfing_man:{keywords:["sports","business"],char:'ðŸŒ',fitzpatrick_scale:true,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:'ðŸ“',fitzpatrick_scale:false,category:"activity"},badminton:{keywords:["sports"],char:'ðŸ¸',fitzpatrick_scale:false,category:"activity"},goal_net:{keywords:["sports"],char:'🥅',fitzpatrick_scale:false,category:"activity"},ice_hockey:{keywords:["sports"],char:'ðŸ’',fitzpatrick_scale:false,category:"activity"},field_hockey:{keywords:["sports"],char:'ðŸ‘',fitzpatrick_scale:false,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:'ðŸ¥',fitzpatrick_scale:false,category:"activity"},cricket:{keywords:["sports"],char:'ðŸ',fitzpatrick_scale:false,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:'🎿',fitzpatrick_scale:false,category:"activity"},skier:{keywords:["sports","winter","snow"],char:'â›·',fitzpatrick_scale:false,category:"activity"},snowboarder:{keywords:["sports","winter"],char:'ðŸ‚',fitzpatrick_scale:true,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:'🤺',fitzpatrick_scale:false,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:'🤼â€â™€ï¸',fitzpatrick_scale:false,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:'🤼â€â™‚ï¸',fitzpatrick_scale:false,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:'🤸â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:'🤸â€â™‚ï¸',fitzpatrick_scale:true,category:"activity"},woman_playing_handball:{keywords:["sports"],char:'🤾â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},man_playing_handball:{keywords:["sports"],char:'🤾â€â™‚ï¸',fitzpatrick_scale:true,category:"activity"},ice_skate:{keywords:["sports"],char:'⛸',fitzpatrick_scale:false,category:"activity"},curling_stone:{keywords:["sports"],char:'🥌',fitzpatrick_scale:false,category:"activity"},skateboard:{keywords:["board"],char:'🛹',fitzpatrick_scale:false,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:'🛷',fitzpatrick_scale:false,category:"activity"},bow_and_arrow:{keywords:["sports"],char:'ðŸ¹',fitzpatrick_scale:false,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:'🎣',fitzpatrick_scale:false,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:'🥊',fitzpatrick_scale:false,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:'🥋',fitzpatrick_scale:false,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:'🚣â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:'🚣',fitzpatrick_scale:true,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:'🧗â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:'🧗â€â™‚ï¸',fitzpatrick_scale:true,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:'ðŸŠâ€â™€ï¸',fitzpatrick_scale:true,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:'ðŸŠ',fitzpatrick_scale:true,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:'🤽â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:'🤽â€â™‚ï¸',fitzpatrick_scale:true,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:'🧘â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:'🧘â€â™‚ï¸',fitzpatrick_scale:true,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:'ðŸ„â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:'ðŸ„',fitzpatrick_scale:true,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:'🛀',fitzpatrick_scale:true,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:'⛹ï¸â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},basketball_man:{keywords:["sports","human"],char:'⛹',fitzpatrick_scale:true,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:'ðŸ‹ï¸â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:'ðŸ‹',fitzpatrick_scale:true,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:'🚴â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:'🚴',fitzpatrick_scale:true,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:'🚵â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:'🚵',fitzpatrick_scale:true,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:'ðŸ‡',fitzpatrick_scale:true,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:'🕴',fitzpatrick_scale:true,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:'ðŸ†',fitzpatrick_scale:false,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:'🎽',fitzpatrick_scale:false,category:"activity"},medal_sports:{keywords:["award","winning"],char:'ðŸ…',fitzpatrick_scale:false,category:"activity"},medal_military:{keywords:["award","winning","army"],char:'🎖',fitzpatrick_scale:false,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:'🥇',fitzpatrick_scale:false,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:'🥈',fitzpatrick_scale:false,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:'🥉',fitzpatrick_scale:false,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:'🎗',fitzpatrick_scale:false,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:'ðŸµ',fitzpatrick_scale:false,category:"activity"},ticket:{keywords:["event","concert","pass"],char:'🎫',fitzpatrick_scale:false,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:'🎟',fitzpatrick_scale:false,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:'🎭',fitzpatrick_scale:false,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:'🎨',fitzpatrick_scale:false,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:'🎪',fitzpatrick_scale:false,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:'🤹â€â™€ï¸',fitzpatrick_scale:true,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:'🤹â€â™‚ï¸',fitzpatrick_scale:true,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:'🎤',fitzpatrick_scale:false,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:'🎧',fitzpatrick_scale:false,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:'🎼',fitzpatrick_scale:false,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:'🎹',fitzpatrick_scale:false,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:'ðŸ¥',fitzpatrick_scale:false,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:'🎷',fitzpatrick_scale:false,category:"activity"},trumpet:{keywords:["music","brass"],char:'🎺',fitzpatrick_scale:false,category:"activity"},guitar:{keywords:["music","instrument"],char:'🎸',fitzpatrick_scale:false,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:'🎻',fitzpatrick_scale:false,category:"activity"},clapper:{keywords:["movie","film","record"],char:'🎬',fitzpatrick_scale:false,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:'🎮',fitzpatrick_scale:false,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:'👾',fitzpatrick_scale:false,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:'🎯',fitzpatrick_scale:false,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:'🎲',fitzpatrick_scale:false,category:"activity"},chess_pawn:{keywords:["expendable"],char:"♟",fitzpatrick_scale:false,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:'🎰',fitzpatrick_scale:false,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:'🧩',fitzpatrick_scale:false,category:"activity"},bowling:{keywords:["sports","fun","play"],char:'🎳',fitzpatrick_scale:false,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:'🚗',fitzpatrick_scale:false,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:'🚕',fitzpatrick_scale:false,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:'🚙',fitzpatrick_scale:false,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:'🚌',fitzpatrick_scale:false,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:'🚎',fitzpatrick_scale:false,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:'ðŸŽ',fitzpatrick_scale:false,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:'🚓',fitzpatrick_scale:false,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:'🚑',fitzpatrick_scale:false,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:'🚒',fitzpatrick_scale:false,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:'ðŸš',fitzpatrick_scale:false,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:'🚚',fitzpatrick_scale:false,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:'🚛',fitzpatrick_scale:false,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:'🚜',fitzpatrick_scale:false,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:'🛴',fitzpatrick_scale:false,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:'ðŸ',fitzpatrick_scale:false,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:'🚲',fitzpatrick_scale:false,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:'🛵',fitzpatrick_scale:false,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:'🚨',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:'🚔',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:'ðŸš',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:'🚘',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:'🚖',fitzpatrick_scale:false,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:'🚡',fitzpatrick_scale:false,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:'🚠',fitzpatrick_scale:false,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:'🚟',fitzpatrick_scale:false,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:'🚃',fitzpatrick_scale:false,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:'🚋',fitzpatrick_scale:false,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:'ðŸš',fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:'🚄',fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:'🚅',fitzpatrick_scale:false,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:'🚈',fitzpatrick_scale:false,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:'🚞',fitzpatrick_scale:false,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:'🚂',fitzpatrick_scale:false,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:'🚆',fitzpatrick_scale:false,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:'🚇',fitzpatrick_scale:false,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:'🚊',fitzpatrick_scale:false,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:'🚉',fitzpatrick_scale:false,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:'🛸',fitzpatrick_scale:false,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:'ðŸš',fitzpatrick_scale:false,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:'🛩',fitzpatrick_scale:false,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:'✈ï¸',fitzpatrick_scale:false,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:'🛫',fitzpatrick_scale:false,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:'🛬',fitzpatrick_scale:false,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:'⛵',fitzpatrick_scale:false,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:'🛥',fitzpatrick_scale:false,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:'🚤',fitzpatrick_scale:false,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:'â›´',fitzpatrick_scale:false,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:'🛳',fitzpatrick_scale:false,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:'🚀',fitzpatrick_scale:false,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:'🛰',fitzpatrick_scale:false,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:'💺',fitzpatrick_scale:false,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:'🛶',fitzpatrick_scale:false,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:'âš“',fitzpatrick_scale:false,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:'🚧',fitzpatrick_scale:false,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:'⛽',fitzpatrick_scale:false,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:'ðŸš',fitzpatrick_scale:false,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:'🚦',fitzpatrick_scale:false,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:'🚥',fitzpatrick_scale:false,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:'ðŸ',fitzpatrick_scale:false,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:'🚢',fitzpatrick_scale:false,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:'🎡',fitzpatrick_scale:false,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:'🎢',fitzpatrick_scale:false,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:'🎠',fitzpatrick_scale:false,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:'ðŸ—',fitzpatrick_scale:false,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:'ðŸŒ',fitzpatrick_scale:false,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:'🗼',fitzpatrick_scale:false,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:'ðŸ­',fitzpatrick_scale:false,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:'⛲',fitzpatrick_scale:false,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:'🎑',fitzpatrick_scale:false,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:'â›°',fitzpatrick_scale:false,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:'ðŸ”',fitzpatrick_scale:false,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:'🗻',fitzpatrick_scale:false,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:'🌋',fitzpatrick_scale:false,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:'🗾',fitzpatrick_scale:false,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:'ðŸ•',fitzpatrick_scale:false,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:'⛺',fitzpatrick_scale:false,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:'ðŸž',fitzpatrick_scale:false,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:'🛣',fitzpatrick_scale:false,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:'🛤',fitzpatrick_scale:false,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:'🌅',fitzpatrick_scale:false,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:'🌄',fitzpatrick_scale:false,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:'ðŸœ',fitzpatrick_scale:false,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:'ðŸ–',fitzpatrick_scale:false,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:'ðŸ',fitzpatrick_scale:false,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:'🌇',fitzpatrick_scale:false,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:'🌆',fitzpatrick_scale:false,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:'ðŸ™',fitzpatrick_scale:false,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:'🌃',fitzpatrick_scale:false,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:'🌉',fitzpatrick_scale:false,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:'🌌',fitzpatrick_scale:false,category:"travel_and_places"},stars:{keywords:["night","photo"],char:'🌠',fitzpatrick_scale:false,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:'🎇',fitzpatrick_scale:false,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:'🎆',fitzpatrick_scale:false,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:'🌈',fitzpatrick_scale:false,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:'ðŸ˜',fitzpatrick_scale:false,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:'ðŸ°',fitzpatrick_scale:false,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:'ðŸ¯',fitzpatrick_scale:false,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:'ðŸŸ',fitzpatrick_scale:false,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:'🗽',fitzpatrick_scale:false,category:"travel_and_places"},house:{keywords:["building","home"],char:'ðŸ ',fitzpatrick_scale:false,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:'ðŸ¡',fitzpatrick_scale:false,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:'ðŸš',fitzpatrick_scale:false,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:'ðŸ¢',fitzpatrick_scale:false,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:'ðŸ¬',fitzpatrick_scale:false,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:'ðŸ£',fitzpatrick_scale:false,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:'ðŸ¤',fitzpatrick_scale:false,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:'ðŸ¥',fitzpatrick_scale:false,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:'ðŸ¦',fitzpatrick_scale:false,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:'ðŸ¨',fitzpatrick_scale:false,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:'ðŸª',fitzpatrick_scale:false,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:'ðŸ«',fitzpatrick_scale:false,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:'ðŸ©',fitzpatrick_scale:false,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:'💒',fitzpatrick_scale:false,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:'ðŸ›',fitzpatrick_scale:false,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:'⛪',fitzpatrick_scale:false,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:'🕌',fitzpatrick_scale:false,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:'ðŸ•',fitzpatrick_scale:false,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:'🕋',fitzpatrick_scale:false,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:'⛩',fitzpatrick_scale:false,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:'⌚',fitzpatrick_scale:false,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:'📱',fitzpatrick_scale:false,category:"objects"},calling:{keywords:["iphone","incoming"],char:'📲',fitzpatrick_scale:false,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:'💻',fitzpatrick_scale:false,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:'⌨',fitzpatrick_scale:false,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:'🖥',fitzpatrick_scale:false,category:"objects"},printer:{keywords:["paper","ink"],char:'🖨',fitzpatrick_scale:false,category:"objects"},computer_mouse:{keywords:["click"],char:'🖱',fitzpatrick_scale:false,category:"objects"},trackball:{keywords:["technology","trackpad"],char:'🖲',fitzpatrick_scale:false,category:"objects"},joystick:{keywords:["game","play"],char:'🕹',fitzpatrick_scale:false,category:"objects"},clamp:{keywords:["tool"],char:'🗜',fitzpatrick_scale:false,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:'💽',fitzpatrick_scale:false,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:'💾',fitzpatrick_scale:false,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:'💿',fitzpatrick_scale:false,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:'📀',fitzpatrick_scale:false,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:'📼',fitzpatrick_scale:false,category:"objects"},camera:{keywords:["gadgets","photography"],char:'📷',fitzpatrick_scale:false,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:'📸',fitzpatrick_scale:false,category:"objects"},video_camera:{keywords:["film","record"],char:'📹',fitzpatrick_scale:false,category:"objects"},movie_camera:{keywords:["film","record"],char:'🎥',fitzpatrick_scale:false,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:'📽',fitzpatrick_scale:false,category:"objects"},film_strip:{keywords:["movie"],char:'🎞',fitzpatrick_scale:false,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:'📞',fitzpatrick_scale:false,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:'☎ï¸',fitzpatrick_scale:false,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:'📟',fitzpatrick_scale:false,category:"objects"},fax:{keywords:["communication","technology"],char:'📠',fitzpatrick_scale:false,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:'📺',fitzpatrick_scale:false,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:'📻',fitzpatrick_scale:false,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:'🎙',fitzpatrick_scale:false,category:"objects"},level_slider:{keywords:["scale"],char:'🎚',fitzpatrick_scale:false,category:"objects"},control_knobs:{keywords:["dial"],char:'🎛',fitzpatrick_scale:false,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:'🧭',fitzpatrick_scale:false,category:"objects"},stopwatch:{keywords:["time","deadline"],char:'â±',fitzpatrick_scale:false,category:"objects"},timer_clock:{keywords:["alarm"],char:'â²',fitzpatrick_scale:false,category:"objects"},alarm_clock:{keywords:["time","wake"],char:'â°',fitzpatrick_scale:false,category:"objects"},mantelpiece_clock:{keywords:["time"],char:'🕰',fitzpatrick_scale:false,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:'â³',fitzpatrick_scale:false,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:'⌛',fitzpatrick_scale:false,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:'📡',fitzpatrick_scale:false,category:"objects"},battery:{keywords:["power","energy","sustain"],char:'🔋',fitzpatrick_scale:false,category:"objects"},electric_plug:{keywords:["charger","power"],char:'🔌',fitzpatrick_scale:false,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:'💡',fitzpatrick_scale:false,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:'🔦',fitzpatrick_scale:false,category:"objects"},candle:{keywords:["fire","wax"],char:'🕯',fitzpatrick_scale:false,category:"objects"},fire_extinguisher:{keywords:["quench"],char:'🧯',fitzpatrick_scale:false,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:'🗑',fitzpatrick_scale:false,category:"objects"},oil_drum:{keywords:["barrell"],char:'🛢',fitzpatrick_scale:false,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:'💸',fitzpatrick_scale:false,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:'💵',fitzpatrick_scale:false,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:'💴',fitzpatrick_scale:false,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:'💶',fitzpatrick_scale:false,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:'💷',fitzpatrick_scale:false,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:'💰',fitzpatrick_scale:false,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:'💳',fitzpatrick_scale:false,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:'💎',fitzpatrick_scale:false,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:'âš–',fitzpatrick_scale:false,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:'🧰',fitzpatrick_scale:false,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:'🔧',fitzpatrick_scale:false,category:"objects"},hammer:{keywords:["tools","build","create"],char:'🔨',fitzpatrick_scale:false,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:'âš’',fitzpatrick_scale:false,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:'🛠',fitzpatrick_scale:false,category:"objects"},pick:{keywords:["tools","dig"],char:'â›',fitzpatrick_scale:false,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:'🔩',fitzpatrick_scale:false,category:"objects"},gear:{keywords:["cog"],char:'âš™',fitzpatrick_scale:false,category:"objects"},brick:{keywords:["bricks"],char:'🧱',fitzpatrick_scale:false,category:"objects"},chains:{keywords:["lock","arrest"],char:'⛓',fitzpatrick_scale:false,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:'🧲',fitzpatrick_scale:false,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:'🔫',fitzpatrick_scale:false,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:'💣',fitzpatrick_scale:false,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:'🧨',fitzpatrick_scale:false,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:'🔪',fitzpatrick_scale:false,category:"objects"},dagger:{keywords:["weapon"],char:'🗡',fitzpatrick_scale:false,category:"objects"},crossed_swords:{keywords:["weapon"],char:'âš”',fitzpatrick_scale:false,category:"objects"},shield:{keywords:["protection","security"],char:'🛡',fitzpatrick_scale:false,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:'🚬',fitzpatrick_scale:false,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:'☠',fitzpatrick_scale:false,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:'âš°',fitzpatrick_scale:false,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:'âš±',fitzpatrick_scale:false,category:"objects"},amphora:{keywords:["vase","jar"],char:'ðŸº',fitzpatrick_scale:false,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:'🔮',fitzpatrick_scale:false,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:'📿',fitzpatrick_scale:false,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:'🧿',fitzpatrick_scale:false,category:"objects"},barber:{keywords:["hair","salon","style"],char:'💈',fitzpatrick_scale:false,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:'âš—',fitzpatrick_scale:false,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:'🔭',fitzpatrick_scale:false,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:'🔬',fitzpatrick_scale:false,category:"objects"},hole:{keywords:["embarrassing"],char:'🕳',fitzpatrick_scale:false,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:'💊',fitzpatrick_scale:false,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:'💉',fitzpatrick_scale:false,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:'🧬',fitzpatrick_scale:false,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:'🦠',fitzpatrick_scale:false,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:'🧫',fitzpatrick_scale:false,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:'🧪',fitzpatrick_scale:false,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:'🌡',fitzpatrick_scale:false,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:'🧹',fitzpatrick_scale:false,category:"objects"},basket:{keywords:["laundry"],char:'🧺',fitzpatrick_scale:false,category:"objects"},toilet_paper:{keywords:["roll"],char:'🧻',fitzpatrick_scale:false,category:"objects"},label:{keywords:["sale","tag"],char:'ðŸ·',fitzpatrick_scale:false,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:'🔖',fitzpatrick_scale:false,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:'🚽',fitzpatrick_scale:false,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:'🚿',fitzpatrick_scale:false,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:'ðŸ›',fitzpatrick_scale:false,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:'🧼',fitzpatrick_scale:false,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:'🧽',fitzpatrick_scale:false,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:'🧴',fitzpatrick_scale:false,category:"objects"},key:{keywords:["lock","door","password"],char:'🔑',fitzpatrick_scale:false,category:"objects"},old_key:{keywords:["lock","door","password"],char:'ðŸ—',fitzpatrick_scale:false,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:'🛋',fitzpatrick_scale:false,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:'🛌',fitzpatrick_scale:true,category:"objects"},bed:{keywords:["sleep","rest"],char:'ðŸ›',fitzpatrick_scale:false,category:"objects"},door:{keywords:["house","entry","exit"],char:'🚪',fitzpatrick_scale:false,category:"objects"},bellhop_bell:{keywords:["service"],char:'🛎',fitzpatrick_scale:false,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:'🧸',fitzpatrick_scale:false,category:"objects"},framed_picture:{keywords:["photography"],char:'🖼',fitzpatrick_scale:false,category:"objects"},world_map:{keywords:["location","direction"],char:'🗺',fitzpatrick_scale:false,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:'â›±',fitzpatrick_scale:false,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:'🗿',fitzpatrick_scale:false,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:'ðŸ›',fitzpatrick_scale:false,category:"objects"},shopping_cart:{keywords:["trolley"],char:'🛒',fitzpatrick_scale:false,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:'🎈',fitzpatrick_scale:false,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:'ðŸŽ',fitzpatrick_scale:false,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:'🎀',fitzpatrick_scale:false,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:'ðŸŽ',fitzpatrick_scale:false,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:'🎊',fitzpatrick_scale:false,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:'🎉',fitzpatrick_scale:false,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:'🎎',fitzpatrick_scale:false,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:'ðŸŽ',fitzpatrick_scale:false,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:'🎌',fitzpatrick_scale:false,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:'ðŸ®',fitzpatrick_scale:false,category:"objects"},red_envelope:{keywords:["gift"],char:'🧧',fitzpatrick_scale:false,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:'✉ï¸',fitzpatrick_scale:false,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:'📩',fitzpatrick_scale:false,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:'📨',fitzpatrick_scale:false,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:'📧',fitzpatrick_scale:false,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:'💌',fitzpatrick_scale:false,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:'📮',fitzpatrick_scale:false,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:'📪',fitzpatrick_scale:false,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:'📫',fitzpatrick_scale:false,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:'📬',fitzpatrick_scale:false,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:'📭',fitzpatrick_scale:false,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:'📦',fitzpatrick_scale:false,category:"objects"},postal_horn:{keywords:["instrument","music"],char:'📯',fitzpatrick_scale:false,category:"objects"},inbox_tray:{keywords:["email","documents"],char:'📥',fitzpatrick_scale:false,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:'📤',fitzpatrick_scale:false,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:'📜',fitzpatrick_scale:false,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:'📃',fitzpatrick_scale:false,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:'📑',fitzpatrick_scale:false,category:"objects"},receipt:{keywords:["accounting","expenses"],char:'🧾',fitzpatrick_scale:false,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:'📊',fitzpatrick_scale:false,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:'📈',fitzpatrick_scale:false,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:'📉',fitzpatrick_scale:false,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:'📄',fitzpatrick_scale:false,category:"objects"},date:{keywords:["calendar","schedule"],char:'📅',fitzpatrick_scale:false,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:'📆',fitzpatrick_scale:false,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:'🗓',fitzpatrick_scale:false,category:"objects"},card_index:{keywords:["business","stationery"],char:'📇',fitzpatrick_scale:false,category:"objects"},card_file_box:{keywords:["business","stationery"],char:'🗃',fitzpatrick_scale:false,category:"objects"},ballot_box:{keywords:["election","vote"],char:'🗳',fitzpatrick_scale:false,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:'🗄',fitzpatrick_scale:false,category:"objects"},clipboard:{keywords:["stationery","documents"],char:'📋',fitzpatrick_scale:false,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:'🗒',fitzpatrick_scale:false,category:"objects"},file_folder:{keywords:["documents","business","office"],char:'ðŸ“',fitzpatrick_scale:false,category:"objects"},open_file_folder:{keywords:["documents","load"],char:'📂',fitzpatrick_scale:false,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:'🗂',fitzpatrick_scale:false,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:'🗞',fitzpatrick_scale:false,category:"objects"},newspaper:{keywords:["press","headline"],char:'📰',fitzpatrick_scale:false,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:'📓',fitzpatrick_scale:false,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:'📕',fitzpatrick_scale:false,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:'📗',fitzpatrick_scale:false,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:'📘',fitzpatrick_scale:false,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:'📙',fitzpatrick_scale:false,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:'📔',fitzpatrick_scale:false,category:"objects"},ledger:{keywords:["notes","paper"],char:'📒',fitzpatrick_scale:false,category:"objects"},books:{keywords:["literature","library","study"],char:'📚',fitzpatrick_scale:false,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:'📖',fitzpatrick_scale:false,category:"objects"},safety_pin:{keywords:["diaper"],char:'🧷',fitzpatrick_scale:false,category:"objects"},link:{keywords:["rings","url"],char:'🔗',fitzpatrick_scale:false,category:"objects"},paperclip:{keywords:["documents","stationery"],char:'📎',fitzpatrick_scale:false,category:"objects"},paperclips:{keywords:["documents","stationery"],char:'🖇',fitzpatrick_scale:false,category:"objects"},scissors:{keywords:["stationery","cut"],char:'✂ï¸',fitzpatrick_scale:false,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:'ðŸ“',fitzpatrick_scale:false,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:'ðŸ“',fitzpatrick_scale:false,category:"objects"},abacus:{keywords:["calculation"],char:'🧮',fitzpatrick_scale:false,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:'📌',fitzpatrick_scale:false,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:'ðŸ“',fitzpatrick_scale:false,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:'🚩',fitzpatrick_scale:false,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:'ðŸ³',fitzpatrick_scale:false,category:"objects"},black_flag:{keywords:["pirate"],char:'ðŸ´',fitzpatrick_scale:false,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:'ðŸ³ï¸â€ðŸŒˆ',fitzpatrick_scale:false,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:'ðŸ”',fitzpatrick_scale:false,category:"objects"},lock:{keywords:["security","password","padlock"],char:'🔒',fitzpatrick_scale:false,category:"objects"},unlock:{keywords:["privacy","security"],char:'🔓',fitzpatrick_scale:false,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:'ðŸ”',fitzpatrick_scale:false,category:"objects"},pen:{keywords:["stationery","writing","write"],char:'🖊',fitzpatrick_scale:false,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:'🖋',fitzpatrick_scale:false,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:'✒ï¸',fitzpatrick_scale:false,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:'ðŸ“',fitzpatrick_scale:false,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:'âœï¸',fitzpatrick_scale:false,category:"objects"},crayon:{keywords:["drawing","creativity"],char:'ðŸ–',fitzpatrick_scale:false,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:'🖌',fitzpatrick_scale:false,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:'ðŸ”',fitzpatrick_scale:false,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:'🔎',fitzpatrick_scale:false,category:"objects"},heart:{keywords:["love","like","valentines"],char:'â¤ï¸',fitzpatrick_scale:false,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:'🧡',fitzpatrick_scale:false,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:'💛',fitzpatrick_scale:false,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:'💚',fitzpatrick_scale:false,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:'💙',fitzpatrick_scale:false,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:'💜',fitzpatrick_scale:false,category:"symbols"},black_heart:{keywords:["evil"],char:'🖤',fitzpatrick_scale:false,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:'💔',fitzpatrick_scale:false,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:'â£',fitzpatrick_scale:false,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:'💕',fitzpatrick_scale:false,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:'💞',fitzpatrick_scale:false,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:'💓',fitzpatrick_scale:false,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:'💗',fitzpatrick_scale:false,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:'💖',fitzpatrick_scale:false,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:'💘',fitzpatrick_scale:false,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:'ðŸ’',fitzpatrick_scale:false,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:'💟',fitzpatrick_scale:false,category:"symbols"},peace_symbol:{keywords:["hippie"],char:'☮',fitzpatrick_scale:false,category:"symbols"},latin_cross:{keywords:["christianity"],char:'âœ',fitzpatrick_scale:false,category:"symbols"},star_and_crescent:{keywords:["islam"],char:'☪',fitzpatrick_scale:false,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'🕉',fitzpatrick_scale:false,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'☸',fitzpatrick_scale:false,category:"symbols"},star_of_david:{keywords:["judaism"],char:'✡',fitzpatrick_scale:false,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:'🔯',fitzpatrick_scale:false,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:'🕎',fitzpatrick_scale:false,category:"symbols"},yin_yang:{keywords:["balance"],char:'☯',fitzpatrick_scale:false,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:'☦',fitzpatrick_scale:false,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:'ðŸ›',fitzpatrick_scale:false,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:'⛎',fitzpatrick_scale:false,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:'♈',fitzpatrick_scale:false,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:'♉',fitzpatrick_scale:false,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:'♊',fitzpatrick_scale:false,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:'♋',fitzpatrick_scale:false,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:'♌',fitzpatrick_scale:false,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:'â™',fitzpatrick_scale:false,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:'♎',fitzpatrick_scale:false,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:'â™',fitzpatrick_scale:false,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:'â™',fitzpatrick_scale:false,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:'♑',fitzpatrick_scale:false,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:'â™’',fitzpatrick_scale:false,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:'♓',fitzpatrick_scale:false,category:"symbols"},id:{keywords:["purple-square","words"],char:'🆔',fitzpatrick_scale:false,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:'âš›',fitzpatrick_scale:false,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:'🈳',fitzpatrick_scale:false,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:'🈹',fitzpatrick_scale:false,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:'☢',fitzpatrick_scale:false,category:"symbols"},biohazard:{keywords:["danger"],char:'☣',fitzpatrick_scale:false,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:'📴',fitzpatrick_scale:false,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:'📳',fitzpatrick_scale:false,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:'🈶',fitzpatrick_scale:false,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:'🈚',fitzpatrick_scale:false,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:'🈸',fitzpatrick_scale:false,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:'🈺',fitzpatrick_scale:false,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:'🈷ï¸',fitzpatrick_scale:false,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:'✴ï¸',fitzpatrick_scale:false,category:"symbols"},vs:{keywords:["words","orange-square"],char:'🆚',fitzpatrick_scale:false,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:'🉑',fitzpatrick_scale:false,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:'💮',fitzpatrick_scale:false,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:'ðŸ‰',fitzpatrick_scale:false,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:'㊙ï¸',fitzpatrick_scale:false,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:'㊗ï¸',fitzpatrick_scale:false,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:'🈴',fitzpatrick_scale:false,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:'🈵',fitzpatrick_scale:false,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:'🈲',fitzpatrick_scale:false,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:'🅰ï¸',fitzpatrick_scale:false,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:'🅱ï¸',fitzpatrick_scale:false,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:'🆎',fitzpatrick_scale:false,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:'🆑',fitzpatrick_scale:false,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:'🅾ï¸',fitzpatrick_scale:false,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:'🆘',fitzpatrick_scale:false,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:'â›”',fitzpatrick_scale:false,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:'📛',fitzpatrick_scale:false,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:'🚫',fitzpatrick_scale:false,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:'âŒ',fitzpatrick_scale:false,category:"symbols"},o:{keywords:["circle","round"],char:'â­•',fitzpatrick_scale:false,category:"symbols"},stop_sign:{keywords:["stop"],char:'🛑',fitzpatrick_scale:false,category:"symbols"},anger:{keywords:["angry","mad"],char:'💢',fitzpatrick_scale:false,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:'♨ï¸',fitzpatrick_scale:false,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:'🚷',fitzpatrick_scale:false,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:'🚯',fitzpatrick_scale:false,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:'🚳',fitzpatrick_scale:false,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:'🚱',fitzpatrick_scale:false,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:'🔞',fitzpatrick_scale:false,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:'📵',fitzpatrick_scale:false,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:'â—',fitzpatrick_scale:false,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:'â•',fitzpatrick_scale:false,category:"symbols"},question:{keywords:["doubt","confused"],char:'â“',fitzpatrick_scale:false,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:'â”',fitzpatrick_scale:false,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:'‼ï¸',fitzpatrick_scale:false,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:'â‰ï¸',fitzpatrick_scale:false,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:'🔅',fitzpatrick_scale:false,category:"symbols"},high_brightness:{keywords:["sun","light"],char:'🔆',fitzpatrick_scale:false,category:"symbols"},trident:{keywords:["weapon","spear"],char:'🔱',fitzpatrick_scale:false,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:'âšœ',fitzpatrick_scale:false,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:'〽ï¸',fitzpatrick_scale:false,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:'âš ï¸',fitzpatrick_scale:false,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:'🚸',fitzpatrick_scale:false,category:"symbols"},beginner:{keywords:["badge","shield"],char:'🔰',fitzpatrick_scale:false,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:'â™»ï¸',fitzpatrick_scale:false,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:'🈯',fitzpatrick_scale:false,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:'💹',fitzpatrick_scale:false,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:'â‡ï¸',fitzpatrick_scale:false,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:'✳ï¸',fitzpatrick_scale:false,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:'âŽ',fitzpatrick_scale:false,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:'✅',fitzpatrick_scale:false,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:'💠',fitzpatrick_scale:false,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:'🌀',fitzpatrick_scale:false,category:"symbols"},loop:{keywords:["tape","cassette"],char:'âž¿',fitzpatrick_scale:false,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:'ðŸŒ',fitzpatrick_scale:false,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:'â“‚ï¸',fitzpatrick_scale:false,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:'ðŸ§',fitzpatrick_scale:false,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:'🈂ï¸',fitzpatrick_scale:false,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:'🛂',fitzpatrick_scale:false,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:'🛃',fitzpatrick_scale:false,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:'🛄',fitzpatrick_scale:false,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:'🛅',fitzpatrick_scale:false,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:'♿',fitzpatrick_scale:false,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:'🚭',fitzpatrick_scale:false,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:'🚾',fitzpatrick_scale:false,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:'🅿ï¸',fitzpatrick_scale:false,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:'🚰',fitzpatrick_scale:false,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:'🚹',fitzpatrick_scale:false,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:'🚺',fitzpatrick_scale:false,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:'🚼',fitzpatrick_scale:false,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:'🚻',fitzpatrick_scale:false,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:'🚮',fitzpatrick_scale:false,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:'🎦',fitzpatrick_scale:false,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:'📶',fitzpatrick_scale:false,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:'ðŸˆ',fitzpatrick_scale:false,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:'🆖',fitzpatrick_scale:false,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:'🆗',fitzpatrick_scale:false,category:"symbols"},up:{keywords:["blue-square","above","high"],char:'🆙',fitzpatrick_scale:false,category:"symbols"},cool:{keywords:["words","blue-square"],char:'🆒',fitzpatrick_scale:false,category:"symbols"},new:{keywords:["blue-square","words","start"],char:'🆕',fitzpatrick_scale:false,category:"symbols"},free:{keywords:["blue-square","words"],char:'🆓',fitzpatrick_scale:false,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:'0ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:'1ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:'2ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:'3ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:'4ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:'5ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:'6ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:'7ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:'8ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:'9ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:'🔟',fitzpatrick_scale:false,category:"symbols"},asterisk:{keywords:["star","keycap"],char:'*⃣',fitzpatrick_scale:false,category:"symbols"},eject_button:{keywords:["blue-square"],char:'âï¸',fitzpatrick_scale:false,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:'â–¶ï¸',fitzpatrick_scale:false,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:'â¸',fitzpatrick_scale:false,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:'â­',fitzpatrick_scale:false,category:"symbols"},stop_button:{keywords:["blue-square"],char:'â¹',fitzpatrick_scale:false,category:"symbols"},record_button:{keywords:["blue-square"],char:'âº',fitzpatrick_scale:false,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:'â¯',fitzpatrick_scale:false,category:"symbols"},previous_track_button:{keywords:["backward"],char:'â®',fitzpatrick_scale:false,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:'â©',fitzpatrick_scale:false,category:"symbols"},rewind:{keywords:["play","blue-square"],char:'âª',fitzpatrick_scale:false,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:'🔀',fitzpatrick_scale:false,category:"symbols"},repeat:{keywords:["loop","record"],char:'ðŸ”',fitzpatrick_scale:false,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:'🔂',fitzpatrick_scale:false,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:'â—€ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:'🔼',fitzpatrick_scale:false,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:'🔽',fitzpatrick_scale:false,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:'â«',fitzpatrick_scale:false,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:'â¬',fitzpatrick_scale:false,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:'âž¡ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:'⬅ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:'⬆ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:'⬇ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:'↗ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:'↘ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:'↙ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:'↖ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:'↕ï¸',fitzpatrick_scale:false,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:'↔ï¸',fitzpatrick_scale:false,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:'🔄',fitzpatrick_scale:false,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:'↪ï¸',fitzpatrick_scale:false,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:'↩ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:'⤴ï¸',fitzpatrick_scale:false,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:'⤵ï¸',fitzpatrick_scale:false,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:'#ï¸âƒ£',fitzpatrick_scale:false,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:'ℹï¸',fitzpatrick_scale:false,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:'🔤',fitzpatrick_scale:false,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:'🔡',fitzpatrick_scale:false,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:'🔠',fitzpatrick_scale:false,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:'🔣',fitzpatrick_scale:false,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:'🎵',fitzpatrick_scale:false,category:"symbols"},notes:{keywords:["music","score"],char:'🎶',fitzpatrick_scale:false,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:'〰ï¸',fitzpatrick_scale:false,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:'âž°',fitzpatrick_scale:false,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:'✔ï¸',fitzpatrick_scale:false,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:'🔃',fitzpatrick_scale:false,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:'âž•',fitzpatrick_scale:false,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:'âž–',fitzpatrick_scale:false,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:'âž—',fitzpatrick_scale:false,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:'✖ï¸',fitzpatrick_scale:false,category:"symbols"},infinity:{keywords:["forever"],char:'♾',fitzpatrick_scale:false,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:'💲',fitzpatrick_scale:false,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:'💱',fitzpatrick_scale:false,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:'©ï¸',fitzpatrick_scale:false,category:"symbols"},registered:{keywords:["alphabet","circle"],char:'®ï¸',fitzpatrick_scale:false,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:'â„¢ï¸',fitzpatrick_scale:false,category:"symbols"},end:{keywords:["words","arrow"],char:'🔚',fitzpatrick_scale:false,category:"symbols"},back:{keywords:["arrow","words","return"],char:'🔙',fitzpatrick_scale:false,category:"symbols"},on:{keywords:["arrow","words"],char:'🔛',fitzpatrick_scale:false,category:"symbols"},top:{keywords:["words","blue-square"],char:'ðŸ”',fitzpatrick_scale:false,category:"symbols"},soon:{keywords:["arrow","words"],char:'🔜',fitzpatrick_scale:false,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:'☑ï¸',fitzpatrick_scale:false,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:'🔘',fitzpatrick_scale:false,category:"symbols"},white_circle:{keywords:["shape","round"],char:'⚪',fitzpatrick_scale:false,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:'âš«',fitzpatrick_scale:false,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:'🔴',fitzpatrick_scale:false,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:'🔵',fitzpatrick_scale:false,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:'🔸',fitzpatrick_scale:false,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:'🔹',fitzpatrick_scale:false,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:'🔶',fitzpatrick_scale:false,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:'🔷',fitzpatrick_scale:false,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:'🔺',fitzpatrick_scale:false,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:'â–ªï¸',fitzpatrick_scale:false,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:'â–«ï¸',fitzpatrick_scale:false,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:'⬛',fitzpatrick_scale:false,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:'⬜',fitzpatrick_scale:false,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:'🔻',fitzpatrick_scale:false,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:'â—¼ï¸',fitzpatrick_scale:false,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:'â—»ï¸',fitzpatrick_scale:false,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:'â—¾',fitzpatrick_scale:false,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:'â—½',fitzpatrick_scale:false,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:'🔲',fitzpatrick_scale:false,category:"symbols"},white_square_button:{keywords:["shape","input"],char:'🔳',fitzpatrick_scale:false,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:'🔈',fitzpatrick_scale:false,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:'🔉',fitzpatrick_scale:false,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:'🔊',fitzpatrick_scale:false,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:'🔇',fitzpatrick_scale:false,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:'📣',fitzpatrick_scale:false,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:'📢',fitzpatrick_scale:false,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:'🔔',fitzpatrick_scale:false,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:'🔕',fitzpatrick_scale:false,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:'ðŸƒ',fitzpatrick_scale:false,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:'🀄',fitzpatrick_scale:false,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:'â™ ï¸',fitzpatrick_scale:false,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:'♣ï¸',fitzpatrick_scale:false,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:'♥ï¸',fitzpatrick_scale:false,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:'♦ï¸',fitzpatrick_scale:false,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:'🎴',fitzpatrick_scale:false,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:'💭',fitzpatrick_scale:false,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:'🗯',fitzpatrick_scale:false,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:'💬',fitzpatrick_scale:false,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:'🗨',fitzpatrick_scale:false,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:'ðŸ•',fitzpatrick_scale:false,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:'🕑',fitzpatrick_scale:false,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:'🕒',fitzpatrick_scale:false,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:'🕓',fitzpatrick_scale:false,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:'🕔',fitzpatrick_scale:false,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:'🕕',fitzpatrick_scale:false,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:'🕖',fitzpatrick_scale:false,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:'🕗',fitzpatrick_scale:false,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:'🕘',fitzpatrick_scale:false,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:'🕙',fitzpatrick_scale:false,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:'🕚',fitzpatrick_scale:false,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:'🕛',fitzpatrick_scale:false,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:'🕜',fitzpatrick_scale:false,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:'ðŸ•',fitzpatrick_scale:false,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:'🕞',fitzpatrick_scale:false,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:'🕟',fitzpatrick_scale:false,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:'🕠',fitzpatrick_scale:false,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:'🕡',fitzpatrick_scale:false,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:'🕢',fitzpatrick_scale:false,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:'🕣',fitzpatrick_scale:false,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:'🕤',fitzpatrick_scale:false,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:'🕥',fitzpatrick_scale:false,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:'🕦',fitzpatrick_scale:false,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:'🕧',fitzpatrick_scale:false,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:'🇦🇫',fitzpatrick_scale:false,category:"flags"},aland_islands:{keywords:["Ã…land","islands","flag","nation","country","banner"],char:'🇦🇽',fitzpatrick_scale:false,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:'🇦🇱',fitzpatrick_scale:false,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:'🇩🇿',fitzpatrick_scale:false,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:'🇦🇸',fitzpatrick_scale:false,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:'🇦🇩',fitzpatrick_scale:false,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:'🇦🇴',fitzpatrick_scale:false,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:'🇦🇮',fitzpatrick_scale:false,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:'🇦🇶',fitzpatrick_scale:false,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:'🇦🇬',fitzpatrick_scale:false,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:'🇦🇷',fitzpatrick_scale:false,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:'🇦🇲',fitzpatrick_scale:false,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:'🇦🇼',fitzpatrick_scale:false,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:'🇦🇺',fitzpatrick_scale:false,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:'🇦🇹',fitzpatrick_scale:false,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:'🇦🇿',fitzpatrick_scale:false,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:'🇧🇸',fitzpatrick_scale:false,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:'🇧🇭',fitzpatrick_scale:false,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:'🇧🇩',fitzpatrick_scale:false,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:'🇧🇧',fitzpatrick_scale:false,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:'🇧🇾',fitzpatrick_scale:false,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:'🇧🇪',fitzpatrick_scale:false,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:'🇧🇿',fitzpatrick_scale:false,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:'🇧🇯',fitzpatrick_scale:false,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:'🇧🇲',fitzpatrick_scale:false,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:'🇧🇹',fitzpatrick_scale:false,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:'🇧🇴',fitzpatrick_scale:false,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:'🇧🇶',fitzpatrick_scale:false,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:'🇧🇦',fitzpatrick_scale:false,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:'🇧🇼',fitzpatrick_scale:false,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:'🇧🇷',fitzpatrick_scale:false,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:'🇮🇴',fitzpatrick_scale:false,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:'🇻🇬',fitzpatrick_scale:false,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:'🇧🇳',fitzpatrick_scale:false,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:'🇧🇬',fitzpatrick_scale:false,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:'🇧🇫',fitzpatrick_scale:false,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:'🇧🇮',fitzpatrick_scale:false,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:'🇨🇻',fitzpatrick_scale:false,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:'🇰🇭',fitzpatrick_scale:false,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:'🇨🇲',fitzpatrick_scale:false,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:'🇨🇦',fitzpatrick_scale:false,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:'🇮🇨',fitzpatrick_scale:false,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:'🇰🇾',fitzpatrick_scale:false,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:'🇨🇫',fitzpatrick_scale:false,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:'🇹🇩',fitzpatrick_scale:false,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:'🇨🇱',fitzpatrick_scale:false,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:'🇨🇳',fitzpatrick_scale:false,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:'🇨🇽',fitzpatrick_scale:false,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:'🇨🇨',fitzpatrick_scale:false,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:'🇨🇴',fitzpatrick_scale:false,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:'🇰🇲',fitzpatrick_scale:false,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:'🇨🇬',fitzpatrick_scale:false,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:'🇨🇩',fitzpatrick_scale:false,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:'🇨🇰',fitzpatrick_scale:false,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:'🇨🇷',fitzpatrick_scale:false,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:'🇭🇷',fitzpatrick_scale:false,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:'🇨🇺',fitzpatrick_scale:false,category:"flags"},curacao:{keywords:["curaçao","flag","nation","country","banner"],char:'🇨🇼',fitzpatrick_scale:false,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:'🇨🇾',fitzpatrick_scale:false,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:'🇨🇿',fitzpatrick_scale:false,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:'🇩🇰',fitzpatrick_scale:false,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:'🇩🇯',fitzpatrick_scale:false,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:'🇩🇲',fitzpatrick_scale:false,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:'🇩🇴',fitzpatrick_scale:false,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:'🇪🇨',fitzpatrick_scale:false,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:'🇪🇬',fitzpatrick_scale:false,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:'🇸🇻',fitzpatrick_scale:false,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:'🇬🇶',fitzpatrick_scale:false,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:'🇪🇷',fitzpatrick_scale:false,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:'🇪🇪',fitzpatrick_scale:false,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:'🇪🇹',fitzpatrick_scale:false,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:'🇪🇺',fitzpatrick_scale:false,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:'🇫🇰',fitzpatrick_scale:false,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:'🇫🇴',fitzpatrick_scale:false,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:'🇫🇯',fitzpatrick_scale:false,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:'🇫🇮',fitzpatrick_scale:false,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:'🇫🇷',fitzpatrick_scale:false,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:'🇬🇫',fitzpatrick_scale:false,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:'🇵🇫',fitzpatrick_scale:false,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:'🇹🇫',fitzpatrick_scale:false,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:'🇬🇦',fitzpatrick_scale:false,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:'🇬🇲',fitzpatrick_scale:false,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:'🇬🇪',fitzpatrick_scale:false,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:'🇩🇪',fitzpatrick_scale:false,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:'🇬🇭',fitzpatrick_scale:false,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:'🇬🇮',fitzpatrick_scale:false,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:'🇬🇷',fitzpatrick_scale:false,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:'🇬🇱',fitzpatrick_scale:false,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:'🇬🇩',fitzpatrick_scale:false,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:'🇬🇵',fitzpatrick_scale:false,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:'🇬🇺',fitzpatrick_scale:false,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:'🇬🇹',fitzpatrick_scale:false,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:'🇬🇬',fitzpatrick_scale:false,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:'🇬🇳',fitzpatrick_scale:false,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:'🇬🇼',fitzpatrick_scale:false,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:'🇬🇾',fitzpatrick_scale:false,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:'🇭🇹',fitzpatrick_scale:false,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:'🇭🇳',fitzpatrick_scale:false,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:'🇭🇰',fitzpatrick_scale:false,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:'🇭🇺',fitzpatrick_scale:false,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:'🇮🇸',fitzpatrick_scale:false,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:'🇮🇳',fitzpatrick_scale:false,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:'🇮🇩',fitzpatrick_scale:false,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:'🇮🇷',fitzpatrick_scale:false,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:'🇮🇶',fitzpatrick_scale:false,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:'🇮🇪',fitzpatrick_scale:false,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:'🇮🇲',fitzpatrick_scale:false,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:'🇮🇱',fitzpatrick_scale:false,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:'🇮🇹',fitzpatrick_scale:false,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:'🇨🇮',fitzpatrick_scale:false,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:'🇯🇲',fitzpatrick_scale:false,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:'🇯🇵',fitzpatrick_scale:false,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:'🇯🇪',fitzpatrick_scale:false,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:'🇯🇴',fitzpatrick_scale:false,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:'🇰🇿',fitzpatrick_scale:false,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:'🇰🇪',fitzpatrick_scale:false,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:'🇰🇮',fitzpatrick_scale:false,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:'🇽🇰',fitzpatrick_scale:false,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:'🇰🇼',fitzpatrick_scale:false,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:'🇰🇬',fitzpatrick_scale:false,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:'🇱🇦',fitzpatrick_scale:false,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:'🇱🇻',fitzpatrick_scale:false,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:'🇱🇧',fitzpatrick_scale:false,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:'🇱🇸',fitzpatrick_scale:false,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:'🇱🇷',fitzpatrick_scale:false,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:'🇱🇾',fitzpatrick_scale:false,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:'🇱🇮',fitzpatrick_scale:false,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:'🇱🇹',fitzpatrick_scale:false,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:'🇱🇺',fitzpatrick_scale:false,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:'🇲🇴',fitzpatrick_scale:false,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:'🇲🇰',fitzpatrick_scale:false,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:'🇲🇬',fitzpatrick_scale:false,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:'🇲🇼',fitzpatrick_scale:false,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:'🇲🇾',fitzpatrick_scale:false,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:'🇲🇻',fitzpatrick_scale:false,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:'🇲🇱',fitzpatrick_scale:false,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:'🇲🇹',fitzpatrick_scale:false,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:'🇲🇭',fitzpatrick_scale:false,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:'🇲🇶',fitzpatrick_scale:false,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:'🇲🇷',fitzpatrick_scale:false,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:'🇲🇺',fitzpatrick_scale:false,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:'🇾🇹',fitzpatrick_scale:false,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:'🇲🇽',fitzpatrick_scale:false,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:'🇫🇲',fitzpatrick_scale:false,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:'🇲🇩',fitzpatrick_scale:false,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:'🇲🇨',fitzpatrick_scale:false,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:'🇲🇳',fitzpatrick_scale:false,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:'🇲🇪',fitzpatrick_scale:false,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:'🇲🇸',fitzpatrick_scale:false,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:'🇲🇦',fitzpatrick_scale:false,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:'🇲🇿',fitzpatrick_scale:false,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:'🇲🇲',fitzpatrick_scale:false,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:'🇳🇦',fitzpatrick_scale:false,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:'🇳🇷',fitzpatrick_scale:false,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:'🇳🇵',fitzpatrick_scale:false,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:'🇳🇱',fitzpatrick_scale:false,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:'🇳🇨',fitzpatrick_scale:false,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:'🇳🇿',fitzpatrick_scale:false,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:'🇳🇮',fitzpatrick_scale:false,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:'🇳🇪',fitzpatrick_scale:false,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:'🇳🇬',fitzpatrick_scale:false,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:'🇳🇺',fitzpatrick_scale:false,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:'🇳🇫',fitzpatrick_scale:false,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:'🇲🇵',fitzpatrick_scale:false,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:'🇰🇵',fitzpatrick_scale:false,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:'🇳🇴',fitzpatrick_scale:false,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:'🇴🇲',fitzpatrick_scale:false,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:'🇵🇰',fitzpatrick_scale:false,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:'🇵🇼',fitzpatrick_scale:false,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:'🇵🇸',fitzpatrick_scale:false,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:'🇵🇦',fitzpatrick_scale:false,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:'🇵🇬',fitzpatrick_scale:false,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:'🇵🇾',fitzpatrick_scale:false,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:'🇵🇪',fitzpatrick_scale:false,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:'🇵🇭',fitzpatrick_scale:false,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:'🇵🇳',fitzpatrick_scale:false,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:'🇵🇱',fitzpatrick_scale:false,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:'🇵🇹',fitzpatrick_scale:false,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:'🇵🇷',fitzpatrick_scale:false,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:'🇶🇦',fitzpatrick_scale:false,category:"flags"},reunion:{keywords:["réunion","flag","nation","country","banner"],char:'🇷🇪',fitzpatrick_scale:false,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:'🇷🇴',fitzpatrick_scale:false,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:'🇷🇺',fitzpatrick_scale:false,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:'🇷🇼',fitzpatrick_scale:false,category:"flags"},st_barthelemy:{keywords:["saint","barthélemy","flag","nation","country","banner"],char:'🇧🇱',fitzpatrick_scale:false,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:'🇸🇭',fitzpatrick_scale:false,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:'🇰🇳',fitzpatrick_scale:false,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:'🇱🇨',fitzpatrick_scale:false,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:'🇵🇲',fitzpatrick_scale:false,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:'🇻🇨',fitzpatrick_scale:false,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:'🇼🇸',fitzpatrick_scale:false,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:'🇸🇲',fitzpatrick_scale:false,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:'🇸🇹',fitzpatrick_scale:false,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:'🇸🇦',fitzpatrick_scale:false,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:'🇸🇳',fitzpatrick_scale:false,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:'🇷🇸',fitzpatrick_scale:false,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:'🇸🇨',fitzpatrick_scale:false,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:'🇸🇱',fitzpatrick_scale:false,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:'🇸🇬',fitzpatrick_scale:false,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:'🇸🇽',fitzpatrick_scale:false,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:'🇸🇰',fitzpatrick_scale:false,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:'🇸🇮',fitzpatrick_scale:false,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:'🇸🇧',fitzpatrick_scale:false,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:'🇸🇴',fitzpatrick_scale:false,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:'🇿🇦',fitzpatrick_scale:false,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:'🇬🇸',fitzpatrick_scale:false,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:'🇰🇷',fitzpatrick_scale:false,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:'🇸🇸',fitzpatrick_scale:false,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:'🇪🇸',fitzpatrick_scale:false,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:'🇱🇰',fitzpatrick_scale:false,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:'🇸🇩',fitzpatrick_scale:false,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:'🇸🇷',fitzpatrick_scale:false,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:'🇸🇿',fitzpatrick_scale:false,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:'🇸🇪',fitzpatrick_scale:false,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:'🇨🇭',fitzpatrick_scale:false,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:'🇸🇾',fitzpatrick_scale:false,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:'🇹🇼',fitzpatrick_scale:false,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:'🇹🇯',fitzpatrick_scale:false,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:'🇹🇿',fitzpatrick_scale:false,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:'🇹🇭',fitzpatrick_scale:false,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:'🇹🇱',fitzpatrick_scale:false,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:'🇹🇬',fitzpatrick_scale:false,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:'🇹🇰',fitzpatrick_scale:false,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:'🇹🇴',fitzpatrick_scale:false,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:'🇹🇹',fitzpatrick_scale:false,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:'🇹🇳',fitzpatrick_scale:false,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:'🇹🇷',fitzpatrick_scale:false,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:'🇹🇲',fitzpatrick_scale:false,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:'🇹🇨',fitzpatrick_scale:false,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:'🇹🇻',fitzpatrick_scale:false,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:'🇺🇬',fitzpatrick_scale:false,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:'🇺🇦',fitzpatrick_scale:false,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:'🇦🇪',fitzpatrick_scale:false,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:'🇬🇧',fitzpatrick_scale:false,category:"flags"},england:{keywords:["flag","english"],char:'ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿',fitzpatrick_scale:false,category:"flags"},scotland:{keywords:["flag","scottish"],char:'ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿',fitzpatrick_scale:false,category:"flags"},wales:{keywords:["flag","welsh"],char:'ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿',fitzpatrick_scale:false,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:'🇺🇸',fitzpatrick_scale:false,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:'🇻🇮',fitzpatrick_scale:false,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:'🇺🇾',fitzpatrick_scale:false,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:'🇺🇿',fitzpatrick_scale:false,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:'🇻🇺',fitzpatrick_scale:false,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:'🇻🇦',fitzpatrick_scale:false,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:'🇻🇪',fitzpatrick_scale:false,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:'🇻🇳',fitzpatrick_scale:false,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:'🇼🇫',fitzpatrick_scale:false,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:'🇪🇭',fitzpatrick_scale:false,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:'🇾🇪',fitzpatrick_scale:false,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:'🇿🇲',fitzpatrick_scale:false,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:'🇿🇼',fitzpatrick_scale:false,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:'🇺🇳',fitzpatrick_scale:false,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:'ðŸ´â€â˜ ï¸',fitzpatrick_scale:false,category:"flags"}}); \ No newline at end of file diff --git a/static/js/tinymce/plugins/emoticons/js/emojiimages.min.js b/static/js/tinymce/plugins/emoticons/js/emojiimages.min.js new file mode 100644 index 0000000..37f3bcf --- /dev/null +++ b/static/js/tinymce/plugins/emoticons/js/emojiimages.min.js @@ -0,0 +1,3 @@ +// Source: npm package: emojilib +// Images provided by twemoji: https://github.com/twitter/twemoji +window.tinymce.Resource.add("tinymce.plugins.emoticons",{100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:'\u{1f4af}',fitzpatrick_scale:!1,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:'\u{1f522}',fitzpatrick_scale:!1,category:"symbols"},grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:'\u{1f600}',fitzpatrick_scale:!1,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:'\u{1f62c}',fitzpatrick_scale:!1,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:'\u{1f601}',fitzpatrick_scale:!1,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:'\u{1f602}',fitzpatrick_scale:!1,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:'\u{1f923}',fitzpatrick_scale:!1,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:'\u{1f973}',fitzpatrick_scale:!1,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:'\u{1f603}',fitzpatrick_scale:!1,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:'\u{1f604}',fitzpatrick_scale:!1,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:'\u{1f605}',fitzpatrick_scale:!1,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:'\u{1f606}',fitzpatrick_scale:!1,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:'\u{1f607}',fitzpatrick_scale:!1,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:'\u{1f609}',fitzpatrick_scale:!1,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:'\u{1f60a}',fitzpatrick_scale:!1,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:'\u{1f642}',fitzpatrick_scale:!1,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:'\u{1f643}',fitzpatrick_scale:!1,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:'\u263a\ufe0f',fitzpatrick_scale:!1,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:'\u{1f60b}',fitzpatrick_scale:!1,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:'\u{1f60c}',fitzpatrick_scale:!1,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:'\u{1f60d}',fitzpatrick_scale:!1,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:'\u{1f970}',fitzpatrick_scale:!1,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'\u{1f618}',fitzpatrick_scale:!1,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:'\u{1f617}',fitzpatrick_scale:!1,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:'\u{1f619}',fitzpatrick_scale:!1,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'\u{1f61a}',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:'\u{1f61c}',fitzpatrick_scale:!1,category:"people"},zany:{keywords:["face","goofy","crazy"],char:'\u{1f92a}',fitzpatrick_scale:!1,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:'\u{1f928}',fitzpatrick_scale:!1,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:'\u{1f9d0}',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:'\u{1f61d}',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:'\u{1f61b}',fitzpatrick_scale:!1,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:'\u{1f911}',fitzpatrick_scale:!1,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:'\u{1f913}',fitzpatrick_scale:!1,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:'\u{1f60e}',fitzpatrick_scale:!1,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:'\u{1f929}',fitzpatrick_scale:!1,category:"people"},clown_face:{keywords:["face"],char:'\u{1f921}',fitzpatrick_scale:!1,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:'\u{1f920}',fitzpatrick_scale:!1,category:"people"},hugs:{keywords:["face","smile","hug"],char:'\u{1f917}',fitzpatrick_scale:!1,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:'\u{1f60f}',fitzpatrick_scale:!1,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:'\u{1f636}',fitzpatrick_scale:!1,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:'\u{1f610}',fitzpatrick_scale:!1,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:'\u{1f611}',fitzpatrick_scale:!1,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:'\u{1f612}',fitzpatrick_scale:!1,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:'\u{1f644}',fitzpatrick_scale:!1,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:'\u{1f914}',fitzpatrick_scale:!1,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:'\u{1f925}',fitzpatrick_scale:!1,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:'\u{1f92d}',fitzpatrick_scale:!1,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:'\u{1f92b}',fitzpatrick_scale:!1,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:'\u{1f92c}',fitzpatrick_scale:!1,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:'\u{1f92f}',fitzpatrick_scale:!1,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:'\u{1f633}',fitzpatrick_scale:!1,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:'\u{1f61e}',fitzpatrick_scale:!1,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:'\u{1f61f}',fitzpatrick_scale:!1,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:'\u{1f620}',fitzpatrick_scale:!1,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:'\u{1f621}',fitzpatrick_scale:!1,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:'\u{1f614}',fitzpatrick_scale:!1,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:'\u{1f615}',fitzpatrick_scale:!1,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:'\u{1f641}',fitzpatrick_scale:!1,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:'\u2639',fitzpatrick_scale:!1,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:'\u{1f623}',fitzpatrick_scale:!1,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:'\u{1f616}',fitzpatrick_scale:!1,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:'\u{1f62b}',fitzpatrick_scale:!1,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:'\u{1f629}',fitzpatrick_scale:!1,category:"people"},pleading:{keywords:["face","begging","mercy"],char:'\u{1f97a}',fitzpatrick_scale:!1,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:'\u{1f624}',fitzpatrick_scale:!1,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:'\u{1f62e}',fitzpatrick_scale:!1,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:'\u{1f631}',fitzpatrick_scale:!1,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:'\u{1f628}',fitzpatrick_scale:!1,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:'\u{1f630}',fitzpatrick_scale:!1,category:"people"},hushed:{keywords:["face","woo","shh"],char:'\u{1f62f}',fitzpatrick_scale:!1,category:"people"},frowning:{keywords:["face","aw","what"],char:'\u{1f626}',fitzpatrick_scale:!1,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:'\u{1f627}',fitzpatrick_scale:!1,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:'\u{1f622}',fitzpatrick_scale:!1,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:'\u{1f625}',fitzpatrick_scale:!1,category:"people"},drooling_face:{keywords:["face"],char:'\u{1f924}',fitzpatrick_scale:!1,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:'\u{1f62a}',fitzpatrick_scale:!1,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:'\u{1f613}',fitzpatrick_scale:!1,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:'\u{1f975}',fitzpatrick_scale:!1,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:'\u{1f976}',fitzpatrick_scale:!1,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:'\u{1f62d}',fitzpatrick_scale:!1,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:'\u{1f635}',fitzpatrick_scale:!1,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:'\u{1f632}',fitzpatrick_scale:!1,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:'\u{1f910}',fitzpatrick_scale:!1,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:'\u{1f922}',fitzpatrick_scale:!1,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:'\u{1f927}',fitzpatrick_scale:!1,category:"people"},vomiting:{keywords:["face","sick"],char:'\u{1f92e}',fitzpatrick_scale:!1,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:'\u{1f637}',fitzpatrick_scale:!1,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:'\u{1f912}',fitzpatrick_scale:!1,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:'\u{1f915}',fitzpatrick_scale:!1,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:'\u{1f974}',fitzpatrick_scale:!1,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:'\u{1f634}',fitzpatrick_scale:!1,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:'\u{1f4a4}',fitzpatrick_scale:!1,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:'\u{1f4a9}',fitzpatrick_scale:!1,category:"people"},smiling_imp:{keywords:["devil","horns"],char:'\u{1f608}',fitzpatrick_scale:!1,category:"people"},imp:{keywords:["devil","angry","horns"],char:'\u{1f47f}',fitzpatrick_scale:!1,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:'\u{1f479}',fitzpatrick_scale:!1,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:'\u{1f47a}',fitzpatrick_scale:!1,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:'\u{1f480}',fitzpatrick_scale:!1,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:'\u{1f47b}',fitzpatrick_scale:!1,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:'\u{1f47d}',fitzpatrick_scale:!1,category:"people"},robot:{keywords:["computer","machine","bot"],char:'\u{1f916}',fitzpatrick_scale:!1,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:'\u{1f63a}',fitzpatrick_scale:!1,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:'\u{1f638}',fitzpatrick_scale:!1,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:'\u{1f639}',fitzpatrick_scale:!1,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:'\u{1f63b}',fitzpatrick_scale:!1,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:'\u{1f63c}',fitzpatrick_scale:!1,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:'\u{1f63d}',fitzpatrick_scale:!1,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:'\u{1f640}',fitzpatrick_scale:!1,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:'\u{1f63f}',fitzpatrick_scale:!1,category:"people"},pouting_cat:{keywords:["animal","cats"],char:'\u{1f63e}',fitzpatrick_scale:!1,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:'\u{1f932}',fitzpatrick_scale:!0,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:'\u{1f64c}',fitzpatrick_scale:!0,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:'\u{1f44f}',fitzpatrick_scale:!0,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:'\u{1f44b}',fitzpatrick_scale:!0,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:'\u{1f919}',fitzpatrick_scale:!0,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:'\u{1f44d}',fitzpatrick_scale:!0,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:'\u{1f44e}',fitzpatrick_scale:!0,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:'\u{1f44a}',fitzpatrick_scale:!0,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:'\u270a',fitzpatrick_scale:!0,category:"people"},fist_left:{keywords:["hand","fistbump"],char:'\u{1f91b}',fitzpatrick_scale:!0,category:"people"},fist_right:{keywords:["hand","fistbump"],char:'\u{1f91c}',fitzpatrick_scale:!0,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:'\u270c',fitzpatrick_scale:!0,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:'\u{1f44c}',fitzpatrick_scale:!0,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:'\u270b',fitzpatrick_scale:!0,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:'\u{1f91a}',fitzpatrick_scale:!0,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:'\u{1f450}',fitzpatrick_scale:!0,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:'\u{1f4aa}',fitzpatrick_scale:!0,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:'\u{1f64f}',fitzpatrick_scale:!0,category:"people"},foot:{keywords:["kick","stomp"],char:'\u{1f9b6}',fitzpatrick_scale:!0,category:"people"},leg:{keywords:["kick","limb"],char:'\u{1f9b5}',fitzpatrick_scale:!0,category:"people"},handshake:{keywords:["agreement","shake"],char:'\u{1f91d}',fitzpatrick_scale:!1,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:'\u261d',fitzpatrick_scale:!0,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:'\u{1f446}',fitzpatrick_scale:!0,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:'\u{1f447}',fitzpatrick_scale:!0,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:'\u{1f448}',fitzpatrick_scale:!0,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:'\u{1f449}',fitzpatrick_scale:!0,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:'\u{1f595}',fitzpatrick_scale:!0,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:'\u{1f590}',fitzpatrick_scale:!0,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:'\u{1f91f}',fitzpatrick_scale:!0,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:'\u{1f918}',fitzpatrick_scale:!0,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:'\u{1f91e}',fitzpatrick_scale:!0,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:'\u{1f596}',fitzpatrick_scale:!0,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:'\u270d',fitzpatrick_scale:!0,category:"people"},selfie:{keywords:["camera","phone"],char:'\u{1f933}',fitzpatrick_scale:!0,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:'\u{1f485}',fitzpatrick_scale:!0,category:"people"},lips:{keywords:["mouth","kiss"],char:'\u{1f444}',fitzpatrick_scale:!1,category:"people"},tooth:{keywords:["teeth","dentist"],char:'\u{1f9b7}',fitzpatrick_scale:!1,category:"people"},tongue:{keywords:["mouth","playful"],char:'\u{1f445}',fitzpatrick_scale:!1,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:'\u{1f442}',fitzpatrick_scale:!0,category:"people"},nose:{keywords:["smell","sniff"],char:'\u{1f443}',fitzpatrick_scale:!0,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:'\u{1f441}',fitzpatrick_scale:!1,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:'\u{1f440}',fitzpatrick_scale:!1,category:"people"},brain:{keywords:["smart","intelligent"],char:'\u{1f9e0}',fitzpatrick_scale:!1,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:'\u{1f464}',fitzpatrick_scale:!1,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:'\u{1f465}',fitzpatrick_scale:!1,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:'\u{1f5e3}',fitzpatrick_scale:!1,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:'\u{1f476}',fitzpatrick_scale:!0,category:"people"},child:{keywords:["gender-neutral","young"],char:'\u{1f9d2}',fitzpatrick_scale:!0,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:'\u{1f466}',fitzpatrick_scale:!0,category:"people"},girl:{keywords:["female","woman","teenager"],char:'\u{1f467}',fitzpatrick_scale:!0,category:"people"},adult:{keywords:["gender-neutral","person"],char:'\u{1f9d1}',fitzpatrick_scale:!0,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:'\u{1f468}',fitzpatrick_scale:!0,category:"people"},woman:{keywords:["female","girls","lady"],char:'\u{1f469}',fitzpatrick_scale:!0,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:'\u{1f471}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:'\u{1f471}',fitzpatrick_scale:!0,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:'\u{1f9d4}',fitzpatrick_scale:!0,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:'\u{1f9d3}',fitzpatrick_scale:!0,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:'\u{1f474}',fitzpatrick_scale:!0,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:'\u{1f475}',fitzpatrick_scale:!0,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:'\u{1f472}',fitzpatrick_scale:!0,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:'\u{1f9d5}',fitzpatrick_scale:!0,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:'\u{1f473}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:'\u{1f473}',fitzpatrick_scale:!0,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:'\u{1f46e}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:'\u{1f46e}',fitzpatrick_scale:!0,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:'\u{1f477}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:'\u{1f477}',fitzpatrick_scale:!0,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:'\u{1f482}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:'\u{1f482}',fitzpatrick_scale:!0,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:'\u{1f575}\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},male_detective:{keywords:["human","spy","detective"],char:'\u{1f575}',fitzpatrick_scale:!0,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:'\u{1f469}\u200d\u2695\ufe0f',fitzpatrick_scale:!0,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:'\u{1f468}\u200d\u2695\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:'\u{1f469}\u200d\u{1f33e}',fitzpatrick_scale:!0,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:'\u{1f468}\u200d\u{1f33e}',fitzpatrick_scale:!0,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:'\u{1f469}\u200d\u{1f373}',fitzpatrick_scale:!0,category:"people"},man_cook:{keywords:["chef","man","human"],char:'\u{1f468}\u200d\u{1f373}',fitzpatrick_scale:!0,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:'\u{1f469}\u200d\u{1f393}',fitzpatrick_scale:!0,category:"people"},man_student:{keywords:["graduate","man","human"],char:'\u{1f468}\u200d\u{1f393}',fitzpatrick_scale:!0,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:'\u{1f469}\u200d\u{1f3a4}',fitzpatrick_scale:!0,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:'\u{1f468}\u200d\u{1f3a4}',fitzpatrick_scale:!0,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:'\u{1f469}\u200d\u{1f3eb}',fitzpatrick_scale:!0,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:'\u{1f468}\u200d\u{1f3eb}',fitzpatrick_scale:!0,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:'\u{1f469}\u200d\u{1f3ed}',fitzpatrick_scale:!0,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:'\u{1f468}\u200d\u{1f3ed}',fitzpatrick_scale:!0,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:'\u{1f469}\u200d\u{1f4bb}',fitzpatrick_scale:!0,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:'\u{1f468}\u200d\u{1f4bb}',fitzpatrick_scale:!0,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:'\u{1f469}\u200d\u{1f4bc}',fitzpatrick_scale:!0,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:'\u{1f468}\u200d\u{1f4bc}',fitzpatrick_scale:!0,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:'\u{1f469}\u200d\u{1f527}',fitzpatrick_scale:!0,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:'\u{1f468}\u200d\u{1f527}',fitzpatrick_scale:!0,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:'\u{1f469}\u200d\u{1f52c}',fitzpatrick_scale:!0,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:'\u{1f468}\u200d\u{1f52c}',fitzpatrick_scale:!0,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:'\u{1f469}\u200d\u{1f3a8}',fitzpatrick_scale:!0,category:"people"},man_artist:{keywords:["painter","man","human"],char:'\u{1f468}\u200d\u{1f3a8}',fitzpatrick_scale:!0,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:'\u{1f469}\u200d\u{1f692}',fitzpatrick_scale:!0,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:'\u{1f468}\u200d\u{1f692}',fitzpatrick_scale:!0,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:'\u{1f469}\u200d\u2708\ufe0f',fitzpatrick_scale:!0,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:'\u{1f468}\u200d\u2708\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:'\u{1f469}\u200d\u{1f680}',fitzpatrick_scale:!0,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:'\u{1f468}\u200d\u{1f680}',fitzpatrick_scale:!0,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:'\u{1f469}\u200d\u2696\ufe0f',fitzpatrick_scale:!0,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:'\u{1f468}\u200d\u2696\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:'\u{1f9b8}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:'\u{1f9b8}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:'\u{1f9b9}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:'\u{1f9b9}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:'\u{1f936}',fitzpatrick_scale:!0,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:'\u{1f385}',fitzpatrick_scale:!0,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:'\u{1f9d9}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:'\u{1f9d9}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_elf:{keywords:["woman","female"],char:'\u{1f9dd}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_elf:{keywords:["man","male"],char:'\u{1f9dd}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_vampire:{keywords:["woman","female"],char:'\u{1f9db}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:'\u{1f9db}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:'\u{1f9df}\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:'\u{1f9df}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"people"},woman_genie:{keywords:["woman","female"],char:'\u{1f9de}\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"people"},man_genie:{keywords:["man","male"],char:'\u{1f9de}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:'\u{1f9dc}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},merman:{keywords:["man","male","triton"],char:'\u{1f9dc}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_fairy:{keywords:["woman","female"],char:'\u{1f9da}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_fairy:{keywords:["man","male"],char:'\u{1f9da}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},angel:{keywords:["heaven","wings","halo"],char:'\u{1f47c}',fitzpatrick_scale:!0,category:"people"},pregnant_woman:{keywords:["baby"],char:'\u{1f930}',fitzpatrick_scale:!0,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:'\u{1f931}',fitzpatrick_scale:!0,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:'\u{1f478}',fitzpatrick_scale:!0,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:'\u{1f934}',fitzpatrick_scale:!0,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:'\u{1f470}',fitzpatrick_scale:!0,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:'\u{1f935}',fitzpatrick_scale:!0,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:'\u{1f3c3}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:'\u{1f3c3}',fitzpatrick_scale:!0,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:'\u{1f6b6}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},walking_man:{keywords:["human","feet","steps"],char:'\u{1f6b6}',fitzpatrick_scale:!0,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:'\u{1f483}',fitzpatrick_scale:!0,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:'\u{1f57a}',fitzpatrick_scale:!0,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:'\u{1f46f}',fitzpatrick_scale:!1,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:'\u{1f46f}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:'\u{1f46b}',fitzpatrick_scale:!1,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:'\u{1f46c}',fitzpatrick_scale:!1,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:'\u{1f46d}',fitzpatrick_scale:!1,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:'\u{1f647}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},bowing_man:{keywords:["man","male","boy"],char:'\u{1f647}',fitzpatrick_scale:!0,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:'\u{1f926}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:'\u{1f926}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:'\u{1f937}',fitzpatrick_scale:!0,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:'\u{1f937}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:'\u{1f481}',fitzpatrick_scale:!0,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:'\u{1f481}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:'\u{1f645}',fitzpatrick_scale:!0,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:'\u{1f645}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:'\u{1f646}',fitzpatrick_scale:!0,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:'\u{1f646}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:'\u{1f64b}',fitzpatrick_scale:!0,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:'\u{1f64b}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:'\u{1f64e}',fitzpatrick_scale:!0,category:"people"},pouting_man:{keywords:["male","boy","man"],char:'\u{1f64e}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:'\u{1f64d}',fitzpatrick_scale:!0,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:'\u{1f64d}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:'\u{1f487}',fitzpatrick_scale:!0,category:"people"},haircut_man:{keywords:["male","boy","man"],char:'\u{1f487}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:'\u{1f486}',fitzpatrick_scale:!0,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:'\u{1f486}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:'\u{1f9d6}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:'\u{1f9d6}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'\u{1f491}',fitzpatrick_scale:!1,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f469}',fitzpatrick_scale:!1,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f468}',fitzpatrick_scale:!1,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'\u{1f48f}',fitzpatrick_scale:!1,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f469}',fitzpatrick_scale:!1,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:'\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f468}',fitzpatrick_scale:!1,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:'\u{1f46a}',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:'\u{1f469}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:'\u{1f469}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f469}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f469}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:'\u{1f469}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:'\u{1f468}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:'\u{1f468}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f468}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f468}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:'\u{1f468}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:'\u{1f9f6}',fitzpatrick_scale:!1,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:'\u{1f9f5}',fitzpatrick_scale:!1,category:"people"},coat:{keywords:["jacket"],char:'\u{1f9e5}',fitzpatrick_scale:!1,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:'\u{1f97c}',fitzpatrick_scale:!1,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:'\u{1f45a}',fitzpatrick_scale:!1,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:'\u{1f455}',fitzpatrick_scale:!1,category:"people"},jeans:{keywords:["fashion","shopping"],char:'\u{1f456}',fitzpatrick_scale:!1,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:'\u{1f454}',fitzpatrick_scale:!1,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:'\u{1f457}',fitzpatrick_scale:!1,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:'\u{1f459}',fitzpatrick_scale:!1,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:'\u{1f458}',fitzpatrick_scale:!1,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:'\u{1f484}',fitzpatrick_scale:!1,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:'\u{1f48b}',fitzpatrick_scale:!1,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:'\u{1f463}',fitzpatrick_scale:!1,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:'\u{1f97f}',fitzpatrick_scale:!1,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:'\u{1f460}',fitzpatrick_scale:!1,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:'\u{1f461}',fitzpatrick_scale:!1,category:"people"},boot:{keywords:["shoes","fashion"],char:'\u{1f462}',fitzpatrick_scale:!1,category:"people"},mans_shoe:{keywords:["fashion","male"],char:'\u{1f45e}',fitzpatrick_scale:!1,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:'\u{1f45f}',fitzpatrick_scale:!1,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:'\u{1f97e}',fitzpatrick_scale:!1,category:"people"},socks:{keywords:["stockings","clothes"],char:'\u{1f9e6}',fitzpatrick_scale:!1,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:'\u{1f9e4}',fitzpatrick_scale:!1,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:'\u{1f9e3}',fitzpatrick_scale:!1,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:'\u{1f452}',fitzpatrick_scale:!1,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:'\u{1f3a9}',fitzpatrick_scale:!1,category:"people"},billed_hat:{keywords:["cap","baseball"],char:'\u{1f9e2}',fitzpatrick_scale:!1,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:'\u26d1',fitzpatrick_scale:!1,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:'\u{1f393}',fitzpatrick_scale:!1,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:'\u{1f451}',fitzpatrick_scale:!1,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:'\u{1f392}',fitzpatrick_scale:!1,category:"people"},luggage:{keywords:["packing","travel"],char:'\u{1f9f3}',fitzpatrick_scale:!1,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:'\u{1f45d}',fitzpatrick_scale:!1,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:'\u{1f45b}',fitzpatrick_scale:!1,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:'\u{1f45c}',fitzpatrick_scale:!1,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:'\u{1f4bc}',fitzpatrick_scale:!1,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:'\u{1f453}',fitzpatrick_scale:!1,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:'\u{1f576}',fitzpatrick_scale:!1,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:'\u{1f97d}',fitzpatrick_scale:!1,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:'\u{1f48d}',fitzpatrick_scale:!1,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:'\u{1f302}',fitzpatrick_scale:!1,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:'\u{1f436}',fitzpatrick_scale:!1,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:'\u{1f431}',fitzpatrick_scale:!1,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:'\u{1f42d}',fitzpatrick_scale:!1,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:'\u{1f439}',fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:'\u{1f430}',fitzpatrick_scale:!1,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:'\u{1f98a}',fitzpatrick_scale:!1,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:'\u{1f43b}',fitzpatrick_scale:!1,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:'\u{1f43c}',fitzpatrick_scale:!1,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:'\u{1f428}',fitzpatrick_scale:!1,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:'\u{1f42f}',fitzpatrick_scale:!1,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:'\u{1f981}',fitzpatrick_scale:!1,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:'\u{1f42e}',fitzpatrick_scale:!1,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:'\u{1f437}',fitzpatrick_scale:!1,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:'\u{1f43d}',fitzpatrick_scale:!1,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:'\u{1f438}',fitzpatrick_scale:!1,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:'\u{1f991}',fitzpatrick_scale:!1,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:'\u{1f419}',fitzpatrick_scale:!1,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:'\u{1f990}',fitzpatrick_scale:!1,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:'\u{1f435}',fitzpatrick_scale:!1,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:'\u{1f98d}',fitzpatrick_scale:!1,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:'\u{1f648}',fitzpatrick_scale:!1,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:'\u{1f649}',fitzpatrick_scale:!1,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:'\u{1f64a}',fitzpatrick_scale:!1,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:'\u{1f412}',fitzpatrick_scale:!1,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:'\u{1f414}',fitzpatrick_scale:!1,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:'\u{1f427}',fitzpatrick_scale:!1,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:'\u{1f426}',fitzpatrick_scale:!1,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:'\u{1f424}',fitzpatrick_scale:!1,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:'\u{1f423}',fitzpatrick_scale:!1,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:'\u{1f425}',fitzpatrick_scale:!1,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:'\u{1f986}',fitzpatrick_scale:!1,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:'\u{1f985}',fitzpatrick_scale:!1,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:'\u{1f989}',fitzpatrick_scale:!1,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:'\u{1f987}',fitzpatrick_scale:!1,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:'\u{1f43a}',fitzpatrick_scale:!1,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:'\u{1f417}',fitzpatrick_scale:!1,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:'\u{1f434}',fitzpatrick_scale:!1,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:'\u{1f984}',fitzpatrick_scale:!1,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:'\u{1f41d}',fitzpatrick_scale:!1,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:'\u{1f41b}',fitzpatrick_scale:!1,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:'\u{1f98b}',fitzpatrick_scale:!1,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:'\u{1f40c}',fitzpatrick_scale:!1,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:'\u{1f41e}',fitzpatrick_scale:!1,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:'\u{1f41c}',fitzpatrick_scale:!1,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:'\u{1f997}',fitzpatrick_scale:!1,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:'\u{1f577}',fitzpatrick_scale:!1,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:'\u{1f982}',fitzpatrick_scale:!1,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:'\u{1f980}',fitzpatrick_scale:!1,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:'\u{1f40d}',fitzpatrick_scale:!1,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:'\u{1f98e}',fitzpatrick_scale:!1,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:'\u{1f996}',fitzpatrick_scale:!1,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:'\u{1f995}',fitzpatrick_scale:!1,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:'\u{1f422}',fitzpatrick_scale:!1,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:'\u{1f420}',fitzpatrick_scale:!1,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:'\u{1f41f}',fitzpatrick_scale:!1,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:'\u{1f421}',fitzpatrick_scale:!1,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:'\u{1f42c}',fitzpatrick_scale:!1,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:'\u{1f988}',fitzpatrick_scale:!1,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:'\u{1f433}',fitzpatrick_scale:!1,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:'\u{1f40b}',fitzpatrick_scale:!1,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:'\u{1f40a}',fitzpatrick_scale:!1,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:'\u{1f406}',fitzpatrick_scale:!1,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:'\u{1f993}',fitzpatrick_scale:!1,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:'\u{1f405}',fitzpatrick_scale:!1,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:'\u{1f403}',fitzpatrick_scale:!1,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:'\u{1f402}',fitzpatrick_scale:!1,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:'\u{1f404}',fitzpatrick_scale:!1,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:'\u{1f98c}',fitzpatrick_scale:!1,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:'\u{1f42a}',fitzpatrick_scale:!1,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:'\u{1f42b}',fitzpatrick_scale:!1,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:'\u{1f992}',fitzpatrick_scale:!1,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:'\u{1f418}',fitzpatrick_scale:!1,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:'\u{1f98f}',fitzpatrick_scale:!1,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:'\u{1f410}',fitzpatrick_scale:!1,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:'\u{1f40f}',fitzpatrick_scale:!1,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:'\u{1f411}',fitzpatrick_scale:!1,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:'\u{1f40e}',fitzpatrick_scale:!1,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:'\u{1f416}',fitzpatrick_scale:!1,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:'\u{1f400}',fitzpatrick_scale:!1,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:'\u{1f401}',fitzpatrick_scale:!1,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:'\u{1f413}',fitzpatrick_scale:!1,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:'\u{1f983}',fitzpatrick_scale:!1,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:'\u{1f54a}',fitzpatrick_scale:!1,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:'\u{1f415}',fitzpatrick_scale:!1,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:'\u{1f429}',fitzpatrick_scale:!1,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:'\u{1f408}',fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:'\u{1f407}',fitzpatrick_scale:!1,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:'\u{1f43f}',fitzpatrick_scale:!1,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:'\u{1f994}',fitzpatrick_scale:!1,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:'\u{1f99d}',fitzpatrick_scale:!1,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:'\u{1f999}',fitzpatrick_scale:!1,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:'\u{1f99b}',fitzpatrick_scale:!1,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:'\u{1f998}',fitzpatrick_scale:!1,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:'\u{1f9a1}',fitzpatrick_scale:!1,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:'\u{1f9a2}',fitzpatrick_scale:!1,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:'\u{1f99a}',fitzpatrick_scale:!1,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:'\u{1f99c}',fitzpatrick_scale:!1,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:'\u{1f99e}',fitzpatrick_scale:!1,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:'\u{1f99f}',fitzpatrick_scale:!1,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:'\u{1f43e}',fitzpatrick_scale:!1,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:'\u{1f409}',fitzpatrick_scale:!1,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:'\u{1f432}',fitzpatrick_scale:!1,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:'\u{1f335}',fitzpatrick_scale:!1,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:'\u{1f384}',fitzpatrick_scale:!1,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:'\u{1f332}',fitzpatrick_scale:!1,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:'\u{1f333}',fitzpatrick_scale:!1,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:'\u{1f334}',fitzpatrick_scale:!1,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:'\u{1f331}',fitzpatrick_scale:!1,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:'\u{1f33f}',fitzpatrick_scale:!1,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:'\u2618',fitzpatrick_scale:!1,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:'\u{1f340}',fitzpatrick_scale:!1,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:'\u{1f38d}',fitzpatrick_scale:!1,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:'\u{1f38b}',fitzpatrick_scale:!1,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:'\u{1f343}',fitzpatrick_scale:!1,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:'\u{1f342}',fitzpatrick_scale:!1,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:'\u{1f341}',fitzpatrick_scale:!1,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:'\u{1f33e}',fitzpatrick_scale:!1,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:'\u{1f33a}',fitzpatrick_scale:!1,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:'\u{1f33b}',fitzpatrick_scale:!1,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:'\u{1f339}',fitzpatrick_scale:!1,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:'\u{1f940}',fitzpatrick_scale:!1,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:'\u{1f337}',fitzpatrick_scale:!1,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:'\u{1f33c}',fitzpatrick_scale:!1,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:'\u{1f338}',fitzpatrick_scale:!1,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:'\u{1f490}',fitzpatrick_scale:!1,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:'\u{1f344}',fitzpatrick_scale:!1,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:'\u{1f330}',fitzpatrick_scale:!1,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:'\u{1f383}',fitzpatrick_scale:!1,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:'\u{1f41a}',fitzpatrick_scale:!1,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:'\u{1f578}',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:'\u{1f30e}',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:'\u{1f30d}',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:'\u{1f30f}',fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:'\u{1f315}',fitzpatrick_scale:!1,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:'\u{1f316}',fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f317}',fitzpatrick_scale:!1,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f318}',fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f311}',fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f312}',fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f313}',fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:'\u{1f314}',fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31a}',fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31d}',fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31b}',fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31c}',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:'\u{1f31e}',fitzpatrick_scale:!1,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:'\u{1f319}',fitzpatrick_scale:!1,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:'\u2b50',fitzpatrick_scale:!1,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:'\u{1f31f}',fitzpatrick_scale:!1,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:'\u{1f4ab}',fitzpatrick_scale:!1,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:'\u2728',fitzpatrick_scale:!1,category:"animals_and_nature"},comet:{keywords:["space"],char:'\u2604',fitzpatrick_scale:!1,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:'\u2600\ufe0f',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:'\u{1f324}',fitzpatrick_scale:!1,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:'\u26c5',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:'\u{1f325}',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:'\u{1f326}',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:'\u2601\ufe0f',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:'\u{1f327}',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:'\u26c8',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:'\u{1f329}',fitzpatrick_scale:!1,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:'\u26a1',fitzpatrick_scale:!1,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:'\u{1f525}',fitzpatrick_scale:!1,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:'\u{1f4a5}',fitzpatrick_scale:!1,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:'\u2744\ufe0f',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:'\u{1f328}',fitzpatrick_scale:!1,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:'\u26c4',fitzpatrick_scale:!1,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:'\u2603',fitzpatrick_scale:!1,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:'\u{1f32c}',fitzpatrick_scale:!1,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:'\u{1f4a8}',fitzpatrick_scale:!1,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:'\u{1f32a}',fitzpatrick_scale:!1,category:"animals_and_nature"},fog:{keywords:["weather"],char:'\u{1f32b}',fitzpatrick_scale:!1,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:'\u2602',fitzpatrick_scale:!1,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:'\u2614',fitzpatrick_scale:!1,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:'\u{1f4a7}',fitzpatrick_scale:!1,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:'\u{1f4a6}',fitzpatrick_scale:!1,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:'\u{1f30a}',fitzpatrick_scale:!1,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:'\u{1f34f}',fitzpatrick_scale:!1,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:'\u{1f34e}',fitzpatrick_scale:!1,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:'\u{1f350}',fitzpatrick_scale:!1,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:'\u{1f34a}',fitzpatrick_scale:!1,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:'\u{1f34b}',fitzpatrick_scale:!1,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:'\u{1f34c}',fitzpatrick_scale:!1,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:'\u{1f349}',fitzpatrick_scale:!1,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:'\u{1f347}',fitzpatrick_scale:!1,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:'\u{1f353}',fitzpatrick_scale:!1,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:'\u{1f348}',fitzpatrick_scale:!1,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:'\u{1f352}',fitzpatrick_scale:!1,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:'\u{1f351}',fitzpatrick_scale:!1,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:'\u{1f34d}',fitzpatrick_scale:!1,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:'\u{1f965}',fitzpatrick_scale:!1,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:'\u{1f95d}',fitzpatrick_scale:!1,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:'\u{1f96d}',fitzpatrick_scale:!1,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:'\u{1f951}',fitzpatrick_scale:!1,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:'\u{1f966}',fitzpatrick_scale:!1,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:'\u{1f345}',fitzpatrick_scale:!1,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:'\u{1f346}',fitzpatrick_scale:!1,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:'\u{1f952}',fitzpatrick_scale:!1,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:'\u{1f955}',fitzpatrick_scale:!1,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:'\u{1f336}',fitzpatrick_scale:!1,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:'\u{1f954}',fitzpatrick_scale:!1,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:'\u{1f33d}',fitzpatrick_scale:!1,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:'\u{1f96c}',fitzpatrick_scale:!1,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:'\u{1f360}',fitzpatrick_scale:!1,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:'\u{1f95c}',fitzpatrick_scale:!1,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:'\u{1f36f}',fitzpatrick_scale:!1,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:'\u{1f950}',fitzpatrick_scale:!1,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:'\u{1f35e}',fitzpatrick_scale:!1,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:'\u{1f956}',fitzpatrick_scale:!1,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:'\u{1f96f}',fitzpatrick_scale:!1,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:'\u{1f968}',fitzpatrick_scale:!1,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:'\u{1f9c0}',fitzpatrick_scale:!1,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:'\u{1f95a}',fitzpatrick_scale:!1,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:'\u{1f953}',fitzpatrick_scale:!1,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:'\u{1f969}',fitzpatrick_scale:!1,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:'\u{1f95e}',fitzpatrick_scale:!1,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:'\u{1f357}',fitzpatrick_scale:!1,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:'\u{1f356}',fitzpatrick_scale:!1,category:"food_and_drink"},bone:{keywords:["skeleton"],char:'\u{1f9b4}',fitzpatrick_scale:!1,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:'\u{1f364}',fitzpatrick_scale:!1,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:'\u{1f373}',fitzpatrick_scale:!1,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:'\u{1f354}',fitzpatrick_scale:!1,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:'\u{1f35f}',fitzpatrick_scale:!1,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:'\u{1f959}',fitzpatrick_scale:!1,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:'\u{1f32d}',fitzpatrick_scale:!1,category:"food_and_drink"},pizza:{keywords:["food","party"],char:'\u{1f355}',fitzpatrick_scale:!1,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:'\u{1f96a}',fitzpatrick_scale:!1,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:'\u{1f96b}',fitzpatrick_scale:!1,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:'\u{1f35d}',fitzpatrick_scale:!1,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:'\u{1f32e}',fitzpatrick_scale:!1,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:'\u{1f32f}',fitzpatrick_scale:!1,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:'\u{1f957}',fitzpatrick_scale:!1,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:'\u{1f958}',fitzpatrick_scale:!1,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:'\u{1f35c}',fitzpatrick_scale:!1,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:'\u{1f372}',fitzpatrick_scale:!1,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:'\u{1f365}',fitzpatrick_scale:!1,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:'\u{1f960}',fitzpatrick_scale:!1,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:'\u{1f363}',fitzpatrick_scale:!1,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:'\u{1f371}',fitzpatrick_scale:!1,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:'\u{1f35b}',fitzpatrick_scale:!1,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:'\u{1f359}',fitzpatrick_scale:!1,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:'\u{1f35a}',fitzpatrick_scale:!1,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:'\u{1f358}',fitzpatrick_scale:!1,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:'\u{1f362}',fitzpatrick_scale:!1,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:'\u{1f361}',fitzpatrick_scale:!1,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:'\u{1f367}',fitzpatrick_scale:!1,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:'\u{1f368}',fitzpatrick_scale:!1,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:'\u{1f366}',fitzpatrick_scale:!1,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:'\u{1f967}',fitzpatrick_scale:!1,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:'\u{1f370}',fitzpatrick_scale:!1,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:'\u{1f9c1}',fitzpatrick_scale:!1,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:'\u{1f96e}',fitzpatrick_scale:!1,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:'\u{1f382}',fitzpatrick_scale:!1,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:'\u{1f36e}',fitzpatrick_scale:!1,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:'\u{1f36c}',fitzpatrick_scale:!1,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:'\u{1f36d}',fitzpatrick_scale:!1,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:'\u{1f36b}',fitzpatrick_scale:!1,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:'\u{1f37f}',fitzpatrick_scale:!1,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:'\u{1f95f}',fitzpatrick_scale:!1,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:'\u{1f369}',fitzpatrick_scale:!1,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:'\u{1f36a}',fitzpatrick_scale:!1,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:'\u{1f95b}',fitzpatrick_scale:!1,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'\u{1f37a}',fitzpatrick_scale:!1,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'\u{1f37b}',fitzpatrick_scale:!1,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:'\u{1f942}',fitzpatrick_scale:!1,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:'\u{1f377}',fitzpatrick_scale:!1,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:'\u{1f943}',fitzpatrick_scale:!1,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:'\u{1f378}',fitzpatrick_scale:!1,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:'\u{1f379}',fitzpatrick_scale:!1,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:'\u{1f37e}',fitzpatrick_scale:!1,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:'\u{1f376}',fitzpatrick_scale:!1,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:'\u{1f375}',fitzpatrick_scale:!1,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:'\u{1f964}',fitzpatrick_scale:!1,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:'\u2615',fitzpatrick_scale:!1,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:'\u{1f37c}',fitzpatrick_scale:!1,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:'\u{1f9c2}',fitzpatrick_scale:!1,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:'\u{1f944}',fitzpatrick_scale:!1,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:'\u{1f374}',fitzpatrick_scale:!1,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:'\u{1f37d}',fitzpatrick_scale:!1,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:'\u{1f963}',fitzpatrick_scale:!1,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:'\u{1f961}',fitzpatrick_scale:!1,category:"food_and_drink"},chopsticks:{keywords:["food"],char:'\u{1f962}',fitzpatrick_scale:!1,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:'\u26bd',fitzpatrick_scale:!1,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:'\u{1f3c0}',fitzpatrick_scale:!1,category:"activity"},football:{keywords:["sports","balls","NFL"],char:'\u{1f3c8}',fitzpatrick_scale:!1,category:"activity"},baseball:{keywords:["sports","balls"],char:'\u26be',fitzpatrick_scale:!1,category:"activity"},softball:{keywords:["sports","balls"],char:'\u{1f94e}',fitzpatrick_scale:!1,category:"activity"},tennis:{keywords:["sports","balls","green"],char:'\u{1f3be}',fitzpatrick_scale:!1,category:"activity"},volleyball:{keywords:["sports","balls"],char:'\u{1f3d0}',fitzpatrick_scale:!1,category:"activity"},rugby_football:{keywords:["sports","team"],char:'\u{1f3c9}',fitzpatrick_scale:!1,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:'\u{1f94f}',fitzpatrick_scale:!1,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:'\u{1f3b1}',fitzpatrick_scale:!1,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:'\u26f3',fitzpatrick_scale:!1,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:'\u{1f3cc}\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"activity"},golfing_man:{keywords:["sports","business"],char:'\u{1f3cc}',fitzpatrick_scale:!0,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:'\u{1f3d3}',fitzpatrick_scale:!1,category:"activity"},badminton:{keywords:["sports"],char:'\u{1f3f8}',fitzpatrick_scale:!1,category:"activity"},goal_net:{keywords:["sports"],char:'\u{1f945}',fitzpatrick_scale:!1,category:"activity"},ice_hockey:{keywords:["sports"],char:'\u{1f3d2}',fitzpatrick_scale:!1,category:"activity"},field_hockey:{keywords:["sports"],char:'\u{1f3d1}',fitzpatrick_scale:!1,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:'\u{1f94d}',fitzpatrick_scale:!1,category:"activity"},cricket:{keywords:["sports"],char:'\u{1f3cf}',fitzpatrick_scale:!1,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:'\u{1f3bf}',fitzpatrick_scale:!1,category:"activity"},skier:{keywords:["sports","winter","snow"],char:'\u26f7',fitzpatrick_scale:!1,category:"activity"},snowboarder:{keywords:["sports","winter"],char:'\u{1f3c2}',fitzpatrick_scale:!0,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:'\u{1f93a}',fitzpatrick_scale:!1,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:'\u{1f93c}\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:'\u{1f93c}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:'\u{1f938}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:'\u{1f938}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},woman_playing_handball:{keywords:["sports"],char:'\u{1f93e}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_playing_handball:{keywords:["sports"],char:'\u{1f93e}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},ice_skate:{keywords:["sports"],char:'\u26f8',fitzpatrick_scale:!1,category:"activity"},curling_stone:{keywords:["sports"],char:'\u{1f94c}',fitzpatrick_scale:!1,category:"activity"},skateboard:{keywords:["board"],char:'\u{1f6f9}',fitzpatrick_scale:!1,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:'\u{1f6f7}',fitzpatrick_scale:!1,category:"activity"},bow_and_arrow:{keywords:["sports"],char:'\u{1f3f9}',fitzpatrick_scale:!1,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:'\u{1f3a3}',fitzpatrick_scale:!1,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:'\u{1f94a}',fitzpatrick_scale:!1,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:'\u{1f94b}',fitzpatrick_scale:!1,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:'\u{1f6a3}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:'\u{1f6a3}',fitzpatrick_scale:!0,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:'\u{1f9d7}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:'\u{1f9d7}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:'\u{1f3ca}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:'\u{1f3ca}',fitzpatrick_scale:!0,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:'\u{1f93d}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:'\u{1f93d}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:'\u{1f9d8}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:'\u{1f9d8}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:'\u{1f3c4}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:'\u{1f3c4}',fitzpatrick_scale:!0,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:'\u{1f6c0}',fitzpatrick_scale:!0,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:'\u26f9\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},basketball_man:{keywords:["sports","human"],char:'\u26f9',fitzpatrick_scale:!0,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:'\u{1f3cb}\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:'\u{1f3cb}',fitzpatrick_scale:!0,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:'\u{1f6b4}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:'\u{1f6b4}',fitzpatrick_scale:!0,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:'\u{1f6b5}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:'\u{1f6b5}',fitzpatrick_scale:!0,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:'\u{1f3c7}',fitzpatrick_scale:!0,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:'\u{1f574}',fitzpatrick_scale:!0,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:'\u{1f3c6}',fitzpatrick_scale:!1,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:'\u{1f3bd}',fitzpatrick_scale:!1,category:"activity"},medal_sports:{keywords:["award","winning"],char:'\u{1f3c5}',fitzpatrick_scale:!1,category:"activity"},medal_military:{keywords:["award","winning","army"],char:'\u{1f396}',fitzpatrick_scale:!1,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:'\u{1f947}',fitzpatrick_scale:!1,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:'\u{1f948}',fitzpatrick_scale:!1,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:'\u{1f949}',fitzpatrick_scale:!1,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:'\u{1f397}',fitzpatrick_scale:!1,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:'\u{1f3f5}',fitzpatrick_scale:!1,category:"activity"},ticket:{keywords:["event","concert","pass"],char:'\u{1f3ab}',fitzpatrick_scale:!1,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:'\u{1f39f}',fitzpatrick_scale:!1,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:'\u{1f3ad}',fitzpatrick_scale:!1,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:'\u{1f3a8}',fitzpatrick_scale:!1,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:'\u{1f3aa}',fitzpatrick_scale:!1,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:'\u{1f939}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:'\u{1f939}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:'\u{1f3a4}',fitzpatrick_scale:!1,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:'\u{1f3a7}',fitzpatrick_scale:!1,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:'\u{1f3bc}',fitzpatrick_scale:!1,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:'\u{1f3b9}',fitzpatrick_scale:!1,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:'\u{1f941}',fitzpatrick_scale:!1,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:'\u{1f3b7}',fitzpatrick_scale:!1,category:"activity"},trumpet:{keywords:["music","brass"],char:'\u{1f3ba}',fitzpatrick_scale:!1,category:"activity"},guitar:{keywords:["music","instrument"],char:'\u{1f3b8}',fitzpatrick_scale:!1,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:'\u{1f3bb}',fitzpatrick_scale:!1,category:"activity"},clapper:{keywords:["movie","film","record"],char:'\u{1f3ac}',fitzpatrick_scale:!1,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:'\u{1f3ae}',fitzpatrick_scale:!1,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:'\u{1f47e}',fitzpatrick_scale:!1,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:'\u{1f3af}',fitzpatrick_scale:!1,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:'\u{1f3b2}',fitzpatrick_scale:!1,category:"activity"},chess_pawn:{keywords:["expendable"],char:"\u265f",fitzpatrick_scale:!1,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:'\u{1f3b0}',fitzpatrick_scale:!1,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:'\u{1f9e9}',fitzpatrick_scale:!1,category:"activity"},bowling:{keywords:["sports","fun","play"],char:'\u{1f3b3}',fitzpatrick_scale:!1,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:'\u{1f697}',fitzpatrick_scale:!1,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:'\u{1f695}',fitzpatrick_scale:!1,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:'\u{1f699}',fitzpatrick_scale:!1,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:'\u{1f68c}',fitzpatrick_scale:!1,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:'\u{1f68e}',fitzpatrick_scale:!1,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:'\u{1f3ce}',fitzpatrick_scale:!1,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:'\u{1f693}',fitzpatrick_scale:!1,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:'\u{1f691}',fitzpatrick_scale:!1,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:'\u{1f692}',fitzpatrick_scale:!1,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:'\u{1f690}',fitzpatrick_scale:!1,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:'\u{1f69a}',fitzpatrick_scale:!1,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:'\u{1f69b}',fitzpatrick_scale:!1,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:'\u{1f69c}',fitzpatrick_scale:!1,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:'\u{1f6f4}',fitzpatrick_scale:!1,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:'\u{1f3cd}',fitzpatrick_scale:!1,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:'\u{1f6b2}',fitzpatrick_scale:!1,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:'\u{1f6f5}',fitzpatrick_scale:!1,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:'\u{1f6a8}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:'\u{1f694}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:'\u{1f68d}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:'\u{1f698}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:'\u{1f696}',fitzpatrick_scale:!1,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:'\u{1f6a1}',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:'\u{1f6a0}',fitzpatrick_scale:!1,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:'\u{1f69f}',fitzpatrick_scale:!1,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:'\u{1f683}',fitzpatrick_scale:!1,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:'\u{1f68b}',fitzpatrick_scale:!1,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:'\u{1f69d}',fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:'\u{1f684}',fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:'\u{1f685}',fitzpatrick_scale:!1,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:'\u{1f688}',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:'\u{1f69e}',fitzpatrick_scale:!1,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:'\u{1f682}',fitzpatrick_scale:!1,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:'\u{1f686}',fitzpatrick_scale:!1,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:'\u{1f687}',fitzpatrick_scale:!1,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:'\u{1f68a}',fitzpatrick_scale:!1,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:'\u{1f689}',fitzpatrick_scale:!1,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:'\u{1f6f8}',fitzpatrick_scale:!1,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:'\u{1f681}',fitzpatrick_scale:!1,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:'\u{1f6e9}',fitzpatrick_scale:!1,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:'\u2708\ufe0f',fitzpatrick_scale:!1,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:'\u{1f6eb}',fitzpatrick_scale:!1,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:'\u{1f6ec}',fitzpatrick_scale:!1,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:'\u26f5',fitzpatrick_scale:!1,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:'\u{1f6e5}',fitzpatrick_scale:!1,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:'\u{1f6a4}',fitzpatrick_scale:!1,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:'\u26f4',fitzpatrick_scale:!1,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:'\u{1f6f3}',fitzpatrick_scale:!1,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:'\u{1f680}',fitzpatrick_scale:!1,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:'\u{1f6f0}',fitzpatrick_scale:!1,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:'\u{1f4ba}',fitzpatrick_scale:!1,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:'\u{1f6f6}',fitzpatrick_scale:!1,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:'\u2693',fitzpatrick_scale:!1,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:'\u{1f6a7}',fitzpatrick_scale:!1,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:'\u26fd',fitzpatrick_scale:!1,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:'\u{1f68f}',fitzpatrick_scale:!1,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:'\u{1f6a6}',fitzpatrick_scale:!1,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:'\u{1f6a5}',fitzpatrick_scale:!1,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:'\u{1f3c1}',fitzpatrick_scale:!1,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:'\u{1f6a2}',fitzpatrick_scale:!1,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:'\u{1f3a1}',fitzpatrick_scale:!1,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:'\u{1f3a2}',fitzpatrick_scale:!1,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:'\u{1f3a0}',fitzpatrick_scale:!1,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:'\u{1f3d7}',fitzpatrick_scale:!1,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:'\u{1f301}',fitzpatrick_scale:!1,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:'\u{1f5fc}',fitzpatrick_scale:!1,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:'\u{1f3ed}',fitzpatrick_scale:!1,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:'\u26f2',fitzpatrick_scale:!1,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:'\u{1f391}',fitzpatrick_scale:!1,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:'\u26f0',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:'\u{1f3d4}',fitzpatrick_scale:!1,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:'\u{1f5fb}',fitzpatrick_scale:!1,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:'\u{1f30b}',fitzpatrick_scale:!1,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:'\u{1f5fe}',fitzpatrick_scale:!1,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:'\u{1f3d5}',fitzpatrick_scale:!1,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:'\u26fa',fitzpatrick_scale:!1,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:'\u{1f3de}',fitzpatrick_scale:!1,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:'\u{1f6e3}',fitzpatrick_scale:!1,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:'\u{1f6e4}',fitzpatrick_scale:!1,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:'\u{1f305}',fitzpatrick_scale:!1,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:'\u{1f304}',fitzpatrick_scale:!1,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:'\u{1f3dc}',fitzpatrick_scale:!1,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:'\u{1f3d6}',fitzpatrick_scale:!1,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:'\u{1f3dd}',fitzpatrick_scale:!1,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:'\u{1f307}',fitzpatrick_scale:!1,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:'\u{1f306}',fitzpatrick_scale:!1,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:'\u{1f3d9}',fitzpatrick_scale:!1,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:'\u{1f303}',fitzpatrick_scale:!1,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:'\u{1f309}',fitzpatrick_scale:!1,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:'\u{1f30c}',fitzpatrick_scale:!1,category:"travel_and_places"},stars:{keywords:["night","photo"],char:'\u{1f320}',fitzpatrick_scale:!1,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:'\u{1f387}',fitzpatrick_scale:!1,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:'\u{1f386}',fitzpatrick_scale:!1,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:'\u{1f308}',fitzpatrick_scale:!1,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:'\u{1f3d8}',fitzpatrick_scale:!1,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:'\u{1f3f0}',fitzpatrick_scale:!1,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:'\u{1f3ef}',fitzpatrick_scale:!1,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:'\u{1f3df}',fitzpatrick_scale:!1,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:'\u{1f5fd}',fitzpatrick_scale:!1,category:"travel_and_places"},house:{keywords:["building","home"],char:'\u{1f3e0}',fitzpatrick_scale:!1,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:'\u{1f3e1}',fitzpatrick_scale:!1,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:'\u{1f3da}',fitzpatrick_scale:!1,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:'\u{1f3e2}',fitzpatrick_scale:!1,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:'\u{1f3ec}',fitzpatrick_scale:!1,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:'\u{1f3e3}',fitzpatrick_scale:!1,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:'\u{1f3e4}',fitzpatrick_scale:!1,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:'\u{1f3e5}',fitzpatrick_scale:!1,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:'\u{1f3e6}',fitzpatrick_scale:!1,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:'\u{1f3e8}',fitzpatrick_scale:!1,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:'\u{1f3ea}',fitzpatrick_scale:!1,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:'\u{1f3eb}',fitzpatrick_scale:!1,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:'\u{1f3e9}',fitzpatrick_scale:!1,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:'\u{1f492}',fitzpatrick_scale:!1,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:'\u{1f3db}',fitzpatrick_scale:!1,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:'\u26ea',fitzpatrick_scale:!1,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:'\u{1f54c}',fitzpatrick_scale:!1,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:'\u{1f54d}',fitzpatrick_scale:!1,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:'\u{1f54b}',fitzpatrick_scale:!1,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:'\u26e9',fitzpatrick_scale:!1,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:'\u231a',fitzpatrick_scale:!1,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:'\u{1f4f1}',fitzpatrick_scale:!1,category:"objects"},calling:{keywords:["iphone","incoming"],char:'\u{1f4f2}',fitzpatrick_scale:!1,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:'\u{1f4bb}',fitzpatrick_scale:!1,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:'\u2328',fitzpatrick_scale:!1,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:'\u{1f5a5}',fitzpatrick_scale:!1,category:"objects"},printer:{keywords:["paper","ink"],char:'\u{1f5a8}',fitzpatrick_scale:!1,category:"objects"},computer_mouse:{keywords:["click"],char:'\u{1f5b1}',fitzpatrick_scale:!1,category:"objects"},trackball:{keywords:["technology","trackpad"],char:'\u{1f5b2}',fitzpatrick_scale:!1,category:"objects"},joystick:{keywords:["game","play"],char:'\u{1f579}',fitzpatrick_scale:!1,category:"objects"},clamp:{keywords:["tool"],char:'\u{1f5dc}',fitzpatrick_scale:!1,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:'\u{1f4bd}',fitzpatrick_scale:!1,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:'\u{1f4be}',fitzpatrick_scale:!1,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:'\u{1f4bf}',fitzpatrick_scale:!1,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:'\u{1f4c0}',fitzpatrick_scale:!1,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:'\u{1f4fc}',fitzpatrick_scale:!1,category:"objects"},camera:{keywords:["gadgets","photography"],char:'\u{1f4f7}',fitzpatrick_scale:!1,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:'\u{1f4f8}',fitzpatrick_scale:!1,category:"objects"},video_camera:{keywords:["film","record"],char:'\u{1f4f9}',fitzpatrick_scale:!1,category:"objects"},movie_camera:{keywords:["film","record"],char:'\u{1f3a5}',fitzpatrick_scale:!1,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:'\u{1f4fd}',fitzpatrick_scale:!1,category:"objects"},film_strip:{keywords:["movie"],char:'\u{1f39e}',fitzpatrick_scale:!1,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:'\u{1f4de}',fitzpatrick_scale:!1,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:'\u260e\ufe0f',fitzpatrick_scale:!1,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:'\u{1f4df}',fitzpatrick_scale:!1,category:"objects"},fax:{keywords:["communication","technology"],char:'\u{1f4e0}',fitzpatrick_scale:!1,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:'\u{1f4fa}',fitzpatrick_scale:!1,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:'\u{1f4fb}',fitzpatrick_scale:!1,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:'\u{1f399}',fitzpatrick_scale:!1,category:"objects"},level_slider:{keywords:["scale"],char:'\u{1f39a}',fitzpatrick_scale:!1,category:"objects"},control_knobs:{keywords:["dial"],char:'\u{1f39b}',fitzpatrick_scale:!1,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:'\u{1f9ed}',fitzpatrick_scale:!1,category:"objects"},stopwatch:{keywords:["time","deadline"],char:'\u23f1',fitzpatrick_scale:!1,category:"objects"},timer_clock:{keywords:["alarm"],char:'\u23f2',fitzpatrick_scale:!1,category:"objects"},alarm_clock:{keywords:["time","wake"],char:'\u23f0',fitzpatrick_scale:!1,category:"objects"},mantelpiece_clock:{keywords:["time"],char:'\u{1f570}',fitzpatrick_scale:!1,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:'\u23f3',fitzpatrick_scale:!1,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:'\u231b',fitzpatrick_scale:!1,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:'\u{1f4e1}',fitzpatrick_scale:!1,category:"objects"},battery:{keywords:["power","energy","sustain"],char:'\u{1f50b}',fitzpatrick_scale:!1,category:"objects"},electric_plug:{keywords:["charger","power"],char:'\u{1f50c}',fitzpatrick_scale:!1,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:'\u{1f4a1}',fitzpatrick_scale:!1,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:'\u{1f526}',fitzpatrick_scale:!1,category:"objects"},candle:{keywords:["fire","wax"],char:'\u{1f56f}',fitzpatrick_scale:!1,category:"objects"},fire_extinguisher:{keywords:["quench"],char:'\u{1f9ef}',fitzpatrick_scale:!1,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:'\u{1f5d1}',fitzpatrick_scale:!1,category:"objects"},oil_drum:{keywords:["barrell"],char:'\u{1f6e2}',fitzpatrick_scale:!1,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:'\u{1f4b8}',fitzpatrick_scale:!1,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:'\u{1f4b5}',fitzpatrick_scale:!1,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:'\u{1f4b4}',fitzpatrick_scale:!1,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:'\u{1f4b6}',fitzpatrick_scale:!1,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:'\u{1f4b7}',fitzpatrick_scale:!1,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:'\u{1f4b0}',fitzpatrick_scale:!1,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:'\u{1f4b3}',fitzpatrick_scale:!1,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:'\u{1f48e}',fitzpatrick_scale:!1,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:'\u2696',fitzpatrick_scale:!1,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:'\u{1f9f0}',fitzpatrick_scale:!1,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:'\u{1f527}',fitzpatrick_scale:!1,category:"objects"},hammer:{keywords:["tools","build","create"],char:'\u{1f528}',fitzpatrick_scale:!1,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:'\u2692',fitzpatrick_scale:!1,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:'\u{1f6e0}',fitzpatrick_scale:!1,category:"objects"},pick:{keywords:["tools","dig"],char:'\u26cf',fitzpatrick_scale:!1,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:'\u{1f529}',fitzpatrick_scale:!1,category:"objects"},gear:{keywords:["cog"],char:'\u2699',fitzpatrick_scale:!1,category:"objects"},brick:{keywords:["bricks"],char:'\u{1f9f1}',fitzpatrick_scale:!1,category:"objects"},chains:{keywords:["lock","arrest"],char:'\u26d3',fitzpatrick_scale:!1,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:'\u{1f9f2}',fitzpatrick_scale:!1,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:'\u{1f52b}',fitzpatrick_scale:!1,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:'\u{1f4a3}',fitzpatrick_scale:!1,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:'\u{1f9e8}',fitzpatrick_scale:!1,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:'\u{1f52a}',fitzpatrick_scale:!1,category:"objects"},dagger:{keywords:["weapon"],char:'\u{1f5e1}',fitzpatrick_scale:!1,category:"objects"},crossed_swords:{keywords:["weapon"],char:'\u2694',fitzpatrick_scale:!1,category:"objects"},shield:{keywords:["protection","security"],char:'\u{1f6e1}',fitzpatrick_scale:!1,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:'\u{1f6ac}',fitzpatrick_scale:!1,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:'\u2620',fitzpatrick_scale:!1,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:'\u26b0',fitzpatrick_scale:!1,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:'\u26b1',fitzpatrick_scale:!1,category:"objects"},amphora:{keywords:["vase","jar"],char:'\u{1f3fa}',fitzpatrick_scale:!1,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:'\u{1f52e}',fitzpatrick_scale:!1,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:'\u{1f4ff}',fitzpatrick_scale:!1,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:'\u{1f9ff}',fitzpatrick_scale:!1,category:"objects"},barber:{keywords:["hair","salon","style"],char:'\u{1f488}',fitzpatrick_scale:!1,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:'\u2697',fitzpatrick_scale:!1,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:'\u{1f52d}',fitzpatrick_scale:!1,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:'\u{1f52c}',fitzpatrick_scale:!1,category:"objects"},hole:{keywords:["embarrassing"],char:'\u{1f573}',fitzpatrick_scale:!1,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:'\u{1f48a}',fitzpatrick_scale:!1,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:'\u{1f489}',fitzpatrick_scale:!1,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:'\u{1f9ec}',fitzpatrick_scale:!1,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:'\u{1f9a0}',fitzpatrick_scale:!1,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:'\u{1f9eb}',fitzpatrick_scale:!1,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:'\u{1f9ea}',fitzpatrick_scale:!1,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:'\u{1f321}',fitzpatrick_scale:!1,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:'\u{1f9f9}',fitzpatrick_scale:!1,category:"objects"},basket:{keywords:["laundry"],char:'\u{1f9fa}',fitzpatrick_scale:!1,category:"objects"},toilet_paper:{keywords:["roll"],char:'\u{1f9fb}',fitzpatrick_scale:!1,category:"objects"},label:{keywords:["sale","tag"],char:'\u{1f3f7}',fitzpatrick_scale:!1,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:'\u{1f516}',fitzpatrick_scale:!1,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:'\u{1f6bd}',fitzpatrick_scale:!1,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:'\u{1f6bf}',fitzpatrick_scale:!1,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:'\u{1f6c1}',fitzpatrick_scale:!1,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:'\u{1f9fc}',fitzpatrick_scale:!1,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:'\u{1f9fd}',fitzpatrick_scale:!1,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:'\u{1f9f4}',fitzpatrick_scale:!1,category:"objects"},key:{keywords:["lock","door","password"],char:'\u{1f511}',fitzpatrick_scale:!1,category:"objects"},old_key:{keywords:["lock","door","password"],char:'\u{1f5dd}',fitzpatrick_scale:!1,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:'\u{1f6cb}',fitzpatrick_scale:!1,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:'\u{1f6cc}',fitzpatrick_scale:!0,category:"objects"},bed:{keywords:["sleep","rest"],char:'\u{1f6cf}',fitzpatrick_scale:!1,category:"objects"},door:{keywords:["house","entry","exit"],char:'\u{1f6aa}',fitzpatrick_scale:!1,category:"objects"},bellhop_bell:{keywords:["service"],char:'\u{1f6ce}',fitzpatrick_scale:!1,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:'\u{1f9f8}',fitzpatrick_scale:!1,category:"objects"},framed_picture:{keywords:["photography"],char:'\u{1f5bc}',fitzpatrick_scale:!1,category:"objects"},world_map:{keywords:["location","direction"],char:'\u{1f5fa}',fitzpatrick_scale:!1,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:'\u26f1',fitzpatrick_scale:!1,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:'\u{1f5ff}',fitzpatrick_scale:!1,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:'\u{1f6cd}',fitzpatrick_scale:!1,category:"objects"},shopping_cart:{keywords:["trolley"],char:'\u{1f6d2}',fitzpatrick_scale:!1,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:'\u{1f388}',fitzpatrick_scale:!1,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:'\u{1f38f}',fitzpatrick_scale:!1,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:'\u{1f380}',fitzpatrick_scale:!1,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:'\u{1f381}',fitzpatrick_scale:!1,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:'\u{1f38a}',fitzpatrick_scale:!1,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:'\u{1f389}',fitzpatrick_scale:!1,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:'\u{1f38e}',fitzpatrick_scale:!1,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:'\u{1f390}',fitzpatrick_scale:!1,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:'\u{1f38c}',fitzpatrick_scale:!1,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:'\u{1f3ee}',fitzpatrick_scale:!1,category:"objects"},red_envelope:{keywords:["gift"],char:'\u{1f9e7}',fitzpatrick_scale:!1,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:'\u2709\ufe0f',fitzpatrick_scale:!1,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:'\u{1f4e9}',fitzpatrick_scale:!1,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:'\u{1f4e8}',fitzpatrick_scale:!1,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:'\u{1f4e7}',fitzpatrick_scale:!1,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:'\u{1f48c}',fitzpatrick_scale:!1,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:'\u{1f4ee}',fitzpatrick_scale:!1,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:'\u{1f4ea}',fitzpatrick_scale:!1,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:'\u{1f4eb}',fitzpatrick_scale:!1,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:'\u{1f4ec}',fitzpatrick_scale:!1,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:'\u{1f4ed}',fitzpatrick_scale:!1,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:'\u{1f4e6}',fitzpatrick_scale:!1,category:"objects"},postal_horn:{keywords:["instrument","music"],char:'\u{1f4ef}',fitzpatrick_scale:!1,category:"objects"},inbox_tray:{keywords:["email","documents"],char:'\u{1f4e5}',fitzpatrick_scale:!1,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:'\u{1f4e4}',fitzpatrick_scale:!1,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:'\u{1f4dc}',fitzpatrick_scale:!1,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:'\u{1f4c3}',fitzpatrick_scale:!1,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:'\u{1f4d1}',fitzpatrick_scale:!1,category:"objects"},receipt:{keywords:["accounting","expenses"],char:'\u{1f9fe}',fitzpatrick_scale:!1,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:'\u{1f4ca}',fitzpatrick_scale:!1,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:'\u{1f4c8}',fitzpatrick_scale:!1,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:'\u{1f4c9}',fitzpatrick_scale:!1,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:'\u{1f4c4}',fitzpatrick_scale:!1,category:"objects"},date:{keywords:["calendar","schedule"],char:'\u{1f4c5}',fitzpatrick_scale:!1,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:'\u{1f4c6}',fitzpatrick_scale:!1,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:'\u{1f5d3}',fitzpatrick_scale:!1,category:"objects"},card_index:{keywords:["business","stationery"],char:'\u{1f4c7}',fitzpatrick_scale:!1,category:"objects"},card_file_box:{keywords:["business","stationery"],char:'\u{1f5c3}',fitzpatrick_scale:!1,category:"objects"},ballot_box:{keywords:["election","vote"],char:'\u{1f5f3}',fitzpatrick_scale:!1,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:'\u{1f5c4}',fitzpatrick_scale:!1,category:"objects"},clipboard:{keywords:["stationery","documents"],char:'\u{1f4cb}',fitzpatrick_scale:!1,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:'\u{1f5d2}',fitzpatrick_scale:!1,category:"objects"},file_folder:{keywords:["documents","business","office"],char:'\u{1f4c1}',fitzpatrick_scale:!1,category:"objects"},open_file_folder:{keywords:["documents","load"],char:'\u{1f4c2}',fitzpatrick_scale:!1,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:'\u{1f5c2}',fitzpatrick_scale:!1,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:'\u{1f5de}',fitzpatrick_scale:!1,category:"objects"},newspaper:{keywords:["press","headline"],char:'\u{1f4f0}',fitzpatrick_scale:!1,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:'\u{1f4d3}',fitzpatrick_scale:!1,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:'\u{1f4d5}',fitzpatrick_scale:!1,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:'\u{1f4d7}',fitzpatrick_scale:!1,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:'\u{1f4d8}',fitzpatrick_scale:!1,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:'\u{1f4d9}',fitzpatrick_scale:!1,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:'\u{1f4d4}',fitzpatrick_scale:!1,category:"objects"},ledger:{keywords:["notes","paper"],char:'\u{1f4d2}',fitzpatrick_scale:!1,category:"objects"},books:{keywords:["literature","library","study"],char:'\u{1f4da}',fitzpatrick_scale:!1,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:'\u{1f4d6}',fitzpatrick_scale:!1,category:"objects"},safety_pin:{keywords:["diaper"],char:'\u{1f9f7}',fitzpatrick_scale:!1,category:"objects"},link:{keywords:["rings","url"],char:'\u{1f517}',fitzpatrick_scale:!1,category:"objects"},paperclip:{keywords:["documents","stationery"],char:'\u{1f4ce}',fitzpatrick_scale:!1,category:"objects"},paperclips:{keywords:["documents","stationery"],char:'\u{1f587}',fitzpatrick_scale:!1,category:"objects"},scissors:{keywords:["stationery","cut"],char:'\u2702\ufe0f',fitzpatrick_scale:!1,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:'\u{1f4d0}',fitzpatrick_scale:!1,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:'\u{1f4cf}',fitzpatrick_scale:!1,category:"objects"},abacus:{keywords:["calculation"],char:'\u{1f9ee}',fitzpatrick_scale:!1,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:'\u{1f4cc}',fitzpatrick_scale:!1,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:'\u{1f4cd}',fitzpatrick_scale:!1,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:'\u{1f6a9}',fitzpatrick_scale:!1,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:'\u{1f3f3}',fitzpatrick_scale:!1,category:"objects"},black_flag:{keywords:["pirate"],char:'\u{1f3f4}',fitzpatrick_scale:!1,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:'\u{1f3f3}\ufe0f\u200d\u{1f308}',fitzpatrick_scale:!1,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:'\u{1f510}',fitzpatrick_scale:!1,category:"objects"},lock:{keywords:["security","password","padlock"],char:'\u{1f512}',fitzpatrick_scale:!1,category:"objects"},unlock:{keywords:["privacy","security"],char:'\u{1f513}',fitzpatrick_scale:!1,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:'\u{1f50f}',fitzpatrick_scale:!1,category:"objects"},pen:{keywords:["stationery","writing","write"],char:'\u{1f58a}',fitzpatrick_scale:!1,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:'\u{1f58b}',fitzpatrick_scale:!1,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:'\u2712\ufe0f',fitzpatrick_scale:!1,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:'\u{1f4dd}',fitzpatrick_scale:!1,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:'\u270f\ufe0f',fitzpatrick_scale:!1,category:"objects"},crayon:{keywords:["drawing","creativity"],char:'\u{1f58d}',fitzpatrick_scale:!1,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:'\u{1f58c}',fitzpatrick_scale:!1,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:'\u{1f50d}',fitzpatrick_scale:!1,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:'\u{1f50e}',fitzpatrick_scale:!1,category:"objects"},heart:{keywords:["love","like","valentines"],char:'\u2764\ufe0f',fitzpatrick_scale:!1,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f9e1}',fitzpatrick_scale:!1,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f49b}',fitzpatrick_scale:!1,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f49a}',fitzpatrick_scale:!1,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f499}',fitzpatrick_scale:!1,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f49c}',fitzpatrick_scale:!1,category:"symbols"},black_heart:{keywords:["evil"],char:'\u{1f5a4}',fitzpatrick_scale:!1,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:'\u{1f494}',fitzpatrick_scale:!1,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:'\u2763',fitzpatrick_scale:!1,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:'\u{1f495}',fitzpatrick_scale:!1,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:'\u{1f49e}',fitzpatrick_scale:!1,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:'\u{1f493}',fitzpatrick_scale:!1,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:'\u{1f497}',fitzpatrick_scale:!1,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f496}',fitzpatrick_scale:!1,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:'\u{1f498}',fitzpatrick_scale:!1,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:'\u{1f49d}',fitzpatrick_scale:!1,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:'\u{1f49f}',fitzpatrick_scale:!1,category:"symbols"},peace_symbol:{keywords:["hippie"],char:'\u262e',fitzpatrick_scale:!1,category:"symbols"},latin_cross:{keywords:["christianity"],char:'\u271d',fitzpatrick_scale:!1,category:"symbols"},star_and_crescent:{keywords:["islam"],char:'\u262a',fitzpatrick_scale:!1,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'\u{1f549}',fitzpatrick_scale:!1,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'\u2638',fitzpatrick_scale:!1,category:"symbols"},star_of_david:{keywords:["judaism"],char:'\u2721',fitzpatrick_scale:!1,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:'\u{1f52f}',fitzpatrick_scale:!1,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:'\u{1f54e}',fitzpatrick_scale:!1,category:"symbols"},yin_yang:{keywords:["balance"],char:'\u262f',fitzpatrick_scale:!1,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:'\u2626',fitzpatrick_scale:!1,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:'\u{1f6d0}',fitzpatrick_scale:!1,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:'\u26ce',fitzpatrick_scale:!1,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u2648',fitzpatrick_scale:!1,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:'\u2649',fitzpatrick_scale:!1,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u264a',fitzpatrick_scale:!1,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u264b',fitzpatrick_scale:!1,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u264c',fitzpatrick_scale:!1,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u264d',fitzpatrick_scale:!1,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u264e',fitzpatrick_scale:!1,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:'\u264f',fitzpatrick_scale:!1,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u2650',fitzpatrick_scale:!1,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u2651',fitzpatrick_scale:!1,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u2652',fitzpatrick_scale:!1,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:'\u2653',fitzpatrick_scale:!1,category:"symbols"},id:{keywords:["purple-square","words"],char:'\u{1f194}',fitzpatrick_scale:!1,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:'\u269b',fitzpatrick_scale:!1,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:'\u{1f233}',fitzpatrick_scale:!1,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:'\u{1f239}',fitzpatrick_scale:!1,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:'\u2622',fitzpatrick_scale:!1,category:"symbols"},biohazard:{keywords:["danger"],char:'\u2623',fitzpatrick_scale:!1,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:'\u{1f4f4}',fitzpatrick_scale:!1,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:'\u{1f4f3}',fitzpatrick_scale:!1,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:'\u{1f236}',fitzpatrick_scale:!1,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:'\u{1f21a}',fitzpatrick_scale:!1,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:'\u{1f238}',fitzpatrick_scale:!1,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:'\u{1f23a}',fitzpatrick_scale:!1,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:'\u{1f237}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:'\u2734\ufe0f',fitzpatrick_scale:!1,category:"symbols"},vs:{keywords:["words","orange-square"],char:'\u{1f19a}',fitzpatrick_scale:!1,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:'\u{1f251}',fitzpatrick_scale:!1,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:'\u{1f4ae}',fitzpatrick_scale:!1,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:'\u{1f250}',fitzpatrick_scale:!1,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:'\u3299\ufe0f',fitzpatrick_scale:!1,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:'\u3297\ufe0f',fitzpatrick_scale:!1,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:'\u{1f234}',fitzpatrick_scale:!1,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:'\u{1f235}',fitzpatrick_scale:!1,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:'\u{1f232}',fitzpatrick_scale:!1,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:'\u{1f170}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:'\u{1f171}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:'\u{1f18e}',fitzpatrick_scale:!1,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:'\u{1f191}',fitzpatrick_scale:!1,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:'\u{1f17e}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:'\u{1f198}',fitzpatrick_scale:!1,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:'\u26d4',fitzpatrick_scale:!1,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:'\u{1f4db}',fitzpatrick_scale:!1,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:'\u{1f6ab}',fitzpatrick_scale:!1,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:'\u274c',fitzpatrick_scale:!1,category:"symbols"},o:{keywords:["circle","round"],char:'\u2b55',fitzpatrick_scale:!1,category:"symbols"},stop_sign:{keywords:["stop"],char:'\u{1f6d1}',fitzpatrick_scale:!1,category:"symbols"},anger:{keywords:["angry","mad"],char:'\u{1f4a2}',fitzpatrick_scale:!1,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:'\u2668\ufe0f',fitzpatrick_scale:!1,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:'\u{1f6b7}',fitzpatrick_scale:!1,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:'\u{1f6af}',fitzpatrick_scale:!1,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:'\u{1f6b3}',fitzpatrick_scale:!1,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:'\u{1f6b1}',fitzpatrick_scale:!1,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:'\u{1f51e}',fitzpatrick_scale:!1,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:'\u{1f4f5}',fitzpatrick_scale:!1,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:'\u2757',fitzpatrick_scale:!1,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:'\u2755',fitzpatrick_scale:!1,category:"symbols"},question:{keywords:["doubt","confused"],char:'\u2753',fitzpatrick_scale:!1,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:'\u2754',fitzpatrick_scale:!1,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:'\u203c\ufe0f',fitzpatrick_scale:!1,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:'\u2049\ufe0f',fitzpatrick_scale:!1,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:'\u{1f505}',fitzpatrick_scale:!1,category:"symbols"},high_brightness:{keywords:["sun","light"],char:'\u{1f506}',fitzpatrick_scale:!1,category:"symbols"},trident:{keywords:["weapon","spear"],char:'\u{1f531}',fitzpatrick_scale:!1,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:'\u269c',fitzpatrick_scale:!1,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:'\u303d\ufe0f',fitzpatrick_scale:!1,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:'\u26a0\ufe0f',fitzpatrick_scale:!1,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:'\u{1f6b8}',fitzpatrick_scale:!1,category:"symbols"},beginner:{keywords:["badge","shield"],char:'\u{1f530}',fitzpatrick_scale:!1,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:'\u267b\ufe0f',fitzpatrick_scale:!1,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:'\u{1f22f}',fitzpatrick_scale:!1,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:'\u{1f4b9}',fitzpatrick_scale:!1,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:'\u2747\ufe0f',fitzpatrick_scale:!1,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:'\u2733\ufe0f',fitzpatrick_scale:!1,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:'\u274e',fitzpatrick_scale:!1,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:'\u2705',fitzpatrick_scale:!1,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:'\u{1f4a0}',fitzpatrick_scale:!1,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:'\u{1f300}',fitzpatrick_scale:!1,category:"symbols"},loop:{keywords:["tape","cassette"],char:'\u27bf',fitzpatrick_scale:!1,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:'\u{1f310}',fitzpatrick_scale:!1,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:'\u24c2\ufe0f',fitzpatrick_scale:!1,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:'\u{1f3e7}',fitzpatrick_scale:!1,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:'\u{1f202}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:'\u{1f6c2}',fitzpatrick_scale:!1,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:'\u{1f6c3}',fitzpatrick_scale:!1,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:'\u{1f6c4}',fitzpatrick_scale:!1,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:'\u{1f6c5}',fitzpatrick_scale:!1,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:'\u267f',fitzpatrick_scale:!1,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:'\u{1f6ad}',fitzpatrick_scale:!1,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:'\u{1f6be}',fitzpatrick_scale:!1,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:'\u{1f17f}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:'\u{1f6b0}',fitzpatrick_scale:!1,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:'\u{1f6b9}',fitzpatrick_scale:!1,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:'\u{1f6ba}',fitzpatrick_scale:!1,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:'\u{1f6bc}',fitzpatrick_scale:!1,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:'\u{1f6bb}',fitzpatrick_scale:!1,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:'\u{1f6ae}',fitzpatrick_scale:!1,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:'\u{1f3a6}',fitzpatrick_scale:!1,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:'\u{1f4f6}',fitzpatrick_scale:!1,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:'\u{1f201}',fitzpatrick_scale:!1,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:'\u{1f196}',fitzpatrick_scale:!1,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:'\u{1f197}',fitzpatrick_scale:!1,category:"symbols"},up:{keywords:["blue-square","above","high"],char:'\u{1f199}',fitzpatrick_scale:!1,category:"symbols"},cool:{keywords:["words","blue-square"],char:'\u{1f192}',fitzpatrick_scale:!1,category:"symbols"},new:{keywords:["blue-square","words","start"],char:'\u{1f195}',fitzpatrick_scale:!1,category:"symbols"},free:{keywords:["blue-square","words"],char:'\u{1f193}',fitzpatrick_scale:!1,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:'0\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:'1\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:'2\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:'3\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:'4\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:'5\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:'6\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:'7\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:'8\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:'9\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:'\u{1f51f}',fitzpatrick_scale:!1,category:"symbols"},asterisk:{keywords:["star","keycap"],char:'*\u20e3',fitzpatrick_scale:!1,category:"symbols"},eject_button:{keywords:["blue-square"],char:'\u23cf\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:'\u25b6\ufe0f',fitzpatrick_scale:!1,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:'\u23f8',fitzpatrick_scale:!1,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:'\u23ed',fitzpatrick_scale:!1,category:"symbols"},stop_button:{keywords:["blue-square"],char:'\u23f9',fitzpatrick_scale:!1,category:"symbols"},record_button:{keywords:["blue-square"],char:'\u23fa',fitzpatrick_scale:!1,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:'\u23ef',fitzpatrick_scale:!1,category:"symbols"},previous_track_button:{keywords:["backward"],char:'\u23ee',fitzpatrick_scale:!1,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:'\u23e9',fitzpatrick_scale:!1,category:"symbols"},rewind:{keywords:["play","blue-square"],char:'\u23ea',fitzpatrick_scale:!1,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:'\u{1f500}',fitzpatrick_scale:!1,category:"symbols"},repeat:{keywords:["loop","record"],char:'\u{1f501}',fitzpatrick_scale:!1,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:'\u{1f502}',fitzpatrick_scale:!1,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:'\u25c0\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:'\u{1f53c}',fitzpatrick_scale:!1,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:'\u{1f53d}',fitzpatrick_scale:!1,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:'\u23eb',fitzpatrick_scale:!1,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:'\u23ec',fitzpatrick_scale:!1,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:'\u27a1\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:'\u2b05\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:'\u2b06\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:'\u2b07\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:'\u2197\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:'\u2198\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:'\u2199\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:'\u2196\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:'\u2195\ufe0f',fitzpatrick_scale:!1,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:'\u2194\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:'\u{1f504}',fitzpatrick_scale:!1,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:'\u21aa\ufe0f',fitzpatrick_scale:!1,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:'\u21a9\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:'\u2934\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:'\u2935\ufe0f',fitzpatrick_scale:!1,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:'#\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:'\u2139\ufe0f',fitzpatrick_scale:!1,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:'\u{1f524}',fitzpatrick_scale:!1,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:'\u{1f521}',fitzpatrick_scale:!1,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:'\u{1f520}',fitzpatrick_scale:!1,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:'\u{1f523}',fitzpatrick_scale:!1,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:'\u{1f3b5}',fitzpatrick_scale:!1,category:"symbols"},notes:{keywords:["music","score"],char:'\u{1f3b6}',fitzpatrick_scale:!1,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:'\u3030\ufe0f',fitzpatrick_scale:!1,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:'\u27b0',fitzpatrick_scale:!1,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:'\u2714\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:'\u{1f503}',fitzpatrick_scale:!1,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:'\u2795',fitzpatrick_scale:!1,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:'\u2796',fitzpatrick_scale:!1,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:'\u2797',fitzpatrick_scale:!1,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:'\u2716\ufe0f',fitzpatrick_scale:!1,category:"symbols"},infinity:{keywords:["forever"],char:'\u267e',fitzpatrick_scale:!1,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:'\u{1f4b2}',fitzpatrick_scale:!1,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:'\u{1f4b1}',fitzpatrick_scale:!1,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:'\xa9\ufe0f',fitzpatrick_scale:!1,category:"symbols"},registered:{keywords:["alphabet","circle"],char:'\xae\ufe0f',fitzpatrick_scale:!1,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:'\u2122\ufe0f',fitzpatrick_scale:!1,category:"symbols"},end:{keywords:["words","arrow"],char:'\u{1f51a}',fitzpatrick_scale:!1,category:"symbols"},back:{keywords:["arrow","words","return"],char:'\u{1f519}',fitzpatrick_scale:!1,category:"symbols"},on:{keywords:["arrow","words"],char:'\u{1f51b}',fitzpatrick_scale:!1,category:"symbols"},top:{keywords:["words","blue-square"],char:'\u{1f51d}',fitzpatrick_scale:!1,category:"symbols"},soon:{keywords:["arrow","words"],char:'\u{1f51c}',fitzpatrick_scale:!1,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:'\u2611\ufe0f',fitzpatrick_scale:!1,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:'\u{1f518}',fitzpatrick_scale:!1,category:"symbols"},white_circle:{keywords:["shape","round"],char:'\u26aa',fitzpatrick_scale:!1,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:'\u26ab',fitzpatrick_scale:!1,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:'\u{1f534}',fitzpatrick_scale:!1,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:'\u{1f535}',fitzpatrick_scale:!1,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f538}',fitzpatrick_scale:!1,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f539}',fitzpatrick_scale:!1,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f536}',fitzpatrick_scale:!1,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f537}',fitzpatrick_scale:!1,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:'\u{1f53a}',fitzpatrick_scale:!1,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:'\u25aa\ufe0f',fitzpatrick_scale:!1,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:'\u25ab\ufe0f',fitzpatrick_scale:!1,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:'\u2b1b',fitzpatrick_scale:!1,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:'\u2b1c',fitzpatrick_scale:!1,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:'\u{1f53b}',fitzpatrick_scale:!1,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:'\u25fc\ufe0f',fitzpatrick_scale:!1,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:'\u25fb\ufe0f',fitzpatrick_scale:!1,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:'\u25fe',fitzpatrick_scale:!1,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:'\u25fd',fitzpatrick_scale:!1,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:'\u{1f532}',fitzpatrick_scale:!1,category:"symbols"},white_square_button:{keywords:["shape","input"],char:'\u{1f533}',fitzpatrick_scale:!1,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:'\u{1f508}',fitzpatrick_scale:!1,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:'\u{1f509}',fitzpatrick_scale:!1,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:'\u{1f50a}',fitzpatrick_scale:!1,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:'\u{1f507}',fitzpatrick_scale:!1,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:'\u{1f4e3}',fitzpatrick_scale:!1,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:'\u{1f4e2}',fitzpatrick_scale:!1,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:'\u{1f514}',fitzpatrick_scale:!1,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:'\u{1f515}',fitzpatrick_scale:!1,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:'\u{1f0cf}',fitzpatrick_scale:!1,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:'\u{1f004}',fitzpatrick_scale:!1,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:'\u2660\ufe0f',fitzpatrick_scale:!1,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:'\u2663\ufe0f',fitzpatrick_scale:!1,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:'\u2665\ufe0f',fitzpatrick_scale:!1,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:'\u2666\ufe0f',fitzpatrick_scale:!1,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:'\u{1f3b4}',fitzpatrick_scale:!1,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:'\u{1f4ad}',fitzpatrick_scale:!1,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:'\u{1f5ef}',fitzpatrick_scale:!1,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:'\u{1f4ac}',fitzpatrick_scale:!1,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:'\u{1f5e8}',fitzpatrick_scale:!1,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:'\u{1f550}',fitzpatrick_scale:!1,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:'\u{1f551}',fitzpatrick_scale:!1,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:'\u{1f552}',fitzpatrick_scale:!1,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:'\u{1f553}',fitzpatrick_scale:!1,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:'\u{1f554}',fitzpatrick_scale:!1,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:'\u{1f555}',fitzpatrick_scale:!1,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:'\u{1f556}',fitzpatrick_scale:!1,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:'\u{1f557}',fitzpatrick_scale:!1,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:'\u{1f558}',fitzpatrick_scale:!1,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:'\u{1f559}',fitzpatrick_scale:!1,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:'\u{1f55a}',fitzpatrick_scale:!1,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:'\u{1f55b}',fitzpatrick_scale:!1,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:'\u{1f55c}',fitzpatrick_scale:!1,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:'\u{1f55d}',fitzpatrick_scale:!1,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:'\u{1f55e}',fitzpatrick_scale:!1,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:'\u{1f55f}',fitzpatrick_scale:!1,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:'\u{1f560}',fitzpatrick_scale:!1,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:'\u{1f561}',fitzpatrick_scale:!1,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:'\u{1f562}',fitzpatrick_scale:!1,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:'\u{1f563}',fitzpatrick_scale:!1,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:'\u{1f564}',fitzpatrick_scale:!1,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:'\u{1f565}',fitzpatrick_scale:!1,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:'\u{1f566}',fitzpatrick_scale:!1,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:'\u{1f567}',fitzpatrick_scale:!1,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},aland_islands:{keywords:["\xc5land","islands","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:'\u{1f1e8}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},curacao:{keywords:["cura\xe7ao","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:'\u{1f1ea}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:'\u{1f1eb}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:'\u{1f1e9}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:'\u{1f1ef}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:'\u{1f1ef}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:'\u{1f1ef}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:'\u{1f1ef}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:'\u{1f1fd}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:'\u{1f1fe}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:'\u{1f1f0}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:'\u{1f1f4}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:'\u{1f1f6}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},reunion:{keywords:["r\xe9union","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},st_barthelemy:{keywords:["saint","barth\xe9lemy","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:'\u{1f1fc}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:'\u{1f1ff}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:'\u{1f1f0}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:'\u{1f1ec}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},england:{keywords:["flag","english"],char:'\u{1f3f4}\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}',fitzpatrick_scale:!1,category:"flags"},scotland:{keywords:["flag","scottish"],char:'\u{1f3f4}\u{e0067}\u{e0062}\u{e0073}\u{e0063}\u{e0074}\u{e007f}',fitzpatrick_scale:!1,category:"flags"},wales:{keywords:["flag","welsh"],char:'\u{1f3f4}\u{e0067}\u{e0062}\u{e0077}\u{e006c}\u{e0073}\u{e007f}',fitzpatrick_scale:!1,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:'\u{1f1fc}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:'\u{1f1fe}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:'\u{1f1ff}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:'\u{1f1ff}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:'\u{1f1fa}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:'\u{1f3f4}\u200d\u2620\ufe0f',fitzpatrick_scale:!1,category:"flags"}}); \ No newline at end of file diff --git a/static/js/tinymce/plugins/emoticons/js/emojis.js b/static/js/tinymce/plugins/emoticons/js/emojis.js new file mode 100644 index 0000000..88455e9 --- /dev/null +++ b/static/js/tinymce/plugins/emoticons/js/emojis.js @@ -0,0 +1 @@ +window.tinymce.Resource.add("tinymce.plugins.emoticons",{grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:"😀",fitzpatrick_scale:false,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:"😬",fitzpatrick_scale:false,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:"ðŸ˜",fitzpatrick_scale:false,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:"😂",fitzpatrick_scale:false,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:"🤣",fitzpatrick_scale:false,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:"🥳",fitzpatrick_scale:false,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:"😃",fitzpatrick_scale:false,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:"😄",fitzpatrick_scale:false,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:"😅",fitzpatrick_scale:false,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:"😆",fitzpatrick_scale:false,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:"😇",fitzpatrick_scale:false,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:"😉",fitzpatrick_scale:false,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:"😊",fitzpatrick_scale:false,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:"🙂",fitzpatrick_scale:false,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:"🙃",fitzpatrick_scale:false,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:"☺ï¸",fitzpatrick_scale:false,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:"😋",fitzpatrick_scale:false,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:"😌",fitzpatrick_scale:false,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:"ðŸ˜",fitzpatrick_scale:false,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:"🥰",fitzpatrick_scale:false,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"😘",fitzpatrick_scale:false,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:"😗",fitzpatrick_scale:false,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:"😙",fitzpatrick_scale:false,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"😚",fitzpatrick_scale:false,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:"😜",fitzpatrick_scale:false,category:"people"},zany:{keywords:["face","goofy","crazy"],char:"🤪",fitzpatrick_scale:false,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:"🤨",fitzpatrick_scale:false,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:"ðŸ§",fitzpatrick_scale:false,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:"ðŸ˜",fitzpatrick_scale:false,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:"😛",fitzpatrick_scale:false,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:"🤑",fitzpatrick_scale:false,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:"🤓",fitzpatrick_scale:false,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:"😎",fitzpatrick_scale:false,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:"🤩",fitzpatrick_scale:false,category:"people"},clown_face:{keywords:["face"],char:"🤡",fitzpatrick_scale:false,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:"🤠",fitzpatrick_scale:false,category:"people"},hugs:{keywords:["face","smile","hug"],char:"🤗",fitzpatrick_scale:false,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:"ðŸ˜",fitzpatrick_scale:false,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:"😶",fitzpatrick_scale:false,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:"ðŸ˜",fitzpatrick_scale:false,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:"😑",fitzpatrick_scale:false,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:"😒",fitzpatrick_scale:false,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:"🙄",fitzpatrick_scale:false,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:"🤔",fitzpatrick_scale:false,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:"🤥",fitzpatrick_scale:false,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:"🤭",fitzpatrick_scale:false,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:"🤫",fitzpatrick_scale:false,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:"🤬",fitzpatrick_scale:false,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:"🤯",fitzpatrick_scale:false,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:"😳",fitzpatrick_scale:false,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:"😞",fitzpatrick_scale:false,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:"😟",fitzpatrick_scale:false,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:"😠",fitzpatrick_scale:false,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:"😡",fitzpatrick_scale:false,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:"😔",fitzpatrick_scale:false,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:"😕",fitzpatrick_scale:false,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:"ðŸ™",fitzpatrick_scale:false,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:"☹",fitzpatrick_scale:false,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:"😣",fitzpatrick_scale:false,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:"😖",fitzpatrick_scale:false,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:"😫",fitzpatrick_scale:false,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:"😩",fitzpatrick_scale:false,category:"people"},pleading:{keywords:["face","begging","mercy"],char:"🥺",fitzpatrick_scale:false,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:"😤",fitzpatrick_scale:false,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:"😮",fitzpatrick_scale:false,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:"😱",fitzpatrick_scale:false,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:"😨",fitzpatrick_scale:false,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:"😰",fitzpatrick_scale:false,category:"people"},hushed:{keywords:["face","woo","shh"],char:"😯",fitzpatrick_scale:false,category:"people"},frowning:{keywords:["face","aw","what"],char:"😦",fitzpatrick_scale:false,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:"😧",fitzpatrick_scale:false,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:"😢",fitzpatrick_scale:false,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:"😥",fitzpatrick_scale:false,category:"people"},drooling_face:{keywords:["face"],char:"🤤",fitzpatrick_scale:false,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:"😪",fitzpatrick_scale:false,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:"😓",fitzpatrick_scale:false,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:"🥵",fitzpatrick_scale:false,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:"🥶",fitzpatrick_scale:false,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:"😭",fitzpatrick_scale:false,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:"😵",fitzpatrick_scale:false,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:"😲",fitzpatrick_scale:false,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:"ðŸ¤",fitzpatrick_scale:false,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:"🤢",fitzpatrick_scale:false,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:"🤧",fitzpatrick_scale:false,category:"people"},vomiting:{keywords:["face","sick"],char:"🤮",fitzpatrick_scale:false,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:"😷",fitzpatrick_scale:false,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:"🤒",fitzpatrick_scale:false,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:"🤕",fitzpatrick_scale:false,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:"🥴",fitzpatrick_scale:false,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:"😴",fitzpatrick_scale:false,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:"💤",fitzpatrick_scale:false,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:"💩",fitzpatrick_scale:false,category:"people"},smiling_imp:{keywords:["devil","horns"],char:"😈",fitzpatrick_scale:false,category:"people"},imp:{keywords:["devil","angry","horns"],char:"👿",fitzpatrick_scale:false,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:"👹",fitzpatrick_scale:false,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:"👺",fitzpatrick_scale:false,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:"💀",fitzpatrick_scale:false,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:"👻",fitzpatrick_scale:false,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:"👽",fitzpatrick_scale:false,category:"people"},robot:{keywords:["computer","machine","bot"],char:"🤖",fitzpatrick_scale:false,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:"😺",fitzpatrick_scale:false,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:"😸",fitzpatrick_scale:false,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:"😹",fitzpatrick_scale:false,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:"😻",fitzpatrick_scale:false,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:"😼",fitzpatrick_scale:false,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:"😽",fitzpatrick_scale:false,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:"🙀",fitzpatrick_scale:false,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:"😿",fitzpatrick_scale:false,category:"people"},pouting_cat:{keywords:["animal","cats"],char:"😾",fitzpatrick_scale:false,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:"🤲",fitzpatrick_scale:true,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:"🙌",fitzpatrick_scale:true,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:"ðŸ‘",fitzpatrick_scale:true,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:"👋",fitzpatrick_scale:true,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:"🤙",fitzpatrick_scale:true,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:"ðŸ‘",fitzpatrick_scale:true,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:"👎",fitzpatrick_scale:true,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:"👊",fitzpatrick_scale:true,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:"✊",fitzpatrick_scale:true,category:"people"},fist_left:{keywords:["hand","fistbump"],char:"🤛",fitzpatrick_scale:true,category:"people"},fist_right:{keywords:["hand","fistbump"],char:"🤜",fitzpatrick_scale:true,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:"✌",fitzpatrick_scale:true,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:"👌",fitzpatrick_scale:true,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:"✋",fitzpatrick_scale:true,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:"🤚",fitzpatrick_scale:true,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:"ðŸ‘",fitzpatrick_scale:true,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:"💪",fitzpatrick_scale:true,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:"ðŸ™",fitzpatrick_scale:true,category:"people"},foot:{keywords:["kick","stomp"],char:"🦶",fitzpatrick_scale:true,category:"people"},leg:{keywords:["kick","limb"],char:"🦵",fitzpatrick_scale:true,category:"people"},handshake:{keywords:["agreement","shake"],char:"ðŸ¤",fitzpatrick_scale:false,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:"â˜",fitzpatrick_scale:true,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:"👆",fitzpatrick_scale:true,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:"👇",fitzpatrick_scale:true,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:"👈",fitzpatrick_scale:true,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:"👉",fitzpatrick_scale:true,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:"🖕",fitzpatrick_scale:true,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:"ðŸ–",fitzpatrick_scale:true,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:"🤟",fitzpatrick_scale:true,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:"🤘",fitzpatrick_scale:true,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:"🤞",fitzpatrick_scale:true,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:"🖖",fitzpatrick_scale:true,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:"âœ",fitzpatrick_scale:true,category:"people"},selfie:{keywords:["camera","phone"],char:"🤳",fitzpatrick_scale:true,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:"💅",fitzpatrick_scale:true,category:"people"},lips:{keywords:["mouth","kiss"],char:"👄",fitzpatrick_scale:false,category:"people"},tooth:{keywords:["teeth","dentist"],char:"🦷",fitzpatrick_scale:false,category:"people"},tongue:{keywords:["mouth","playful"],char:"👅",fitzpatrick_scale:false,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:"👂",fitzpatrick_scale:true,category:"people"},nose:{keywords:["smell","sniff"],char:"👃",fitzpatrick_scale:true,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:"ðŸ‘",fitzpatrick_scale:false,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:"👀",fitzpatrick_scale:false,category:"people"},brain:{keywords:["smart","intelligent"],char:"🧠",fitzpatrick_scale:false,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:"👤",fitzpatrick_scale:false,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:"👥",fitzpatrick_scale:false,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:"🗣",fitzpatrick_scale:false,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:"👶",fitzpatrick_scale:true,category:"people"},child:{keywords:["gender-neutral","young"],char:"🧒",fitzpatrick_scale:true,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:"👦",fitzpatrick_scale:true,category:"people"},girl:{keywords:["female","woman","teenager"],char:"👧",fitzpatrick_scale:true,category:"people"},adult:{keywords:["gender-neutral","person"],char:"🧑",fitzpatrick_scale:true,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:"👨",fitzpatrick_scale:true,category:"people"},woman:{keywords:["female","girls","lady"],char:"👩",fitzpatrick_scale:true,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:"👱â€â™€ï¸",fitzpatrick_scale:true,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:"👱",fitzpatrick_scale:true,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:"🧔",fitzpatrick_scale:true,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:"🧓",fitzpatrick_scale:true,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:"👴",fitzpatrick_scale:true,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:"👵",fitzpatrick_scale:true,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:"👲",fitzpatrick_scale:true,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:"🧕",fitzpatrick_scale:true,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:"👳â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:"👳",fitzpatrick_scale:true,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:"👮â€â™€ï¸",fitzpatrick_scale:true,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:"👮",fitzpatrick_scale:true,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:"👷â€â™€ï¸",fitzpatrick_scale:true,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:"👷",fitzpatrick_scale:true,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:"💂â€â™€ï¸",fitzpatrick_scale:true,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:"💂",fitzpatrick_scale:true,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:"🕵ï¸â€â™€ï¸",fitzpatrick_scale:true,category:"people"},male_detective:{keywords:["human","spy","detective"],char:"🕵",fitzpatrick_scale:true,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:"👩â€âš•ï¸",fitzpatrick_scale:true,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:"👨â€âš•ï¸",fitzpatrick_scale:true,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:"👩â€ðŸŒ¾",fitzpatrick_scale:true,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:"👨â€ðŸŒ¾",fitzpatrick_scale:true,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:"👩â€ðŸ³",fitzpatrick_scale:true,category:"people"},man_cook:{keywords:["chef","man","human"],char:"👨â€ðŸ³",fitzpatrick_scale:true,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:"👩â€ðŸŽ“",fitzpatrick_scale:true,category:"people"},man_student:{keywords:["graduate","man","human"],char:"👨â€ðŸŽ“",fitzpatrick_scale:true,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:"👩â€ðŸŽ¤",fitzpatrick_scale:true,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:"👨â€ðŸŽ¤",fitzpatrick_scale:true,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:"👩â€ðŸ«",fitzpatrick_scale:true,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:"👨â€ðŸ«",fitzpatrick_scale:true,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:"👩â€ðŸ­",fitzpatrick_scale:true,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:"👨â€ðŸ­",fitzpatrick_scale:true,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:"👩â€ðŸ’»",fitzpatrick_scale:true,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:"👨â€ðŸ’»",fitzpatrick_scale:true,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:"👩â€ðŸ’¼",fitzpatrick_scale:true,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:"👨â€ðŸ’¼",fitzpatrick_scale:true,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:"👩â€ðŸ”§",fitzpatrick_scale:true,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:"👨â€ðŸ”§",fitzpatrick_scale:true,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:"👩â€ðŸ”¬",fitzpatrick_scale:true,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:"👨â€ðŸ”¬",fitzpatrick_scale:true,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:"👩â€ðŸŽ¨",fitzpatrick_scale:true,category:"people"},man_artist:{keywords:["painter","man","human"],char:"👨â€ðŸŽ¨",fitzpatrick_scale:true,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:"👩â€ðŸš’",fitzpatrick_scale:true,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:"👨â€ðŸš’",fitzpatrick_scale:true,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:"👩â€âœˆï¸",fitzpatrick_scale:true,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:"👨â€âœˆï¸",fitzpatrick_scale:true,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:"👩â€ðŸš€",fitzpatrick_scale:true,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:"👨â€ðŸš€",fitzpatrick_scale:true,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:"👩â€âš–ï¸",fitzpatrick_scale:true,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:"👨â€âš–ï¸",fitzpatrick_scale:true,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:"🦸â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:"🦸â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:"🦹â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:"🦹â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:"🤶",fitzpatrick_scale:true,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:"🎅",fitzpatrick_scale:true,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:"🧙â€â™€ï¸",fitzpatrick_scale:true,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:"🧙â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_elf:{keywords:["woman","female"],char:"ðŸ§â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_elf:{keywords:["man","male"],char:"ðŸ§â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_vampire:{keywords:["woman","female"],char:"🧛â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:"🧛â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:"🧟â€â™€ï¸",fitzpatrick_scale:false,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:"🧟â€â™‚ï¸",fitzpatrick_scale:false,category:"people"},woman_genie:{keywords:["woman","female"],char:"🧞â€â™€ï¸",fitzpatrick_scale:false,category:"people"},man_genie:{keywords:["man","male"],char:"🧞â€â™‚ï¸",fitzpatrick_scale:false,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:"🧜â€â™€ï¸",fitzpatrick_scale:true,category:"people"},merman:{keywords:["man","male","triton"],char:"🧜â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_fairy:{keywords:["woman","female"],char:"🧚â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_fairy:{keywords:["man","male"],char:"🧚â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},angel:{keywords:["heaven","wings","halo"],char:"👼",fitzpatrick_scale:true,category:"people"},pregnant_woman:{keywords:["baby"],char:"🤰",fitzpatrick_scale:true,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:"🤱",fitzpatrick_scale:true,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:"👸",fitzpatrick_scale:true,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:"🤴",fitzpatrick_scale:true,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:"👰",fitzpatrick_scale:true,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:"🤵",fitzpatrick_scale:true,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:"ðŸƒâ€â™€ï¸",fitzpatrick_scale:true,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:"ðŸƒ",fitzpatrick_scale:true,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:"🚶â€â™€ï¸",fitzpatrick_scale:true,category:"people"},walking_man:{keywords:["human","feet","steps"],char:"🚶",fitzpatrick_scale:true,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:"💃",fitzpatrick_scale:true,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:"🕺",fitzpatrick_scale:true,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:"👯",fitzpatrick_scale:false,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:"👯â€â™‚ï¸",fitzpatrick_scale:false,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:"👫",fitzpatrick_scale:false,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:"👬",fitzpatrick_scale:false,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:"👭",fitzpatrick_scale:false,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:"🙇â€â™€ï¸",fitzpatrick_scale:true,category:"people"},bowing_man:{keywords:["man","male","boy"],char:"🙇",fitzpatrick_scale:true,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:"🤦â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:"🤦â€â™€ï¸",fitzpatrick_scale:true,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:"🤷",fitzpatrick_scale:true,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:"🤷â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:"ðŸ’",fitzpatrick_scale:true,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:"ðŸ’â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:"🙅",fitzpatrick_scale:true,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:"🙅â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:"🙆",fitzpatrick_scale:true,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:"🙆â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:"🙋",fitzpatrick_scale:true,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:"🙋â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:"🙎",fitzpatrick_scale:true,category:"people"},pouting_man:{keywords:["male","boy","man"],char:"🙎â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:"ðŸ™",fitzpatrick_scale:true,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:"ðŸ™â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:"💇",fitzpatrick_scale:true,category:"people"},haircut_man:{keywords:["male","boy","man"],char:"💇â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:"💆",fitzpatrick_scale:true,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:"💆â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:"🧖â€â™€ï¸",fitzpatrick_scale:true,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:"🧖â€â™‚ï¸",fitzpatrick_scale:true,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"💑",fitzpatrick_scale:false,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"👩â€â¤ï¸â€ðŸ‘©",fitzpatrick_scale:false,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"👨â€â¤ï¸â€ðŸ‘¨",fitzpatrick_scale:false,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"ðŸ’",fitzpatrick_scale:false,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©",fitzpatrick_scale:false,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:"👨â€â¤ï¸â€ðŸ’‹â€ðŸ‘¨",fitzpatrick_scale:false,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:"👪",fitzpatrick_scale:false,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:"👨â€ðŸ‘©â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:"👩â€ðŸ‘©â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:"👩â€ðŸ‘©â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"👩â€ðŸ‘©â€ðŸ‘¦â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"👩â€ðŸ‘©â€ðŸ‘§â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘¨â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘¨â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘¨â€ðŸ‘¦â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:"👨â€ðŸ‘¨â€ðŸ‘§â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:"👩â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:"👩â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:"👩â€ðŸ‘§â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:"👩â€ðŸ‘¦â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:"👩â€ðŸ‘§â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:"👨â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:"👨â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:"👨â€ðŸ‘§â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:"👨â€ðŸ‘¦â€ðŸ‘¦",fitzpatrick_scale:false,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:"👨â€ðŸ‘§â€ðŸ‘§",fitzpatrick_scale:false,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:"🧶",fitzpatrick_scale:false,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:"🧵",fitzpatrick_scale:false,category:"people"},coat:{keywords:["jacket"],char:"🧥",fitzpatrick_scale:false,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:"🥼",fitzpatrick_scale:false,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:"👚",fitzpatrick_scale:false,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:"👕",fitzpatrick_scale:false,category:"people"},jeans:{keywords:["fashion","shopping"],char:"👖",fitzpatrick_scale:false,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:"👔",fitzpatrick_scale:false,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:"👗",fitzpatrick_scale:false,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:"👙",fitzpatrick_scale:false,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:"👘",fitzpatrick_scale:false,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:"💄",fitzpatrick_scale:false,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:"💋",fitzpatrick_scale:false,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:"👣",fitzpatrick_scale:false,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:"🥿",fitzpatrick_scale:false,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:"👠",fitzpatrick_scale:false,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:"👡",fitzpatrick_scale:false,category:"people"},boot:{keywords:["shoes","fashion"],char:"👢",fitzpatrick_scale:false,category:"people"},mans_shoe:{keywords:["fashion","male"],char:"👞",fitzpatrick_scale:false,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:"👟",fitzpatrick_scale:false,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:"🥾",fitzpatrick_scale:false,category:"people"},socks:{keywords:["stockings","clothes"],char:"🧦",fitzpatrick_scale:false,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:"🧤",fitzpatrick_scale:false,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:"🧣",fitzpatrick_scale:false,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:"👒",fitzpatrick_scale:false,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:"🎩",fitzpatrick_scale:false,category:"people"},billed_hat:{keywords:["cap","baseball"],char:"🧢",fitzpatrick_scale:false,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:"⛑",fitzpatrick_scale:false,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:"🎓",fitzpatrick_scale:false,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:"👑",fitzpatrick_scale:false,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:"🎒",fitzpatrick_scale:false,category:"people"},luggage:{keywords:["packing","travel"],char:"🧳",fitzpatrick_scale:false,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:"ðŸ‘",fitzpatrick_scale:false,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:"👛",fitzpatrick_scale:false,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:"👜",fitzpatrick_scale:false,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:"💼",fitzpatrick_scale:false,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:"👓",fitzpatrick_scale:false,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:"🕶",fitzpatrick_scale:false,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:"🥽",fitzpatrick_scale:false,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:"ðŸ’",fitzpatrick_scale:false,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:"🌂",fitzpatrick_scale:false,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:"ðŸ¶",fitzpatrick_scale:false,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:"ðŸ±",fitzpatrick_scale:false,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:"ðŸ­",fitzpatrick_scale:false,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:"ðŸ¹",fitzpatrick_scale:false,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:"ðŸ°",fitzpatrick_scale:false,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:"🦊",fitzpatrick_scale:false,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:"ðŸ»",fitzpatrick_scale:false,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:"ðŸ¼",fitzpatrick_scale:false,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:"ðŸ¨",fitzpatrick_scale:false,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:"ðŸ¯",fitzpatrick_scale:false,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:"ðŸ¦",fitzpatrick_scale:false,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:"ðŸ®",fitzpatrick_scale:false,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:"ðŸ·",fitzpatrick_scale:false,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:"ðŸ½",fitzpatrick_scale:false,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:"ðŸ¸",fitzpatrick_scale:false,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:"🦑",fitzpatrick_scale:false,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:"ðŸ™",fitzpatrick_scale:false,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:"ðŸ¦",fitzpatrick_scale:false,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:"ðŸµ",fitzpatrick_scale:false,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:"ðŸ¦",fitzpatrick_scale:false,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:"🙈",fitzpatrick_scale:false,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:"🙉",fitzpatrick_scale:false,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:"🙊",fitzpatrick_scale:false,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:"ðŸ’",fitzpatrick_scale:false,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:"ðŸ”",fitzpatrick_scale:false,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:"ðŸ§",fitzpatrick_scale:false,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:"ðŸ¦",fitzpatrick_scale:false,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:"ðŸ¤",fitzpatrick_scale:false,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:"ðŸ£",fitzpatrick_scale:false,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:"ðŸ¥",fitzpatrick_scale:false,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:"🦆",fitzpatrick_scale:false,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:"🦅",fitzpatrick_scale:false,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:"🦉",fitzpatrick_scale:false,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:"🦇",fitzpatrick_scale:false,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:"ðŸº",fitzpatrick_scale:false,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:"ðŸ—",fitzpatrick_scale:false,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:"ðŸ´",fitzpatrick_scale:false,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:"🦄",fitzpatrick_scale:false,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:"ðŸ",fitzpatrick_scale:false,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:"ðŸ›",fitzpatrick_scale:false,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:"🦋",fitzpatrick_scale:false,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:"ðŸŒ",fitzpatrick_scale:false,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:"ðŸž",fitzpatrick_scale:false,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:"ðŸœ",fitzpatrick_scale:false,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:"🦗",fitzpatrick_scale:false,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:"🕷",fitzpatrick_scale:false,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:"🦂",fitzpatrick_scale:false,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:"🦀",fitzpatrick_scale:false,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:"ðŸ",fitzpatrick_scale:false,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:"🦎",fitzpatrick_scale:false,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:"🦖",fitzpatrick_scale:false,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:"🦕",fitzpatrick_scale:false,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:"ðŸ¢",fitzpatrick_scale:false,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:"ðŸ ",fitzpatrick_scale:false,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:"ðŸŸ",fitzpatrick_scale:false,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:"ðŸ¡",fitzpatrick_scale:false,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:"ðŸ¬",fitzpatrick_scale:false,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:"🦈",fitzpatrick_scale:false,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:"ðŸ³",fitzpatrick_scale:false,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:"ðŸ‹",fitzpatrick_scale:false,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:"ðŸŠ",fitzpatrick_scale:false,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:"ðŸ†",fitzpatrick_scale:false,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:"🦓",fitzpatrick_scale:false,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:"ðŸ…",fitzpatrick_scale:false,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:"ðŸƒ",fitzpatrick_scale:false,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:"ðŸ‚",fitzpatrick_scale:false,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:"ðŸ„",fitzpatrick_scale:false,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:"🦌",fitzpatrick_scale:false,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:"ðŸª",fitzpatrick_scale:false,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:"ðŸ«",fitzpatrick_scale:false,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:"🦒",fitzpatrick_scale:false,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:"ðŸ˜",fitzpatrick_scale:false,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:"ðŸ¦",fitzpatrick_scale:false,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:"ðŸ",fitzpatrick_scale:false,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:"ðŸ",fitzpatrick_scale:false,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:"ðŸ‘",fitzpatrick_scale:false,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:"ðŸŽ",fitzpatrick_scale:false,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:"ðŸ–",fitzpatrick_scale:false,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:"ðŸ€",fitzpatrick_scale:false,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:"ðŸ",fitzpatrick_scale:false,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:"ðŸ“",fitzpatrick_scale:false,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:"🦃",fitzpatrick_scale:false,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:"🕊",fitzpatrick_scale:false,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:"ðŸ•",fitzpatrick_scale:false,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:"ðŸ©",fitzpatrick_scale:false,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:"ðŸˆ",fitzpatrick_scale:false,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:"ðŸ‡",fitzpatrick_scale:false,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:"ðŸ¿",fitzpatrick_scale:false,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:"🦔",fitzpatrick_scale:false,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:"ðŸ¦",fitzpatrick_scale:false,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:"🦙",fitzpatrick_scale:false,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:"🦛",fitzpatrick_scale:false,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:"🦘",fitzpatrick_scale:false,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:"🦡",fitzpatrick_scale:false,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:"🦢",fitzpatrick_scale:false,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:"🦚",fitzpatrick_scale:false,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:"🦜",fitzpatrick_scale:false,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:"🦞",fitzpatrick_scale:false,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:"🦟",fitzpatrick_scale:false,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:"ðŸ¾",fitzpatrick_scale:false,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:"ðŸ‰",fitzpatrick_scale:false,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:"ðŸ²",fitzpatrick_scale:false,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:"🌵",fitzpatrick_scale:false,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:"🎄",fitzpatrick_scale:false,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:"🌲",fitzpatrick_scale:false,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:"🌳",fitzpatrick_scale:false,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:"🌴",fitzpatrick_scale:false,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:"🌱",fitzpatrick_scale:false,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:"🌿",fitzpatrick_scale:false,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:"☘",fitzpatrick_scale:false,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:"ðŸ€",fitzpatrick_scale:false,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:"ðŸŽ",fitzpatrick_scale:false,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:"🎋",fitzpatrick_scale:false,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:"ðŸƒ",fitzpatrick_scale:false,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:"ðŸ‚",fitzpatrick_scale:false,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:"ðŸ",fitzpatrick_scale:false,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:"🌾",fitzpatrick_scale:false,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:"🌺",fitzpatrick_scale:false,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:"🌻",fitzpatrick_scale:false,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:"🌹",fitzpatrick_scale:false,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:"🥀",fitzpatrick_scale:false,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:"🌷",fitzpatrick_scale:false,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:"🌼",fitzpatrick_scale:false,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:"🌸",fitzpatrick_scale:false,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:"ðŸ’",fitzpatrick_scale:false,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:"ðŸ„",fitzpatrick_scale:false,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:"🌰",fitzpatrick_scale:false,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:"🎃",fitzpatrick_scale:false,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:"ðŸš",fitzpatrick_scale:false,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:"🕸",fitzpatrick_scale:false,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:"🌎",fitzpatrick_scale:false,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:"ðŸŒ",fitzpatrick_scale:false,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:"ðŸŒ",fitzpatrick_scale:false,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:"🌕",fitzpatrick_scale:false,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:"🌖",fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌗",fitzpatrick_scale:false,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌘",fitzpatrick_scale:false,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌑",fitzpatrick_scale:false,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌒",fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌓",fitzpatrick_scale:false,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:"🌔",fitzpatrick_scale:false,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌚",fitzpatrick_scale:false,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"ðŸŒ",fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌛",fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌜",fitzpatrick_scale:false,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:"🌞",fitzpatrick_scale:false,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:"🌙",fitzpatrick_scale:false,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:"â­",fitzpatrick_scale:false,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:"🌟",fitzpatrick_scale:false,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:"💫",fitzpatrick_scale:false,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:"✨",fitzpatrick_scale:false,category:"animals_and_nature"},comet:{keywords:["space"],char:"☄",fitzpatrick_scale:false,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:"☀ï¸",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:"🌤",fitzpatrick_scale:false,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:"â›…",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:"🌥",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:"🌦",fitzpatrick_scale:false,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:"â˜ï¸",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:"🌧",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:"⛈",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:"🌩",fitzpatrick_scale:false,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:"âš¡",fitzpatrick_scale:false,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:"🔥",fitzpatrick_scale:false,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:"💥",fitzpatrick_scale:false,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:"â„ï¸",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:"🌨",fitzpatrick_scale:false,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:"⛄",fitzpatrick_scale:false,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:"☃",fitzpatrick_scale:false,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:"🌬",fitzpatrick_scale:false,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:"💨",fitzpatrick_scale:false,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:"🌪",fitzpatrick_scale:false,category:"animals_and_nature"},fog:{keywords:["weather"],char:"🌫",fitzpatrick_scale:false,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:"☂",fitzpatrick_scale:false,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:"☔",fitzpatrick_scale:false,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:"💧",fitzpatrick_scale:false,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:"💦",fitzpatrick_scale:false,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:"🌊",fitzpatrick_scale:false,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:"ðŸ",fitzpatrick_scale:false,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:"ðŸŽ",fitzpatrick_scale:false,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:"ðŸ",fitzpatrick_scale:false,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:"ðŸŠ",fitzpatrick_scale:false,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:"ðŸ‹",fitzpatrick_scale:false,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:"ðŸŒ",fitzpatrick_scale:false,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:"ðŸ‰",fitzpatrick_scale:false,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:"ðŸ‡",fitzpatrick_scale:false,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:"ðŸ“",fitzpatrick_scale:false,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:"ðŸˆ",fitzpatrick_scale:false,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:"ðŸ’",fitzpatrick_scale:false,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:"ðŸ‘",fitzpatrick_scale:false,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:"ðŸ",fitzpatrick_scale:false,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:"🥥",fitzpatrick_scale:false,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:"ðŸ¥",fitzpatrick_scale:false,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:"🥭",fitzpatrick_scale:false,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:"🥑",fitzpatrick_scale:false,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:"🥦",fitzpatrick_scale:false,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:"ðŸ…",fitzpatrick_scale:false,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:"ðŸ†",fitzpatrick_scale:false,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:"🥒",fitzpatrick_scale:false,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:"🥕",fitzpatrick_scale:false,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:"🌶",fitzpatrick_scale:false,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:"🥔",fitzpatrick_scale:false,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:"🌽",fitzpatrick_scale:false,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:"🥬",fitzpatrick_scale:false,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:"ðŸ ",fitzpatrick_scale:false,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:"🥜",fitzpatrick_scale:false,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:"ðŸ¯",fitzpatrick_scale:false,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:"ðŸ¥",fitzpatrick_scale:false,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:"ðŸž",fitzpatrick_scale:false,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:"🥖",fitzpatrick_scale:false,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:"🥯",fitzpatrick_scale:false,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:"🥨",fitzpatrick_scale:false,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:"🧀",fitzpatrick_scale:false,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:"🥚",fitzpatrick_scale:false,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:"🥓",fitzpatrick_scale:false,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:"🥩",fitzpatrick_scale:false,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:"🥞",fitzpatrick_scale:false,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:"ðŸ—",fitzpatrick_scale:false,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:"ðŸ–",fitzpatrick_scale:false,category:"food_and_drink"},bone:{keywords:["skeleton"],char:"🦴",fitzpatrick_scale:false,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:"ðŸ¤",fitzpatrick_scale:false,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:"ðŸ³",fitzpatrick_scale:false,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:"ðŸ”",fitzpatrick_scale:false,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:"ðŸŸ",fitzpatrick_scale:false,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:"🥙",fitzpatrick_scale:false,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:"🌭",fitzpatrick_scale:false,category:"food_and_drink"},pizza:{keywords:["food","party"],char:"ðŸ•",fitzpatrick_scale:false,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:"🥪",fitzpatrick_scale:false,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:"🥫",fitzpatrick_scale:false,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:"ðŸ",fitzpatrick_scale:false,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:"🌮",fitzpatrick_scale:false,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:"🌯",fitzpatrick_scale:false,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:"🥗",fitzpatrick_scale:false,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:"🥘",fitzpatrick_scale:false,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:"ðŸœ",fitzpatrick_scale:false,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:"ðŸ²",fitzpatrick_scale:false,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:"ðŸ¥",fitzpatrick_scale:false,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:"🥠",fitzpatrick_scale:false,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:"ðŸ£",fitzpatrick_scale:false,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:"ðŸ±",fitzpatrick_scale:false,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:"ðŸ›",fitzpatrick_scale:false,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:"ðŸ™",fitzpatrick_scale:false,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:"ðŸš",fitzpatrick_scale:false,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:"ðŸ˜",fitzpatrick_scale:false,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:"ðŸ¢",fitzpatrick_scale:false,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:"ðŸ¡",fitzpatrick_scale:false,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:"ðŸ§",fitzpatrick_scale:false,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:"ðŸ¨",fitzpatrick_scale:false,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:"ðŸ¦",fitzpatrick_scale:false,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:"🥧",fitzpatrick_scale:false,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:"ðŸ°",fitzpatrick_scale:false,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:"ðŸ§",fitzpatrick_scale:false,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:"🥮",fitzpatrick_scale:false,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:"🎂",fitzpatrick_scale:false,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:"ðŸ®",fitzpatrick_scale:false,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:"ðŸ¬",fitzpatrick_scale:false,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:"ðŸ­",fitzpatrick_scale:false,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:"ðŸ«",fitzpatrick_scale:false,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:"ðŸ¿",fitzpatrick_scale:false,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:"🥟",fitzpatrick_scale:false,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:"ðŸ©",fitzpatrick_scale:false,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:"ðŸª",fitzpatrick_scale:false,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:"🥛",fitzpatrick_scale:false,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"ðŸº",fitzpatrick_scale:false,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"ðŸ»",fitzpatrick_scale:false,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:"🥂",fitzpatrick_scale:false,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:"ðŸ·",fitzpatrick_scale:false,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:"🥃",fitzpatrick_scale:false,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:"ðŸ¸",fitzpatrick_scale:false,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:"ðŸ¹",fitzpatrick_scale:false,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:"ðŸ¾",fitzpatrick_scale:false,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:"ðŸ¶",fitzpatrick_scale:false,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:"ðŸµ",fitzpatrick_scale:false,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:"🥤",fitzpatrick_scale:false,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:"☕",fitzpatrick_scale:false,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:"ðŸ¼",fitzpatrick_scale:false,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:"🧂",fitzpatrick_scale:false,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:"🥄",fitzpatrick_scale:false,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:"ðŸ´",fitzpatrick_scale:false,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:"ðŸ½",fitzpatrick_scale:false,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:"🥣",fitzpatrick_scale:false,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:"🥡",fitzpatrick_scale:false,category:"food_and_drink"},chopsticks:{keywords:["food"],char:"🥢",fitzpatrick_scale:false,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:"âš½",fitzpatrick_scale:false,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:"ðŸ€",fitzpatrick_scale:false,category:"activity"},football:{keywords:["sports","balls","NFL"],char:"ðŸˆ",fitzpatrick_scale:false,category:"activity"},baseball:{keywords:["sports","balls"],char:"âš¾",fitzpatrick_scale:false,category:"activity"},softball:{keywords:["sports","balls"],char:"🥎",fitzpatrick_scale:false,category:"activity"},tennis:{keywords:["sports","balls","green"],char:"🎾",fitzpatrick_scale:false,category:"activity"},volleyball:{keywords:["sports","balls"],char:"ðŸ",fitzpatrick_scale:false,category:"activity"},rugby_football:{keywords:["sports","team"],char:"ðŸ‰",fitzpatrick_scale:false,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:"ðŸ¥",fitzpatrick_scale:false,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:"🎱",fitzpatrick_scale:false,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:"⛳",fitzpatrick_scale:false,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:"ðŸŒï¸â€â™€ï¸",fitzpatrick_scale:false,category:"activity"},golfing_man:{keywords:["sports","business"],char:"ðŸŒ",fitzpatrick_scale:true,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:"ðŸ“",fitzpatrick_scale:false,category:"activity"},badminton:{keywords:["sports"],char:"ðŸ¸",fitzpatrick_scale:false,category:"activity"},goal_net:{keywords:["sports"],char:"🥅",fitzpatrick_scale:false,category:"activity"},ice_hockey:{keywords:["sports"],char:"ðŸ’",fitzpatrick_scale:false,category:"activity"},field_hockey:{keywords:["sports"],char:"ðŸ‘",fitzpatrick_scale:false,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:"ðŸ¥",fitzpatrick_scale:false,category:"activity"},cricket:{keywords:["sports"],char:"ðŸ",fitzpatrick_scale:false,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:"🎿",fitzpatrick_scale:false,category:"activity"},skier:{keywords:["sports","winter","snow"],char:"â›·",fitzpatrick_scale:false,category:"activity"},snowboarder:{keywords:["sports","winter"],char:"ðŸ‚",fitzpatrick_scale:true,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:"🤺",fitzpatrick_scale:false,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:"🤼â€â™€ï¸",fitzpatrick_scale:false,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:"🤼â€â™‚ï¸",fitzpatrick_scale:false,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:"🤸â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:"🤸â€â™‚ï¸",fitzpatrick_scale:true,category:"activity"},woman_playing_handball:{keywords:["sports"],char:"🤾â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},man_playing_handball:{keywords:["sports"],char:"🤾â€â™‚ï¸",fitzpatrick_scale:true,category:"activity"},ice_skate:{keywords:["sports"],char:"⛸",fitzpatrick_scale:false,category:"activity"},curling_stone:{keywords:["sports"],char:"🥌",fitzpatrick_scale:false,category:"activity"},skateboard:{keywords:["board"],char:"🛹",fitzpatrick_scale:false,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:"🛷",fitzpatrick_scale:false,category:"activity"},bow_and_arrow:{keywords:["sports"],char:"ðŸ¹",fitzpatrick_scale:false,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:"🎣",fitzpatrick_scale:false,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:"🥊",fitzpatrick_scale:false,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:"🥋",fitzpatrick_scale:false,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:"🚣â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:"🚣",fitzpatrick_scale:true,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:"🧗â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:"🧗â€â™‚ï¸",fitzpatrick_scale:true,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:"ðŸŠâ€â™€ï¸",fitzpatrick_scale:true,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:"ðŸŠ",fitzpatrick_scale:true,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:"🤽â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:"🤽â€â™‚ï¸",fitzpatrick_scale:true,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:"🧘â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:"🧘â€â™‚ï¸",fitzpatrick_scale:true,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:"ðŸ„â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:"ðŸ„",fitzpatrick_scale:true,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:"🛀",fitzpatrick_scale:true,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:"⛹ï¸â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},basketball_man:{keywords:["sports","human"],char:"⛹",fitzpatrick_scale:true,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:"ðŸ‹ï¸â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:"ðŸ‹",fitzpatrick_scale:true,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:"🚴â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:"🚴",fitzpatrick_scale:true,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:"🚵â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:"🚵",fitzpatrick_scale:true,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:"ðŸ‡",fitzpatrick_scale:true,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:"🕴",fitzpatrick_scale:true,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:"ðŸ†",fitzpatrick_scale:false,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:"🎽",fitzpatrick_scale:false,category:"activity"},medal_sports:{keywords:["award","winning"],char:"ðŸ…",fitzpatrick_scale:false,category:"activity"},medal_military:{keywords:["award","winning","army"],char:"🎖",fitzpatrick_scale:false,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:"🥇",fitzpatrick_scale:false,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:"🥈",fitzpatrick_scale:false,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:"🥉",fitzpatrick_scale:false,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:"🎗",fitzpatrick_scale:false,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:"ðŸµ",fitzpatrick_scale:false,category:"activity"},ticket:{keywords:["event","concert","pass"],char:"🎫",fitzpatrick_scale:false,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:"🎟",fitzpatrick_scale:false,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:"🎭",fitzpatrick_scale:false,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:"🎨",fitzpatrick_scale:false,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:"🎪",fitzpatrick_scale:false,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:"🤹â€â™€ï¸",fitzpatrick_scale:true,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:"🤹â€â™‚ï¸",fitzpatrick_scale:true,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:"🎤",fitzpatrick_scale:false,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:"🎧",fitzpatrick_scale:false,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:"🎼",fitzpatrick_scale:false,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:"🎹",fitzpatrick_scale:false,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:"ðŸ¥",fitzpatrick_scale:false,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:"🎷",fitzpatrick_scale:false,category:"activity"},trumpet:{keywords:["music","brass"],char:"🎺",fitzpatrick_scale:false,category:"activity"},guitar:{keywords:["music","instrument"],char:"🎸",fitzpatrick_scale:false,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:"🎻",fitzpatrick_scale:false,category:"activity"},clapper:{keywords:["movie","film","record"],char:"🎬",fitzpatrick_scale:false,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:"🎮",fitzpatrick_scale:false,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:"👾",fitzpatrick_scale:false,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:"🎯",fitzpatrick_scale:false,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:"🎲",fitzpatrick_scale:false,category:"activity"},chess_pawn:{keywords:["expendable"],char:"♟",fitzpatrick_scale:false,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:"🎰",fitzpatrick_scale:false,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:"🧩",fitzpatrick_scale:false,category:"activity"},bowling:{keywords:["sports","fun","play"],char:"🎳",fitzpatrick_scale:false,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:"🚗",fitzpatrick_scale:false,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:"🚕",fitzpatrick_scale:false,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:"🚙",fitzpatrick_scale:false,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:"🚌",fitzpatrick_scale:false,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:"🚎",fitzpatrick_scale:false,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:"ðŸŽ",fitzpatrick_scale:false,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:"🚓",fitzpatrick_scale:false,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:"🚑",fitzpatrick_scale:false,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:"🚒",fitzpatrick_scale:false,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:"ðŸš",fitzpatrick_scale:false,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:"🚚",fitzpatrick_scale:false,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:"🚛",fitzpatrick_scale:false,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:"🚜",fitzpatrick_scale:false,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:"🛴",fitzpatrick_scale:false,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:"ðŸ",fitzpatrick_scale:false,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:"🚲",fitzpatrick_scale:false,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:"🛵",fitzpatrick_scale:false,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:"🚨",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:"🚔",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:"ðŸš",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:"🚘",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:"🚖",fitzpatrick_scale:false,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:"🚡",fitzpatrick_scale:false,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:"🚠",fitzpatrick_scale:false,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:"🚟",fitzpatrick_scale:false,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:"🚃",fitzpatrick_scale:false,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:"🚋",fitzpatrick_scale:false,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:"ðŸš",fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:"🚄",fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:"🚅",fitzpatrick_scale:false,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:"🚈",fitzpatrick_scale:false,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:"🚞",fitzpatrick_scale:false,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:"🚂",fitzpatrick_scale:false,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:"🚆",fitzpatrick_scale:false,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:"🚇",fitzpatrick_scale:false,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:"🚊",fitzpatrick_scale:false,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:"🚉",fitzpatrick_scale:false,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:"🛸",fitzpatrick_scale:false,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:"ðŸš",fitzpatrick_scale:false,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:"🛩",fitzpatrick_scale:false,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:"✈ï¸",fitzpatrick_scale:false,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:"🛫",fitzpatrick_scale:false,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:"🛬",fitzpatrick_scale:false,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:"⛵",fitzpatrick_scale:false,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:"🛥",fitzpatrick_scale:false,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:"🚤",fitzpatrick_scale:false,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:"â›´",fitzpatrick_scale:false,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:"🛳",fitzpatrick_scale:false,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:"🚀",fitzpatrick_scale:false,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:"🛰",fitzpatrick_scale:false,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:"💺",fitzpatrick_scale:false,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:"🛶",fitzpatrick_scale:false,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:"âš“",fitzpatrick_scale:false,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:"🚧",fitzpatrick_scale:false,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:"⛽",fitzpatrick_scale:false,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:"ðŸš",fitzpatrick_scale:false,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:"🚦",fitzpatrick_scale:false,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:"🚥",fitzpatrick_scale:false,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:"ðŸ",fitzpatrick_scale:false,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:"🚢",fitzpatrick_scale:false,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:"🎡",fitzpatrick_scale:false,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:"🎢",fitzpatrick_scale:false,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:"🎠",fitzpatrick_scale:false,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:"ðŸ—",fitzpatrick_scale:false,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:"ðŸŒ",fitzpatrick_scale:false,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:"🗼",fitzpatrick_scale:false,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:"ðŸ­",fitzpatrick_scale:false,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:"⛲",fitzpatrick_scale:false,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:"🎑",fitzpatrick_scale:false,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:"â›°",fitzpatrick_scale:false,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:"ðŸ”",fitzpatrick_scale:false,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:"🗻",fitzpatrick_scale:false,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:"🌋",fitzpatrick_scale:false,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:"🗾",fitzpatrick_scale:false,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:"ðŸ•",fitzpatrick_scale:false,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:"⛺",fitzpatrick_scale:false,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:"ðŸž",fitzpatrick_scale:false,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:"🛣",fitzpatrick_scale:false,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:"🛤",fitzpatrick_scale:false,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:"🌅",fitzpatrick_scale:false,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:"🌄",fitzpatrick_scale:false,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:"ðŸœ",fitzpatrick_scale:false,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:"ðŸ–",fitzpatrick_scale:false,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:"ðŸ",fitzpatrick_scale:false,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:"🌇",fitzpatrick_scale:false,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:"🌆",fitzpatrick_scale:false,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:"ðŸ™",fitzpatrick_scale:false,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:"🌃",fitzpatrick_scale:false,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:"🌉",fitzpatrick_scale:false,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:"🌌",fitzpatrick_scale:false,category:"travel_and_places"},stars:{keywords:["night","photo"],char:"🌠",fitzpatrick_scale:false,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:"🎇",fitzpatrick_scale:false,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:"🎆",fitzpatrick_scale:false,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:"🌈",fitzpatrick_scale:false,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:"ðŸ˜",fitzpatrick_scale:false,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:"ðŸ°",fitzpatrick_scale:false,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:"ðŸ¯",fitzpatrick_scale:false,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:"ðŸŸ",fitzpatrick_scale:false,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:"🗽",fitzpatrick_scale:false,category:"travel_and_places"},house:{keywords:["building","home"],char:"ðŸ ",fitzpatrick_scale:false,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:"ðŸ¡",fitzpatrick_scale:false,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:"ðŸš",fitzpatrick_scale:false,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:"ðŸ¢",fitzpatrick_scale:false,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:"ðŸ¬",fitzpatrick_scale:false,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:"ðŸ£",fitzpatrick_scale:false,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:"ðŸ¤",fitzpatrick_scale:false,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:"ðŸ¥",fitzpatrick_scale:false,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:"ðŸ¦",fitzpatrick_scale:false,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:"ðŸ¨",fitzpatrick_scale:false,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:"ðŸª",fitzpatrick_scale:false,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:"ðŸ«",fitzpatrick_scale:false,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:"ðŸ©",fitzpatrick_scale:false,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:"💒",fitzpatrick_scale:false,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:"ðŸ›",fitzpatrick_scale:false,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:"⛪",fitzpatrick_scale:false,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:"🕌",fitzpatrick_scale:false,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:"ðŸ•",fitzpatrick_scale:false,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:"🕋",fitzpatrick_scale:false,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:"⛩",fitzpatrick_scale:false,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:"⌚",fitzpatrick_scale:false,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:"📱",fitzpatrick_scale:false,category:"objects"},calling:{keywords:["iphone","incoming"],char:"📲",fitzpatrick_scale:false,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:"💻",fitzpatrick_scale:false,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:"⌨",fitzpatrick_scale:false,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:"🖥",fitzpatrick_scale:false,category:"objects"},printer:{keywords:["paper","ink"],char:"🖨",fitzpatrick_scale:false,category:"objects"},computer_mouse:{keywords:["click"],char:"🖱",fitzpatrick_scale:false,category:"objects"},trackball:{keywords:["technology","trackpad"],char:"🖲",fitzpatrick_scale:false,category:"objects"},joystick:{keywords:["game","play"],char:"🕹",fitzpatrick_scale:false,category:"objects"},clamp:{keywords:["tool"],char:"🗜",fitzpatrick_scale:false,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:"💽",fitzpatrick_scale:false,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:"💾",fitzpatrick_scale:false,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:"💿",fitzpatrick_scale:false,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:"📀",fitzpatrick_scale:false,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:"📼",fitzpatrick_scale:false,category:"objects"},camera:{keywords:["gadgets","photography"],char:"📷",fitzpatrick_scale:false,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:"📸",fitzpatrick_scale:false,category:"objects"},video_camera:{keywords:["film","record"],char:"📹",fitzpatrick_scale:false,category:"objects"},movie_camera:{keywords:["film","record"],char:"🎥",fitzpatrick_scale:false,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:"📽",fitzpatrick_scale:false,category:"objects"},film_strip:{keywords:["movie"],char:"🎞",fitzpatrick_scale:false,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:"📞",fitzpatrick_scale:false,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:"☎ï¸",fitzpatrick_scale:false,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:"📟",fitzpatrick_scale:false,category:"objects"},fax:{keywords:["communication","technology"],char:"📠",fitzpatrick_scale:false,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:"📺",fitzpatrick_scale:false,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:"📻",fitzpatrick_scale:false,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:"🎙",fitzpatrick_scale:false,category:"objects"},level_slider:{keywords:["scale"],char:"🎚",fitzpatrick_scale:false,category:"objects"},control_knobs:{keywords:["dial"],char:"🎛",fitzpatrick_scale:false,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:"🧭",fitzpatrick_scale:false,category:"objects"},stopwatch:{keywords:["time","deadline"],char:"â±",fitzpatrick_scale:false,category:"objects"},timer_clock:{keywords:["alarm"],char:"â²",fitzpatrick_scale:false,category:"objects"},alarm_clock:{keywords:["time","wake"],char:"â°",fitzpatrick_scale:false,category:"objects"},mantelpiece_clock:{keywords:["time"],char:"🕰",fitzpatrick_scale:false,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:"â³",fitzpatrick_scale:false,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:"⌛",fitzpatrick_scale:false,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:"📡",fitzpatrick_scale:false,category:"objects"},battery:{keywords:["power","energy","sustain"],char:"🔋",fitzpatrick_scale:false,category:"objects"},electric_plug:{keywords:["charger","power"],char:"🔌",fitzpatrick_scale:false,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:"💡",fitzpatrick_scale:false,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:"🔦",fitzpatrick_scale:false,category:"objects"},candle:{keywords:["fire","wax"],char:"🕯",fitzpatrick_scale:false,category:"objects"},fire_extinguisher:{keywords:["quench"],char:"🧯",fitzpatrick_scale:false,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:"🗑",fitzpatrick_scale:false,category:"objects"},oil_drum:{keywords:["barrell"],char:"🛢",fitzpatrick_scale:false,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:"💸",fitzpatrick_scale:false,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:"💵",fitzpatrick_scale:false,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:"💴",fitzpatrick_scale:false,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:"💶",fitzpatrick_scale:false,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:"💷",fitzpatrick_scale:false,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:"💰",fitzpatrick_scale:false,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:"💳",fitzpatrick_scale:false,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:"💎",fitzpatrick_scale:false,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:"âš–",fitzpatrick_scale:false,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:"🧰",fitzpatrick_scale:false,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:"🔧",fitzpatrick_scale:false,category:"objects"},hammer:{keywords:["tools","build","create"],char:"🔨",fitzpatrick_scale:false,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:"âš’",fitzpatrick_scale:false,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:"🛠",fitzpatrick_scale:false,category:"objects"},pick:{keywords:["tools","dig"],char:"â›",fitzpatrick_scale:false,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:"🔩",fitzpatrick_scale:false,category:"objects"},gear:{keywords:["cog"],char:"âš™",fitzpatrick_scale:false,category:"objects"},brick:{keywords:["bricks"],char:"🧱",fitzpatrick_scale:false,category:"objects"},chains:{keywords:["lock","arrest"],char:"⛓",fitzpatrick_scale:false,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:"🧲",fitzpatrick_scale:false,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:"🔫",fitzpatrick_scale:false,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:"💣",fitzpatrick_scale:false,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:"🧨",fitzpatrick_scale:false,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:"🔪",fitzpatrick_scale:false,category:"objects"},dagger:{keywords:["weapon"],char:"🗡",fitzpatrick_scale:false,category:"objects"},crossed_swords:{keywords:["weapon"],char:"âš”",fitzpatrick_scale:false,category:"objects"},shield:{keywords:["protection","security"],char:"🛡",fitzpatrick_scale:false,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:"🚬",fitzpatrick_scale:false,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:"☠",fitzpatrick_scale:false,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:"âš°",fitzpatrick_scale:false,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:"âš±",fitzpatrick_scale:false,category:"objects"},amphora:{keywords:["vase","jar"],char:"ðŸº",fitzpatrick_scale:false,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:"🔮",fitzpatrick_scale:false,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:"📿",fitzpatrick_scale:false,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:"🧿",fitzpatrick_scale:false,category:"objects"},barber:{keywords:["hair","salon","style"],char:"💈",fitzpatrick_scale:false,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:"âš—",fitzpatrick_scale:false,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:"🔭",fitzpatrick_scale:false,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:"🔬",fitzpatrick_scale:false,category:"objects"},hole:{keywords:["embarrassing"],char:"🕳",fitzpatrick_scale:false,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:"💊",fitzpatrick_scale:false,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:"💉",fitzpatrick_scale:false,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:"🧬",fitzpatrick_scale:false,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:"🦠",fitzpatrick_scale:false,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:"🧫",fitzpatrick_scale:false,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:"🧪",fitzpatrick_scale:false,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:"🌡",fitzpatrick_scale:false,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:"🧹",fitzpatrick_scale:false,category:"objects"},basket:{keywords:["laundry"],char:"🧺",fitzpatrick_scale:false,category:"objects"},toilet_paper:{keywords:["roll"],char:"🧻",fitzpatrick_scale:false,category:"objects"},label:{keywords:["sale","tag"],char:"ðŸ·",fitzpatrick_scale:false,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:"🔖",fitzpatrick_scale:false,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:"🚽",fitzpatrick_scale:false,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:"🚿",fitzpatrick_scale:false,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:"ðŸ›",fitzpatrick_scale:false,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:"🧼",fitzpatrick_scale:false,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:"🧽",fitzpatrick_scale:false,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:"🧴",fitzpatrick_scale:false,category:"objects"},key:{keywords:["lock","door","password"],char:"🔑",fitzpatrick_scale:false,category:"objects"},old_key:{keywords:["lock","door","password"],char:"ðŸ—",fitzpatrick_scale:false,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:"🛋",fitzpatrick_scale:false,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:"🛌",fitzpatrick_scale:true,category:"objects"},bed:{keywords:["sleep","rest"],char:"ðŸ›",fitzpatrick_scale:false,category:"objects"},door:{keywords:["house","entry","exit"],char:"🚪",fitzpatrick_scale:false,category:"objects"},bellhop_bell:{keywords:["service"],char:"🛎",fitzpatrick_scale:false,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:"🧸",fitzpatrick_scale:false,category:"objects"},framed_picture:{keywords:["photography"],char:"🖼",fitzpatrick_scale:false,category:"objects"},world_map:{keywords:["location","direction"],char:"🗺",fitzpatrick_scale:false,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:"â›±",fitzpatrick_scale:false,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:"🗿",fitzpatrick_scale:false,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:"ðŸ›",fitzpatrick_scale:false,category:"objects"},shopping_cart:{keywords:["trolley"],char:"🛒",fitzpatrick_scale:false,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:"🎈",fitzpatrick_scale:false,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:"ðŸŽ",fitzpatrick_scale:false,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:"🎀",fitzpatrick_scale:false,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:"ðŸŽ",fitzpatrick_scale:false,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:"🎊",fitzpatrick_scale:false,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:"🎉",fitzpatrick_scale:false,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:"🎎",fitzpatrick_scale:false,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:"ðŸŽ",fitzpatrick_scale:false,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:"🎌",fitzpatrick_scale:false,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:"ðŸ®",fitzpatrick_scale:false,category:"objects"},red_envelope:{keywords:["gift"],char:"🧧",fitzpatrick_scale:false,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:"✉ï¸",fitzpatrick_scale:false,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:"📩",fitzpatrick_scale:false,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:"📨",fitzpatrick_scale:false,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:"📧",fitzpatrick_scale:false,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:"💌",fitzpatrick_scale:false,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:"📮",fitzpatrick_scale:false,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:"📪",fitzpatrick_scale:false,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:"📫",fitzpatrick_scale:false,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:"📬",fitzpatrick_scale:false,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:"📭",fitzpatrick_scale:false,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:"📦",fitzpatrick_scale:false,category:"objects"},postal_horn:{keywords:["instrument","music"],char:"📯",fitzpatrick_scale:false,category:"objects"},inbox_tray:{keywords:["email","documents"],char:"📥",fitzpatrick_scale:false,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:"📤",fitzpatrick_scale:false,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:"📜",fitzpatrick_scale:false,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:"📃",fitzpatrick_scale:false,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:"📑",fitzpatrick_scale:false,category:"objects"},receipt:{keywords:["accounting","expenses"],char:"🧾",fitzpatrick_scale:false,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:"📊",fitzpatrick_scale:false,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:"📈",fitzpatrick_scale:false,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:"📉",fitzpatrick_scale:false,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:"📄",fitzpatrick_scale:false,category:"objects"},date:{keywords:["calendar","schedule"],char:"📅",fitzpatrick_scale:false,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:"📆",fitzpatrick_scale:false,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:"🗓",fitzpatrick_scale:false,category:"objects"},card_index:{keywords:["business","stationery"],char:"📇",fitzpatrick_scale:false,category:"objects"},card_file_box:{keywords:["business","stationery"],char:"🗃",fitzpatrick_scale:false,category:"objects"},ballot_box:{keywords:["election","vote"],char:"🗳",fitzpatrick_scale:false,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:"🗄",fitzpatrick_scale:false,category:"objects"},clipboard:{keywords:["stationery","documents"],char:"📋",fitzpatrick_scale:false,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:"🗒",fitzpatrick_scale:false,category:"objects"},file_folder:{keywords:["documents","business","office"],char:"ðŸ“",fitzpatrick_scale:false,category:"objects"},open_file_folder:{keywords:["documents","load"],char:"📂",fitzpatrick_scale:false,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:"🗂",fitzpatrick_scale:false,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:"🗞",fitzpatrick_scale:false,category:"objects"},newspaper:{keywords:["press","headline"],char:"📰",fitzpatrick_scale:false,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:"📓",fitzpatrick_scale:false,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:"📕",fitzpatrick_scale:false,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:"📗",fitzpatrick_scale:false,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:"📘",fitzpatrick_scale:false,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:"📙",fitzpatrick_scale:false,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:"📔",fitzpatrick_scale:false,category:"objects"},ledger:{keywords:["notes","paper"],char:"📒",fitzpatrick_scale:false,category:"objects"},books:{keywords:["literature","library","study"],char:"📚",fitzpatrick_scale:false,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:"📖",fitzpatrick_scale:false,category:"objects"},safety_pin:{keywords:["diaper"],char:"🧷",fitzpatrick_scale:false,category:"objects"},link:{keywords:["rings","url"],char:"🔗",fitzpatrick_scale:false,category:"objects"},paperclip:{keywords:["documents","stationery"],char:"📎",fitzpatrick_scale:false,category:"objects"},paperclips:{keywords:["documents","stationery"],char:"🖇",fitzpatrick_scale:false,category:"objects"},scissors:{keywords:["stationery","cut"],char:"✂ï¸",fitzpatrick_scale:false,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:"ðŸ“",fitzpatrick_scale:false,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:"ðŸ“",fitzpatrick_scale:false,category:"objects"},abacus:{keywords:["calculation"],char:"🧮",fitzpatrick_scale:false,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:"📌",fitzpatrick_scale:false,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:"ðŸ“",fitzpatrick_scale:false,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:"🚩",fitzpatrick_scale:false,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:"ðŸ³",fitzpatrick_scale:false,category:"objects"},black_flag:{keywords:["pirate"],char:"ðŸ´",fitzpatrick_scale:false,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:"ðŸ³ï¸â€ðŸŒˆ",fitzpatrick_scale:false,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:"ðŸ”",fitzpatrick_scale:false,category:"objects"},lock:{keywords:["security","password","padlock"],char:"🔒",fitzpatrick_scale:false,category:"objects"},unlock:{keywords:["privacy","security"],char:"🔓",fitzpatrick_scale:false,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:"ðŸ”",fitzpatrick_scale:false,category:"objects"},pen:{keywords:["stationery","writing","write"],char:"🖊",fitzpatrick_scale:false,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:"🖋",fitzpatrick_scale:false,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:"✒ï¸",fitzpatrick_scale:false,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:"ðŸ“",fitzpatrick_scale:false,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:"âœï¸",fitzpatrick_scale:false,category:"objects"},crayon:{keywords:["drawing","creativity"],char:"ðŸ–",fitzpatrick_scale:false,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:"🖌",fitzpatrick_scale:false,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:"ðŸ”",fitzpatrick_scale:false,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:"🔎",fitzpatrick_scale:false,category:"objects"},heart:{keywords:["love","like","valentines"],char:"â¤ï¸",fitzpatrick_scale:false,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:"🧡",fitzpatrick_scale:false,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:"💛",fitzpatrick_scale:false,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:"💚",fitzpatrick_scale:false,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:"💙",fitzpatrick_scale:false,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:"💜",fitzpatrick_scale:false,category:"symbols"},black_heart:{keywords:["evil"],char:"🖤",fitzpatrick_scale:false,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:"💔",fitzpatrick_scale:false,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:"â£",fitzpatrick_scale:false,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:"💕",fitzpatrick_scale:false,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:"💞",fitzpatrick_scale:false,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:"💓",fitzpatrick_scale:false,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:"💗",fitzpatrick_scale:false,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:"💖",fitzpatrick_scale:false,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:"💘",fitzpatrick_scale:false,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:"ðŸ’",fitzpatrick_scale:false,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:"💟",fitzpatrick_scale:false,category:"symbols"},peace_symbol:{keywords:["hippie"],char:"☮",fitzpatrick_scale:false,category:"symbols"},latin_cross:{keywords:["christianity"],char:"âœ",fitzpatrick_scale:false,category:"symbols"},star_and_crescent:{keywords:["islam"],char:"☪",fitzpatrick_scale:false,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"🕉",fitzpatrick_scale:false,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"☸",fitzpatrick_scale:false,category:"symbols"},star_of_david:{keywords:["judaism"],char:"✡",fitzpatrick_scale:false,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:"🔯",fitzpatrick_scale:false,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:"🕎",fitzpatrick_scale:false,category:"symbols"},yin_yang:{keywords:["balance"],char:"☯",fitzpatrick_scale:false,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:"☦",fitzpatrick_scale:false,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:"ðŸ›",fitzpatrick_scale:false,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:"⛎",fitzpatrick_scale:false,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:"♈",fitzpatrick_scale:false,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:"♉",fitzpatrick_scale:false,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:"♊",fitzpatrick_scale:false,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:"♋",fitzpatrick_scale:false,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:"♌",fitzpatrick_scale:false,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:"â™",fitzpatrick_scale:false,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:"♎",fitzpatrick_scale:false,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:"â™",fitzpatrick_scale:false,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:"â™",fitzpatrick_scale:false,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:"♑",fitzpatrick_scale:false,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:"â™’",fitzpatrick_scale:false,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:"♓",fitzpatrick_scale:false,category:"symbols"},id:{keywords:["purple-square","words"],char:"🆔",fitzpatrick_scale:false,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:"âš›",fitzpatrick_scale:false,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:"🈳",fitzpatrick_scale:false,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:"🈹",fitzpatrick_scale:false,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:"☢",fitzpatrick_scale:false,category:"symbols"},biohazard:{keywords:["danger"],char:"☣",fitzpatrick_scale:false,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:"📴",fitzpatrick_scale:false,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:"📳",fitzpatrick_scale:false,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:"🈶",fitzpatrick_scale:false,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:"🈚",fitzpatrick_scale:false,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:"🈸",fitzpatrick_scale:false,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:"🈺",fitzpatrick_scale:false,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:"🈷ï¸",fitzpatrick_scale:false,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:"✴ï¸",fitzpatrick_scale:false,category:"symbols"},vs:{keywords:["words","orange-square"],char:"🆚",fitzpatrick_scale:false,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:"🉑",fitzpatrick_scale:false,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:"💮",fitzpatrick_scale:false,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:"ðŸ‰",fitzpatrick_scale:false,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:"㊙ï¸",fitzpatrick_scale:false,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:"㊗ï¸",fitzpatrick_scale:false,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:"🈴",fitzpatrick_scale:false,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:"🈵",fitzpatrick_scale:false,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:"🈲",fitzpatrick_scale:false,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:"🅰ï¸",fitzpatrick_scale:false,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:"🅱ï¸",fitzpatrick_scale:false,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:"🆎",fitzpatrick_scale:false,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:"🆑",fitzpatrick_scale:false,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:"🅾ï¸",fitzpatrick_scale:false,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:"🆘",fitzpatrick_scale:false,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:"â›”",fitzpatrick_scale:false,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:"📛",fitzpatrick_scale:false,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:"🚫",fitzpatrick_scale:false,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:"âŒ",fitzpatrick_scale:false,category:"symbols"},o:{keywords:["circle","round"],char:"â­•",fitzpatrick_scale:false,category:"symbols"},stop_sign:{keywords:["stop"],char:"🛑",fitzpatrick_scale:false,category:"symbols"},anger:{keywords:["angry","mad"],char:"💢",fitzpatrick_scale:false,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:"♨ï¸",fitzpatrick_scale:false,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:"🚷",fitzpatrick_scale:false,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:"🚯",fitzpatrick_scale:false,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:"🚳",fitzpatrick_scale:false,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:"🚱",fitzpatrick_scale:false,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:"🔞",fitzpatrick_scale:false,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:"📵",fitzpatrick_scale:false,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:"â—",fitzpatrick_scale:false,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:"â•",fitzpatrick_scale:false,category:"symbols"},question:{keywords:["doubt","confused"],char:"â“",fitzpatrick_scale:false,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:"â”",fitzpatrick_scale:false,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:"‼ï¸",fitzpatrick_scale:false,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:"â‰ï¸",fitzpatrick_scale:false,category:"symbols"},100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:"💯",fitzpatrick_scale:false,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:"🔅",fitzpatrick_scale:false,category:"symbols"},high_brightness:{keywords:["sun","light"],char:"🔆",fitzpatrick_scale:false,category:"symbols"},trident:{keywords:["weapon","spear"],char:"🔱",fitzpatrick_scale:false,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:"âšœ",fitzpatrick_scale:false,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:"〽ï¸",fitzpatrick_scale:false,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:"âš ï¸",fitzpatrick_scale:false,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:"🚸",fitzpatrick_scale:false,category:"symbols"},beginner:{keywords:["badge","shield"],char:"🔰",fitzpatrick_scale:false,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:"â™»ï¸",fitzpatrick_scale:false,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:"🈯",fitzpatrick_scale:false,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:"💹",fitzpatrick_scale:false,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:"â‡ï¸",fitzpatrick_scale:false,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:"✳ï¸",fitzpatrick_scale:false,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:"âŽ",fitzpatrick_scale:false,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:"✅",fitzpatrick_scale:false,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:"💠",fitzpatrick_scale:false,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:"🌀",fitzpatrick_scale:false,category:"symbols"},loop:{keywords:["tape","cassette"],char:"âž¿",fitzpatrick_scale:false,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:"ðŸŒ",fitzpatrick_scale:false,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:"â“‚ï¸",fitzpatrick_scale:false,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:"ðŸ§",fitzpatrick_scale:false,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:"🈂ï¸",fitzpatrick_scale:false,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:"🛂",fitzpatrick_scale:false,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:"🛃",fitzpatrick_scale:false,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:"🛄",fitzpatrick_scale:false,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:"🛅",fitzpatrick_scale:false,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:"♿",fitzpatrick_scale:false,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:"🚭",fitzpatrick_scale:false,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:"🚾",fitzpatrick_scale:false,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:"🅿ï¸",fitzpatrick_scale:false,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:"🚰",fitzpatrick_scale:false,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:"🚹",fitzpatrick_scale:false,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:"🚺",fitzpatrick_scale:false,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:"🚼",fitzpatrick_scale:false,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:"🚻",fitzpatrick_scale:false,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:"🚮",fitzpatrick_scale:false,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:"🎦",fitzpatrick_scale:false,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:"📶",fitzpatrick_scale:false,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:"ðŸˆ",fitzpatrick_scale:false,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:"🆖",fitzpatrick_scale:false,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:"🆗",fitzpatrick_scale:false,category:"symbols"},up:{keywords:["blue-square","above","high"],char:"🆙",fitzpatrick_scale:false,category:"symbols"},cool:{keywords:["words","blue-square"],char:"🆒",fitzpatrick_scale:false,category:"symbols"},new:{keywords:["blue-square","words","start"],char:"🆕",fitzpatrick_scale:false,category:"symbols"},free:{keywords:["blue-square","words"],char:"🆓",fitzpatrick_scale:false,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:"0ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:"1ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:"2ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:"3ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:"4ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:"5ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:"6ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:"7ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:"8ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:"9ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:"🔟",fitzpatrick_scale:false,category:"symbols"},asterisk:{keywords:["star","keycap"],char:"*⃣",fitzpatrick_scale:false,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:"🔢",fitzpatrick_scale:false,category:"symbols"},eject_button:{keywords:["blue-square"],char:"âï¸",fitzpatrick_scale:false,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:"â–¶ï¸",fitzpatrick_scale:false,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:"â¸",fitzpatrick_scale:false,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:"â­",fitzpatrick_scale:false,category:"symbols"},stop_button:{keywords:["blue-square"],char:"â¹",fitzpatrick_scale:false,category:"symbols"},record_button:{keywords:["blue-square"],char:"âº",fitzpatrick_scale:false,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:"â¯",fitzpatrick_scale:false,category:"symbols"},previous_track_button:{keywords:["backward"],char:"â®",fitzpatrick_scale:false,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:"â©",fitzpatrick_scale:false,category:"symbols"},rewind:{keywords:["play","blue-square"],char:"âª",fitzpatrick_scale:false,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:"🔀",fitzpatrick_scale:false,category:"symbols"},repeat:{keywords:["loop","record"],char:"ðŸ”",fitzpatrick_scale:false,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:"🔂",fitzpatrick_scale:false,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:"â—€ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:"🔼",fitzpatrick_scale:false,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:"🔽",fitzpatrick_scale:false,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:"â«",fitzpatrick_scale:false,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:"â¬",fitzpatrick_scale:false,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:"âž¡ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:"⬅ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:"⬆ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:"⬇ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:"↗ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:"↘ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:"↙ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:"↖ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:"↕ï¸",fitzpatrick_scale:false,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:"↔ï¸",fitzpatrick_scale:false,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:"🔄",fitzpatrick_scale:false,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:"↪ï¸",fitzpatrick_scale:false,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:"↩ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:"⤴ï¸",fitzpatrick_scale:false,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:"⤵ï¸",fitzpatrick_scale:false,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:"#ï¸âƒ£",fitzpatrick_scale:false,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:"ℹï¸",fitzpatrick_scale:false,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:"🔤",fitzpatrick_scale:false,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:"🔡",fitzpatrick_scale:false,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:"🔠",fitzpatrick_scale:false,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:"🔣",fitzpatrick_scale:false,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:"🎵",fitzpatrick_scale:false,category:"symbols"},notes:{keywords:["music","score"],char:"🎶",fitzpatrick_scale:false,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:"〰ï¸",fitzpatrick_scale:false,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:"âž°",fitzpatrick_scale:false,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:"✔ï¸",fitzpatrick_scale:false,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:"🔃",fitzpatrick_scale:false,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:"âž•",fitzpatrick_scale:false,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:"âž–",fitzpatrick_scale:false,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:"âž—",fitzpatrick_scale:false,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:"✖ï¸",fitzpatrick_scale:false,category:"symbols"},infinity:{keywords:["forever"],char:"♾",fitzpatrick_scale:false,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:"💲",fitzpatrick_scale:false,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:"💱",fitzpatrick_scale:false,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:"©ï¸",fitzpatrick_scale:false,category:"symbols"},registered:{keywords:["alphabet","circle"],char:"®ï¸",fitzpatrick_scale:false,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:"â„¢ï¸",fitzpatrick_scale:false,category:"symbols"},end:{keywords:["words","arrow"],char:"🔚",fitzpatrick_scale:false,category:"symbols"},back:{keywords:["arrow","words","return"],char:"🔙",fitzpatrick_scale:false,category:"symbols"},on:{keywords:["arrow","words"],char:"🔛",fitzpatrick_scale:false,category:"symbols"},top:{keywords:["words","blue-square"],char:"ðŸ”",fitzpatrick_scale:false,category:"symbols"},soon:{keywords:["arrow","words"],char:"🔜",fitzpatrick_scale:false,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:"☑ï¸",fitzpatrick_scale:false,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:"🔘",fitzpatrick_scale:false,category:"symbols"},white_circle:{keywords:["shape","round"],char:"⚪",fitzpatrick_scale:false,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:"âš«",fitzpatrick_scale:false,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:"🔴",fitzpatrick_scale:false,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:"🔵",fitzpatrick_scale:false,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:"🔸",fitzpatrick_scale:false,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:"🔹",fitzpatrick_scale:false,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:"🔶",fitzpatrick_scale:false,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:"🔷",fitzpatrick_scale:false,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:"🔺",fitzpatrick_scale:false,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:"â–ªï¸",fitzpatrick_scale:false,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:"â–«ï¸",fitzpatrick_scale:false,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:"⬛",fitzpatrick_scale:false,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:"⬜",fitzpatrick_scale:false,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:"🔻",fitzpatrick_scale:false,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:"â—¼ï¸",fitzpatrick_scale:false,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:"â—»ï¸",fitzpatrick_scale:false,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:"â—¾",fitzpatrick_scale:false,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:"â—½",fitzpatrick_scale:false,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:"🔲",fitzpatrick_scale:false,category:"symbols"},white_square_button:{keywords:["shape","input"],char:"🔳",fitzpatrick_scale:false,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:"🔈",fitzpatrick_scale:false,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:"🔉",fitzpatrick_scale:false,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:"🔊",fitzpatrick_scale:false,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:"🔇",fitzpatrick_scale:false,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:"📣",fitzpatrick_scale:false,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:"📢",fitzpatrick_scale:false,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:"🔔",fitzpatrick_scale:false,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:"🔕",fitzpatrick_scale:false,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:"ðŸƒ",fitzpatrick_scale:false,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:"🀄",fitzpatrick_scale:false,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:"â™ ï¸",fitzpatrick_scale:false,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:"♣ï¸",fitzpatrick_scale:false,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:"♥ï¸",fitzpatrick_scale:false,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:"♦ï¸",fitzpatrick_scale:false,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:"🎴",fitzpatrick_scale:false,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:"💭",fitzpatrick_scale:false,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:"🗯",fitzpatrick_scale:false,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:"💬",fitzpatrick_scale:false,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:"🗨",fitzpatrick_scale:false,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:"ðŸ•",fitzpatrick_scale:false,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:"🕑",fitzpatrick_scale:false,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:"🕒",fitzpatrick_scale:false,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:"🕓",fitzpatrick_scale:false,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:"🕔",fitzpatrick_scale:false,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:"🕕",fitzpatrick_scale:false,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:"🕖",fitzpatrick_scale:false,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:"🕗",fitzpatrick_scale:false,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:"🕘",fitzpatrick_scale:false,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:"🕙",fitzpatrick_scale:false,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:"🕚",fitzpatrick_scale:false,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:"🕛",fitzpatrick_scale:false,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:"🕜",fitzpatrick_scale:false,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:"ðŸ•",fitzpatrick_scale:false,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:"🕞",fitzpatrick_scale:false,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:"🕟",fitzpatrick_scale:false,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:"🕠",fitzpatrick_scale:false,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:"🕡",fitzpatrick_scale:false,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:"🕢",fitzpatrick_scale:false,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:"🕣",fitzpatrick_scale:false,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:"🕤",fitzpatrick_scale:false,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:"🕥",fitzpatrick_scale:false,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:"🕦",fitzpatrick_scale:false,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:"🕧",fitzpatrick_scale:false,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:"🇦🇫",fitzpatrick_scale:false,category:"flags"},aland_islands:{keywords:["Ã…land","islands","flag","nation","country","banner"],char:"🇦🇽",fitzpatrick_scale:false,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:"🇦🇱",fitzpatrick_scale:false,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:"🇩🇿",fitzpatrick_scale:false,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:"🇦🇸",fitzpatrick_scale:false,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:"🇦🇩",fitzpatrick_scale:false,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:"🇦🇴",fitzpatrick_scale:false,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:"🇦🇮",fitzpatrick_scale:false,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:"🇦🇶",fitzpatrick_scale:false,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:"🇦🇬",fitzpatrick_scale:false,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:"🇦🇷",fitzpatrick_scale:false,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:"🇦🇲",fitzpatrick_scale:false,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:"🇦🇼",fitzpatrick_scale:false,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:"🇦🇺",fitzpatrick_scale:false,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:"🇦🇹",fitzpatrick_scale:false,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:"🇦🇿",fitzpatrick_scale:false,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:"🇧🇸",fitzpatrick_scale:false,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:"🇧🇭",fitzpatrick_scale:false,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:"🇧🇩",fitzpatrick_scale:false,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:"🇧🇧",fitzpatrick_scale:false,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:"🇧🇾",fitzpatrick_scale:false,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:"🇧🇪",fitzpatrick_scale:false,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:"🇧🇿",fitzpatrick_scale:false,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:"🇧🇯",fitzpatrick_scale:false,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:"🇧🇲",fitzpatrick_scale:false,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:"🇧🇹",fitzpatrick_scale:false,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:"🇧🇴",fitzpatrick_scale:false,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:"🇧🇶",fitzpatrick_scale:false,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:"🇧🇦",fitzpatrick_scale:false,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:"🇧🇼",fitzpatrick_scale:false,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:"🇧🇷",fitzpatrick_scale:false,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:"🇮🇴",fitzpatrick_scale:false,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:"🇻🇬",fitzpatrick_scale:false,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:"🇧🇳",fitzpatrick_scale:false,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:"🇧🇬",fitzpatrick_scale:false,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:"🇧🇫",fitzpatrick_scale:false,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:"🇧🇮",fitzpatrick_scale:false,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:"🇨🇻",fitzpatrick_scale:false,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:"🇰🇭",fitzpatrick_scale:false,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:"🇨🇲",fitzpatrick_scale:false,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:"🇨🇦",fitzpatrick_scale:false,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:"🇮🇨",fitzpatrick_scale:false,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:"🇰🇾",fitzpatrick_scale:false,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:"🇨🇫",fitzpatrick_scale:false,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:"🇹🇩",fitzpatrick_scale:false,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:"🇨🇱",fitzpatrick_scale:false,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:"🇨🇳",fitzpatrick_scale:false,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:"🇨🇽",fitzpatrick_scale:false,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:"🇨🇨",fitzpatrick_scale:false,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:"🇨🇴",fitzpatrick_scale:false,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:"🇰🇲",fitzpatrick_scale:false,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:"🇨🇬",fitzpatrick_scale:false,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:"🇨🇩",fitzpatrick_scale:false,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:"🇨🇰",fitzpatrick_scale:false,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:"🇨🇷",fitzpatrick_scale:false,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:"🇭🇷",fitzpatrick_scale:false,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:"🇨🇺",fitzpatrick_scale:false,category:"flags"},curacao:{keywords:["curaçao","flag","nation","country","banner"],char:"🇨🇼",fitzpatrick_scale:false,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:"🇨🇾",fitzpatrick_scale:false,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:"🇨🇿",fitzpatrick_scale:false,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:"🇩🇰",fitzpatrick_scale:false,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:"🇩🇯",fitzpatrick_scale:false,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:"🇩🇲",fitzpatrick_scale:false,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:"🇩🇴",fitzpatrick_scale:false,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:"🇪🇨",fitzpatrick_scale:false,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:"🇪🇬",fitzpatrick_scale:false,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:"🇸🇻",fitzpatrick_scale:false,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:"🇬🇶",fitzpatrick_scale:false,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:"🇪🇷",fitzpatrick_scale:false,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:"🇪🇪",fitzpatrick_scale:false,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:"🇪🇹",fitzpatrick_scale:false,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:"🇪🇺",fitzpatrick_scale:false,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:"🇫🇰",fitzpatrick_scale:false,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:"🇫🇴",fitzpatrick_scale:false,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:"🇫🇯",fitzpatrick_scale:false,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:"🇫🇮",fitzpatrick_scale:false,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:"🇫🇷",fitzpatrick_scale:false,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:"🇬🇫",fitzpatrick_scale:false,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:"🇵🇫",fitzpatrick_scale:false,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:"🇹🇫",fitzpatrick_scale:false,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:"🇬🇦",fitzpatrick_scale:false,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:"🇬🇲",fitzpatrick_scale:false,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:"🇬🇪",fitzpatrick_scale:false,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:"🇩🇪",fitzpatrick_scale:false,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:"🇬🇭",fitzpatrick_scale:false,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:"🇬🇮",fitzpatrick_scale:false,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:"🇬🇷",fitzpatrick_scale:false,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:"🇬🇱",fitzpatrick_scale:false,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:"🇬🇩",fitzpatrick_scale:false,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:"🇬🇵",fitzpatrick_scale:false,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:"🇬🇺",fitzpatrick_scale:false,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:"🇬🇹",fitzpatrick_scale:false,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:"🇬🇬",fitzpatrick_scale:false,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:"🇬🇳",fitzpatrick_scale:false,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:"🇬🇼",fitzpatrick_scale:false,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:"🇬🇾",fitzpatrick_scale:false,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:"🇭🇹",fitzpatrick_scale:false,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:"🇭🇳",fitzpatrick_scale:false,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:"🇭🇰",fitzpatrick_scale:false,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:"🇭🇺",fitzpatrick_scale:false,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:"🇮🇸",fitzpatrick_scale:false,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:"🇮🇳",fitzpatrick_scale:false,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:"🇮🇩",fitzpatrick_scale:false,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:"🇮🇷",fitzpatrick_scale:false,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:"🇮🇶",fitzpatrick_scale:false,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:"🇮🇪",fitzpatrick_scale:false,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:"🇮🇲",fitzpatrick_scale:false,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:"🇮🇱",fitzpatrick_scale:false,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:"🇮🇹",fitzpatrick_scale:false,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:"🇨🇮",fitzpatrick_scale:false,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:"🇯🇲",fitzpatrick_scale:false,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:"🇯🇵",fitzpatrick_scale:false,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:"🇯🇪",fitzpatrick_scale:false,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:"🇯🇴",fitzpatrick_scale:false,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:"🇰🇿",fitzpatrick_scale:false,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:"🇰🇪",fitzpatrick_scale:false,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:"🇰🇮",fitzpatrick_scale:false,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:"🇽🇰",fitzpatrick_scale:false,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:"🇰🇼",fitzpatrick_scale:false,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:"🇰🇬",fitzpatrick_scale:false,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:"🇱🇦",fitzpatrick_scale:false,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:"🇱🇻",fitzpatrick_scale:false,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:"🇱🇧",fitzpatrick_scale:false,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:"🇱🇸",fitzpatrick_scale:false,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:"🇱🇷",fitzpatrick_scale:false,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:"🇱🇾",fitzpatrick_scale:false,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:"🇱🇮",fitzpatrick_scale:false,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:"🇱🇹",fitzpatrick_scale:false,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:"🇱🇺",fitzpatrick_scale:false,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:"🇲🇴",fitzpatrick_scale:false,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:"🇲🇰",fitzpatrick_scale:false,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:"🇲🇬",fitzpatrick_scale:false,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:"🇲🇼",fitzpatrick_scale:false,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:"🇲🇾",fitzpatrick_scale:false,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:"🇲🇻",fitzpatrick_scale:false,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:"🇲🇱",fitzpatrick_scale:false,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:"🇲🇹",fitzpatrick_scale:false,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:"🇲🇭",fitzpatrick_scale:false,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:"🇲🇶",fitzpatrick_scale:false,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:"🇲🇷",fitzpatrick_scale:false,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:"🇲🇺",fitzpatrick_scale:false,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:"🇾🇹",fitzpatrick_scale:false,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:"🇲🇽",fitzpatrick_scale:false,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:"🇫🇲",fitzpatrick_scale:false,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:"🇲🇩",fitzpatrick_scale:false,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:"🇲🇨",fitzpatrick_scale:false,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:"🇲🇳",fitzpatrick_scale:false,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:"🇲🇪",fitzpatrick_scale:false,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:"🇲🇸",fitzpatrick_scale:false,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:"🇲🇦",fitzpatrick_scale:false,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:"🇲🇿",fitzpatrick_scale:false,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:"🇲🇲",fitzpatrick_scale:false,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:"🇳🇦",fitzpatrick_scale:false,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:"🇳🇷",fitzpatrick_scale:false,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:"🇳🇵",fitzpatrick_scale:false,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:"🇳🇱",fitzpatrick_scale:false,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:"🇳🇨",fitzpatrick_scale:false,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:"🇳🇿",fitzpatrick_scale:false,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:"🇳🇮",fitzpatrick_scale:false,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:"🇳🇪",fitzpatrick_scale:false,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:"🇳🇬",fitzpatrick_scale:false,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:"🇳🇺",fitzpatrick_scale:false,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:"🇳🇫",fitzpatrick_scale:false,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:"🇲🇵",fitzpatrick_scale:false,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:"🇰🇵",fitzpatrick_scale:false,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:"🇳🇴",fitzpatrick_scale:false,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:"🇴🇲",fitzpatrick_scale:false,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:"🇵🇰",fitzpatrick_scale:false,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:"🇵🇼",fitzpatrick_scale:false,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:"🇵🇸",fitzpatrick_scale:false,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:"🇵🇦",fitzpatrick_scale:false,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:"🇵🇬",fitzpatrick_scale:false,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:"🇵🇾",fitzpatrick_scale:false,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:"🇵🇪",fitzpatrick_scale:false,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:"🇵🇭",fitzpatrick_scale:false,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:"🇵🇳",fitzpatrick_scale:false,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:"🇵🇱",fitzpatrick_scale:false,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:"🇵🇹",fitzpatrick_scale:false,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:"🇵🇷",fitzpatrick_scale:false,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:"🇶🇦",fitzpatrick_scale:false,category:"flags"},reunion:{keywords:["réunion","flag","nation","country","banner"],char:"🇷🇪",fitzpatrick_scale:false,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:"🇷🇴",fitzpatrick_scale:false,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:"🇷🇺",fitzpatrick_scale:false,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:"🇷🇼",fitzpatrick_scale:false,category:"flags"},st_barthelemy:{keywords:["saint","barthélemy","flag","nation","country","banner"],char:"🇧🇱",fitzpatrick_scale:false,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:"🇸🇭",fitzpatrick_scale:false,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:"🇰🇳",fitzpatrick_scale:false,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:"🇱🇨",fitzpatrick_scale:false,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:"🇵🇲",fitzpatrick_scale:false,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:"🇻🇨",fitzpatrick_scale:false,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:"🇼🇸",fitzpatrick_scale:false,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:"🇸🇲",fitzpatrick_scale:false,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:"🇸🇹",fitzpatrick_scale:false,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:"🇸🇦",fitzpatrick_scale:false,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:"🇸🇳",fitzpatrick_scale:false,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:"🇷🇸",fitzpatrick_scale:false,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:"🇸🇨",fitzpatrick_scale:false,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:"🇸🇱",fitzpatrick_scale:false,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:"🇸🇬",fitzpatrick_scale:false,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:"🇸🇽",fitzpatrick_scale:false,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:"🇸🇰",fitzpatrick_scale:false,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:"🇸🇮",fitzpatrick_scale:false,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:"🇸🇧",fitzpatrick_scale:false,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:"🇸🇴",fitzpatrick_scale:false,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:"🇿🇦",fitzpatrick_scale:false,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:"🇬🇸",fitzpatrick_scale:false,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:"🇰🇷",fitzpatrick_scale:false,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:"🇸🇸",fitzpatrick_scale:false,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:"🇪🇸",fitzpatrick_scale:false,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:"🇱🇰",fitzpatrick_scale:false,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:"🇸🇩",fitzpatrick_scale:false,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:"🇸🇷",fitzpatrick_scale:false,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:"🇸🇿",fitzpatrick_scale:false,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:"🇸🇪",fitzpatrick_scale:false,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:"🇨🇭",fitzpatrick_scale:false,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:"🇸🇾",fitzpatrick_scale:false,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:"🇹🇼",fitzpatrick_scale:false,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:"🇹🇯",fitzpatrick_scale:false,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:"🇹🇿",fitzpatrick_scale:false,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:"🇹🇭",fitzpatrick_scale:false,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:"🇹🇱",fitzpatrick_scale:false,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:"🇹🇬",fitzpatrick_scale:false,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:"🇹🇰",fitzpatrick_scale:false,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:"🇹🇴",fitzpatrick_scale:false,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:"🇹🇹",fitzpatrick_scale:false,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:"🇹🇳",fitzpatrick_scale:false,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:"🇹🇷",fitzpatrick_scale:false,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:"🇹🇲",fitzpatrick_scale:false,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:"🇹🇨",fitzpatrick_scale:false,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:"🇹🇻",fitzpatrick_scale:false,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:"🇺🇬",fitzpatrick_scale:false,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:"🇺🇦",fitzpatrick_scale:false,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:"🇦🇪",fitzpatrick_scale:false,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:"🇬🇧",fitzpatrick_scale:false,category:"flags"},england:{keywords:["flag","english"],char:"ðŸ´ó §ó ¢ó ¥ó ®ó §ó ¿",fitzpatrick_scale:false,category:"flags"},scotland:{keywords:["flag","scottish"],char:"ðŸ´ó §ó ¢ó ³ó £ó ´ó ¿",fitzpatrick_scale:false,category:"flags"},wales:{keywords:["flag","welsh"],char:"ðŸ´ó §ó ¢ó ·ó ¬ó ³ó ¿",fitzpatrick_scale:false,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:"🇺🇸",fitzpatrick_scale:false,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:"🇻🇮",fitzpatrick_scale:false,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:"🇺🇾",fitzpatrick_scale:false,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:"🇺🇿",fitzpatrick_scale:false,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:"🇻🇺",fitzpatrick_scale:false,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:"🇻🇦",fitzpatrick_scale:false,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:"🇻🇪",fitzpatrick_scale:false,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:"🇻🇳",fitzpatrick_scale:false,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:"🇼🇫",fitzpatrick_scale:false,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:"🇪🇭",fitzpatrick_scale:false,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:"🇾🇪",fitzpatrick_scale:false,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:"🇿🇲",fitzpatrick_scale:false,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:"🇿🇼",fitzpatrick_scale:false,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:"🇺🇳",fitzpatrick_scale:false,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:"ðŸ´â€â˜ ï¸",fitzpatrick_scale:false,category:"flags"}}); \ No newline at end of file diff --git a/static/js/tinymce/plugins/emoticons/js/emojis.min.js b/static/js/tinymce/plugins/emoticons/js/emojis.min.js new file mode 100644 index 0000000..5a1c491 --- /dev/null +++ b/static/js/tinymce/plugins/emoticons/js/emojis.min.js @@ -0,0 +1,2 @@ +// Source: npm package: emojilib, file:emojis.json +window.tinymce.Resource.add("tinymce.plugins.emoticons",{grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:"\u{1f600}",fitzpatrick_scale:!1,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:"\u{1f62c}",fitzpatrick_scale:!1,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:"\u{1f601}",fitzpatrick_scale:!1,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:"\u{1f602}",fitzpatrick_scale:!1,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:"\u{1f923}",fitzpatrick_scale:!1,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:"\u{1f973}",fitzpatrick_scale:!1,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:"\u{1f603}",fitzpatrick_scale:!1,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:"\u{1f604}",fitzpatrick_scale:!1,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:"\u{1f605}",fitzpatrick_scale:!1,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:"\u{1f606}",fitzpatrick_scale:!1,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:"\u{1f607}",fitzpatrick_scale:!1,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:"\u{1f609}",fitzpatrick_scale:!1,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:"\u{1f60a}",fitzpatrick_scale:!1,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:"\u{1f642}",fitzpatrick_scale:!1,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:"\u{1f643}",fitzpatrick_scale:!1,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:"\u263a\ufe0f",fitzpatrick_scale:!1,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:"\u{1f60b}",fitzpatrick_scale:!1,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:"\u{1f60c}",fitzpatrick_scale:!1,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:"\u{1f60d}",fitzpatrick_scale:!1,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:"\u{1f970}",fitzpatrick_scale:!1,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"\u{1f618}",fitzpatrick_scale:!1,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:"\u{1f617}",fitzpatrick_scale:!1,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:"\u{1f619}",fitzpatrick_scale:!1,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"\u{1f61a}",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:"\u{1f61c}",fitzpatrick_scale:!1,category:"people"},zany:{keywords:["face","goofy","crazy"],char:"\u{1f92a}",fitzpatrick_scale:!1,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:"\u{1f928}",fitzpatrick_scale:!1,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:"\u{1f9d0}",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:"\u{1f61d}",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:"\u{1f61b}",fitzpatrick_scale:!1,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:"\u{1f911}",fitzpatrick_scale:!1,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:"\u{1f913}",fitzpatrick_scale:!1,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:"\u{1f60e}",fitzpatrick_scale:!1,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:"\u{1f929}",fitzpatrick_scale:!1,category:"people"},clown_face:{keywords:["face"],char:"\u{1f921}",fitzpatrick_scale:!1,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:"\u{1f920}",fitzpatrick_scale:!1,category:"people"},hugs:{keywords:["face","smile","hug"],char:"\u{1f917}",fitzpatrick_scale:!1,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:"\u{1f60f}",fitzpatrick_scale:!1,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:"\u{1f636}",fitzpatrick_scale:!1,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:"\u{1f610}",fitzpatrick_scale:!1,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:"\u{1f611}",fitzpatrick_scale:!1,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:"\u{1f612}",fitzpatrick_scale:!1,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:"\u{1f644}",fitzpatrick_scale:!1,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:"\u{1f914}",fitzpatrick_scale:!1,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:"\u{1f925}",fitzpatrick_scale:!1,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:"\u{1f92d}",fitzpatrick_scale:!1,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:"\u{1f92b}",fitzpatrick_scale:!1,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:"\u{1f92c}",fitzpatrick_scale:!1,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:"\u{1f92f}",fitzpatrick_scale:!1,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:"\u{1f633}",fitzpatrick_scale:!1,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:"\u{1f61e}",fitzpatrick_scale:!1,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:"\u{1f61f}",fitzpatrick_scale:!1,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:"\u{1f620}",fitzpatrick_scale:!1,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:"\u{1f621}",fitzpatrick_scale:!1,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:"\u{1f614}",fitzpatrick_scale:!1,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:"\u{1f615}",fitzpatrick_scale:!1,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:"\u{1f641}",fitzpatrick_scale:!1,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:"\u2639",fitzpatrick_scale:!1,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:"\u{1f623}",fitzpatrick_scale:!1,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:"\u{1f616}",fitzpatrick_scale:!1,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:"\u{1f62b}",fitzpatrick_scale:!1,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:"\u{1f629}",fitzpatrick_scale:!1,category:"people"},pleading:{keywords:["face","begging","mercy"],char:"\u{1f97a}",fitzpatrick_scale:!1,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:"\u{1f624}",fitzpatrick_scale:!1,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:"\u{1f62e}",fitzpatrick_scale:!1,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:"\u{1f631}",fitzpatrick_scale:!1,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:"\u{1f628}",fitzpatrick_scale:!1,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:"\u{1f630}",fitzpatrick_scale:!1,category:"people"},hushed:{keywords:["face","woo","shh"],char:"\u{1f62f}",fitzpatrick_scale:!1,category:"people"},frowning:{keywords:["face","aw","what"],char:"\u{1f626}",fitzpatrick_scale:!1,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:"\u{1f627}",fitzpatrick_scale:!1,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:"\u{1f622}",fitzpatrick_scale:!1,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:"\u{1f625}",fitzpatrick_scale:!1,category:"people"},drooling_face:{keywords:["face"],char:"\u{1f924}",fitzpatrick_scale:!1,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:"\u{1f62a}",fitzpatrick_scale:!1,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:"\u{1f613}",fitzpatrick_scale:!1,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:"\u{1f975}",fitzpatrick_scale:!1,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:"\u{1f976}",fitzpatrick_scale:!1,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:"\u{1f62d}",fitzpatrick_scale:!1,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:"\u{1f635}",fitzpatrick_scale:!1,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:"\u{1f632}",fitzpatrick_scale:!1,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:"\u{1f910}",fitzpatrick_scale:!1,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:"\u{1f922}",fitzpatrick_scale:!1,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:"\u{1f927}",fitzpatrick_scale:!1,category:"people"},vomiting:{keywords:["face","sick"],char:"\u{1f92e}",fitzpatrick_scale:!1,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:"\u{1f637}",fitzpatrick_scale:!1,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:"\u{1f912}",fitzpatrick_scale:!1,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:"\u{1f915}",fitzpatrick_scale:!1,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:"\u{1f974}",fitzpatrick_scale:!1,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:"\u{1f634}",fitzpatrick_scale:!1,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:"\u{1f4a4}",fitzpatrick_scale:!1,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:"\u{1f4a9}",fitzpatrick_scale:!1,category:"people"},smiling_imp:{keywords:["devil","horns"],char:"\u{1f608}",fitzpatrick_scale:!1,category:"people"},imp:{keywords:["devil","angry","horns"],char:"\u{1f47f}",fitzpatrick_scale:!1,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:"\u{1f479}",fitzpatrick_scale:!1,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:"\u{1f47a}",fitzpatrick_scale:!1,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:"\u{1f480}",fitzpatrick_scale:!1,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:"\u{1f47b}",fitzpatrick_scale:!1,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:"\u{1f47d}",fitzpatrick_scale:!1,category:"people"},robot:{keywords:["computer","machine","bot"],char:"\u{1f916}",fitzpatrick_scale:!1,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:"\u{1f63a}",fitzpatrick_scale:!1,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:"\u{1f638}",fitzpatrick_scale:!1,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:"\u{1f639}",fitzpatrick_scale:!1,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:"\u{1f63b}",fitzpatrick_scale:!1,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:"\u{1f63c}",fitzpatrick_scale:!1,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:"\u{1f63d}",fitzpatrick_scale:!1,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:"\u{1f640}",fitzpatrick_scale:!1,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:"\u{1f63f}",fitzpatrick_scale:!1,category:"people"},pouting_cat:{keywords:["animal","cats"],char:"\u{1f63e}",fitzpatrick_scale:!1,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:"\u{1f932}",fitzpatrick_scale:!0,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:"\u{1f64c}",fitzpatrick_scale:!0,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:"\u{1f44f}",fitzpatrick_scale:!0,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:"\u{1f44b}",fitzpatrick_scale:!0,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:"\u{1f919}",fitzpatrick_scale:!0,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:"\u{1f44d}",fitzpatrick_scale:!0,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:"\u{1f44e}",fitzpatrick_scale:!0,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:"\u{1f44a}",fitzpatrick_scale:!0,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:"\u270a",fitzpatrick_scale:!0,category:"people"},fist_left:{keywords:["hand","fistbump"],char:"\u{1f91b}",fitzpatrick_scale:!0,category:"people"},fist_right:{keywords:["hand","fistbump"],char:"\u{1f91c}",fitzpatrick_scale:!0,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:"\u270c",fitzpatrick_scale:!0,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:"\u{1f44c}",fitzpatrick_scale:!0,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:"\u270b",fitzpatrick_scale:!0,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:"\u{1f91a}",fitzpatrick_scale:!0,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:"\u{1f450}",fitzpatrick_scale:!0,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:"\u{1f4aa}",fitzpatrick_scale:!0,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:"\u{1f64f}",fitzpatrick_scale:!0,category:"people"},foot:{keywords:["kick","stomp"],char:"\u{1f9b6}",fitzpatrick_scale:!0,category:"people"},leg:{keywords:["kick","limb"],char:"\u{1f9b5}",fitzpatrick_scale:!0,category:"people"},handshake:{keywords:["agreement","shake"],char:"\u{1f91d}",fitzpatrick_scale:!1,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:"\u261d",fitzpatrick_scale:!0,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:"\u{1f446}",fitzpatrick_scale:!0,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:"\u{1f447}",fitzpatrick_scale:!0,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:"\u{1f448}",fitzpatrick_scale:!0,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:"\u{1f449}",fitzpatrick_scale:!0,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:"\u{1f595}",fitzpatrick_scale:!0,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:"\u{1f590}",fitzpatrick_scale:!0,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:"\u{1f91f}",fitzpatrick_scale:!0,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:"\u{1f918}",fitzpatrick_scale:!0,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:"\u{1f91e}",fitzpatrick_scale:!0,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:"\u{1f596}",fitzpatrick_scale:!0,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:"\u270d",fitzpatrick_scale:!0,category:"people"},selfie:{keywords:["camera","phone"],char:"\u{1f933}",fitzpatrick_scale:!0,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:"\u{1f485}",fitzpatrick_scale:!0,category:"people"},lips:{keywords:["mouth","kiss"],char:"\u{1f444}",fitzpatrick_scale:!1,category:"people"},tooth:{keywords:["teeth","dentist"],char:"\u{1f9b7}",fitzpatrick_scale:!1,category:"people"},tongue:{keywords:["mouth","playful"],char:"\u{1f445}",fitzpatrick_scale:!1,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:"\u{1f442}",fitzpatrick_scale:!0,category:"people"},nose:{keywords:["smell","sniff"],char:"\u{1f443}",fitzpatrick_scale:!0,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:"\u{1f441}",fitzpatrick_scale:!1,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:"\u{1f440}",fitzpatrick_scale:!1,category:"people"},brain:{keywords:["smart","intelligent"],char:"\u{1f9e0}",fitzpatrick_scale:!1,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:"\u{1f464}",fitzpatrick_scale:!1,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:"\u{1f465}",fitzpatrick_scale:!1,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:"\u{1f5e3}",fitzpatrick_scale:!1,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:"\u{1f476}",fitzpatrick_scale:!0,category:"people"},child:{keywords:["gender-neutral","young"],char:"\u{1f9d2}",fitzpatrick_scale:!0,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:"\u{1f466}",fitzpatrick_scale:!0,category:"people"},girl:{keywords:["female","woman","teenager"],char:"\u{1f467}",fitzpatrick_scale:!0,category:"people"},adult:{keywords:["gender-neutral","person"],char:"\u{1f9d1}",fitzpatrick_scale:!0,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:"\u{1f468}",fitzpatrick_scale:!0,category:"people"},woman:{keywords:["female","girls","lady"],char:"\u{1f469}",fitzpatrick_scale:!0,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:"\u{1f471}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:"\u{1f471}",fitzpatrick_scale:!0,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:"\u{1f9d4}",fitzpatrick_scale:!0,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:"\u{1f9d3}",fitzpatrick_scale:!0,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:"\u{1f474}",fitzpatrick_scale:!0,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:"\u{1f475}",fitzpatrick_scale:!0,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:"\u{1f472}",fitzpatrick_scale:!0,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:"\u{1f9d5}",fitzpatrick_scale:!0,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:"\u{1f473}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:"\u{1f473}",fitzpatrick_scale:!0,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:"\u{1f46e}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:"\u{1f46e}",fitzpatrick_scale:!0,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:"\u{1f477}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:"\u{1f477}",fitzpatrick_scale:!0,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:"\u{1f482}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:"\u{1f482}",fitzpatrick_scale:!0,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:"\u{1f575}\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},male_detective:{keywords:["human","spy","detective"],char:"\u{1f575}",fitzpatrick_scale:!0,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:"\u{1f469}\u200d\u2695\ufe0f",fitzpatrick_scale:!0,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:"\u{1f468}\u200d\u2695\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:"\u{1f469}\u200d\u{1f33e}",fitzpatrick_scale:!0,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:"\u{1f468}\u200d\u{1f33e}",fitzpatrick_scale:!0,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:"\u{1f469}\u200d\u{1f373}",fitzpatrick_scale:!0,category:"people"},man_cook:{keywords:["chef","man","human"],char:"\u{1f468}\u200d\u{1f373}",fitzpatrick_scale:!0,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:"\u{1f469}\u200d\u{1f393}",fitzpatrick_scale:!0,category:"people"},man_student:{keywords:["graduate","man","human"],char:"\u{1f468}\u200d\u{1f393}",fitzpatrick_scale:!0,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:"\u{1f469}\u200d\u{1f3a4}",fitzpatrick_scale:!0,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:"\u{1f468}\u200d\u{1f3a4}",fitzpatrick_scale:!0,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:"\u{1f469}\u200d\u{1f3eb}",fitzpatrick_scale:!0,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:"\u{1f468}\u200d\u{1f3eb}",fitzpatrick_scale:!0,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:"\u{1f469}\u200d\u{1f3ed}",fitzpatrick_scale:!0,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:"\u{1f468}\u200d\u{1f3ed}",fitzpatrick_scale:!0,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:"\u{1f469}\u200d\u{1f4bb}",fitzpatrick_scale:!0,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:"\u{1f468}\u200d\u{1f4bb}",fitzpatrick_scale:!0,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:"\u{1f469}\u200d\u{1f4bc}",fitzpatrick_scale:!0,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:"\u{1f468}\u200d\u{1f4bc}",fitzpatrick_scale:!0,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:"\u{1f469}\u200d\u{1f527}",fitzpatrick_scale:!0,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:"\u{1f468}\u200d\u{1f527}",fitzpatrick_scale:!0,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:"\u{1f469}\u200d\u{1f52c}",fitzpatrick_scale:!0,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:"\u{1f468}\u200d\u{1f52c}",fitzpatrick_scale:!0,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:"\u{1f469}\u200d\u{1f3a8}",fitzpatrick_scale:!0,category:"people"},man_artist:{keywords:["painter","man","human"],char:"\u{1f468}\u200d\u{1f3a8}",fitzpatrick_scale:!0,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:"\u{1f469}\u200d\u{1f692}",fitzpatrick_scale:!0,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:"\u{1f468}\u200d\u{1f692}",fitzpatrick_scale:!0,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:"\u{1f469}\u200d\u2708\ufe0f",fitzpatrick_scale:!0,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:"\u{1f468}\u200d\u2708\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:"\u{1f469}\u200d\u{1f680}",fitzpatrick_scale:!0,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:"\u{1f468}\u200d\u{1f680}",fitzpatrick_scale:!0,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:"\u{1f469}\u200d\u2696\ufe0f",fitzpatrick_scale:!0,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:"\u{1f468}\u200d\u2696\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:"\u{1f9b8}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:"\u{1f9b8}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:"\u{1f9b9}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:"\u{1f9b9}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:"\u{1f936}",fitzpatrick_scale:!0,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:"\u{1f385}",fitzpatrick_scale:!0,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:"\u{1f9d9}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:"\u{1f9d9}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_elf:{keywords:["woman","female"],char:"\u{1f9dd}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_elf:{keywords:["man","male"],char:"\u{1f9dd}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_vampire:{keywords:["woman","female"],char:"\u{1f9db}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:"\u{1f9db}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:"\u{1f9df}\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:"\u{1f9df}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},woman_genie:{keywords:["woman","female"],char:"\u{1f9de}\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"people"},man_genie:{keywords:["man","male"],char:"\u{1f9de}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:"\u{1f9dc}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},merman:{keywords:["man","male","triton"],char:"\u{1f9dc}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_fairy:{keywords:["woman","female"],char:"\u{1f9da}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_fairy:{keywords:["man","male"],char:"\u{1f9da}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},angel:{keywords:["heaven","wings","halo"],char:"\u{1f47c}",fitzpatrick_scale:!0,category:"people"},pregnant_woman:{keywords:["baby"],char:"\u{1f930}",fitzpatrick_scale:!0,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:"\u{1f931}",fitzpatrick_scale:!0,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:"\u{1f478}",fitzpatrick_scale:!0,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:"\u{1f934}",fitzpatrick_scale:!0,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:"\u{1f470}",fitzpatrick_scale:!0,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:"\u{1f935}",fitzpatrick_scale:!0,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:"\u{1f3c3}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:"\u{1f3c3}",fitzpatrick_scale:!0,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:"\u{1f6b6}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},walking_man:{keywords:["human","feet","steps"],char:"\u{1f6b6}",fitzpatrick_scale:!0,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:"\u{1f483}",fitzpatrick_scale:!0,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:"\u{1f57a}",fitzpatrick_scale:!0,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:"\u{1f46f}",fitzpatrick_scale:!1,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:"\u{1f46f}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:"\u{1f46b}",fitzpatrick_scale:!1,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:"\u{1f46c}",fitzpatrick_scale:!1,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:"\u{1f46d}",fitzpatrick_scale:!1,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:"\u{1f647}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},bowing_man:{keywords:["man","male","boy"],char:"\u{1f647}",fitzpatrick_scale:!0,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:"\u{1f926}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:"\u{1f926}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:"\u{1f937}",fitzpatrick_scale:!0,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:"\u{1f937}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:"\u{1f481}",fitzpatrick_scale:!0,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:"\u{1f481}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:"\u{1f645}",fitzpatrick_scale:!0,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:"\u{1f645}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:"\u{1f646}",fitzpatrick_scale:!0,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:"\u{1f646}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:"\u{1f64b}",fitzpatrick_scale:!0,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:"\u{1f64b}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:"\u{1f64e}",fitzpatrick_scale:!0,category:"people"},pouting_man:{keywords:["male","boy","man"],char:"\u{1f64e}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:"\u{1f64d}",fitzpatrick_scale:!0,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:"\u{1f64d}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:"\u{1f487}",fitzpatrick_scale:!0,category:"people"},haircut_man:{keywords:["male","boy","man"],char:"\u{1f487}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:"\u{1f486}",fitzpatrick_scale:!0,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:"\u{1f486}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:"\u{1f9d6}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:"\u{1f9d6}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\u{1f491}",fitzpatrick_scale:!1,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f469}",fitzpatrick_scale:!1,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f468}",fitzpatrick_scale:!1,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\u{1f48f}",fitzpatrick_scale:!1,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f469}",fitzpatrick_scale:!1,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f468}",fitzpatrick_scale:!1,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:"\u{1f46a}",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:"\u{1f469}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:"\u{1f469}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f469}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f469}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:"\u{1f469}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:"\u{1f468}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:"\u{1f468}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f468}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f468}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:"\u{1f468}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:"\u{1f9f6}",fitzpatrick_scale:!1,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:"\u{1f9f5}",fitzpatrick_scale:!1,category:"people"},coat:{keywords:["jacket"],char:"\u{1f9e5}",fitzpatrick_scale:!1,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:"\u{1f97c}",fitzpatrick_scale:!1,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:"\u{1f45a}",fitzpatrick_scale:!1,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:"\u{1f455}",fitzpatrick_scale:!1,category:"people"},jeans:{keywords:["fashion","shopping"],char:"\u{1f456}",fitzpatrick_scale:!1,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:"\u{1f454}",fitzpatrick_scale:!1,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:"\u{1f457}",fitzpatrick_scale:!1,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:"\u{1f459}",fitzpatrick_scale:!1,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:"\u{1f458}",fitzpatrick_scale:!1,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:"\u{1f484}",fitzpatrick_scale:!1,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:"\u{1f48b}",fitzpatrick_scale:!1,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:"\u{1f463}",fitzpatrick_scale:!1,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:"\u{1f97f}",fitzpatrick_scale:!1,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:"\u{1f460}",fitzpatrick_scale:!1,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:"\u{1f461}",fitzpatrick_scale:!1,category:"people"},boot:{keywords:["shoes","fashion"],char:"\u{1f462}",fitzpatrick_scale:!1,category:"people"},mans_shoe:{keywords:["fashion","male"],char:"\u{1f45e}",fitzpatrick_scale:!1,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:"\u{1f45f}",fitzpatrick_scale:!1,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:"\u{1f97e}",fitzpatrick_scale:!1,category:"people"},socks:{keywords:["stockings","clothes"],char:"\u{1f9e6}",fitzpatrick_scale:!1,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:"\u{1f9e4}",fitzpatrick_scale:!1,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:"\u{1f9e3}",fitzpatrick_scale:!1,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:"\u{1f452}",fitzpatrick_scale:!1,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:"\u{1f3a9}",fitzpatrick_scale:!1,category:"people"},billed_hat:{keywords:["cap","baseball"],char:"\u{1f9e2}",fitzpatrick_scale:!1,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:"\u26d1",fitzpatrick_scale:!1,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:"\u{1f393}",fitzpatrick_scale:!1,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:"\u{1f451}",fitzpatrick_scale:!1,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:"\u{1f392}",fitzpatrick_scale:!1,category:"people"},luggage:{keywords:["packing","travel"],char:"\u{1f9f3}",fitzpatrick_scale:!1,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:"\u{1f45d}",fitzpatrick_scale:!1,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:"\u{1f45b}",fitzpatrick_scale:!1,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:"\u{1f45c}",fitzpatrick_scale:!1,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:"\u{1f4bc}",fitzpatrick_scale:!1,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:"\u{1f453}",fitzpatrick_scale:!1,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:"\u{1f576}",fitzpatrick_scale:!1,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:"\u{1f97d}",fitzpatrick_scale:!1,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:"\u{1f48d}",fitzpatrick_scale:!1,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:"\u{1f302}",fitzpatrick_scale:!1,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:"\u{1f436}",fitzpatrick_scale:!1,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:"\u{1f431}",fitzpatrick_scale:!1,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:"\u{1f42d}",fitzpatrick_scale:!1,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:"\u{1f439}",fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:"\u{1f430}",fitzpatrick_scale:!1,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:"\u{1f98a}",fitzpatrick_scale:!1,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:"\u{1f43b}",fitzpatrick_scale:!1,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:"\u{1f43c}",fitzpatrick_scale:!1,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:"\u{1f428}",fitzpatrick_scale:!1,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:"\u{1f42f}",fitzpatrick_scale:!1,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:"\u{1f981}",fitzpatrick_scale:!1,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:"\u{1f42e}",fitzpatrick_scale:!1,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:"\u{1f437}",fitzpatrick_scale:!1,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:"\u{1f43d}",fitzpatrick_scale:!1,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:"\u{1f438}",fitzpatrick_scale:!1,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:"\u{1f991}",fitzpatrick_scale:!1,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:"\u{1f419}",fitzpatrick_scale:!1,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:"\u{1f990}",fitzpatrick_scale:!1,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:"\u{1f435}",fitzpatrick_scale:!1,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:"\u{1f98d}",fitzpatrick_scale:!1,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:"\u{1f648}",fitzpatrick_scale:!1,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:"\u{1f649}",fitzpatrick_scale:!1,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:"\u{1f64a}",fitzpatrick_scale:!1,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:"\u{1f412}",fitzpatrick_scale:!1,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:"\u{1f414}",fitzpatrick_scale:!1,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:"\u{1f427}",fitzpatrick_scale:!1,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:"\u{1f426}",fitzpatrick_scale:!1,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:"\u{1f424}",fitzpatrick_scale:!1,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:"\u{1f423}",fitzpatrick_scale:!1,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:"\u{1f425}",fitzpatrick_scale:!1,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:"\u{1f986}",fitzpatrick_scale:!1,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:"\u{1f985}",fitzpatrick_scale:!1,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:"\u{1f989}",fitzpatrick_scale:!1,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:"\u{1f987}",fitzpatrick_scale:!1,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:"\u{1f43a}",fitzpatrick_scale:!1,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:"\u{1f417}",fitzpatrick_scale:!1,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:"\u{1f434}",fitzpatrick_scale:!1,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:"\u{1f984}",fitzpatrick_scale:!1,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:"\u{1f41d}",fitzpatrick_scale:!1,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:"\u{1f41b}",fitzpatrick_scale:!1,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:"\u{1f98b}",fitzpatrick_scale:!1,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:"\u{1f40c}",fitzpatrick_scale:!1,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:"\u{1f41e}",fitzpatrick_scale:!1,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:"\u{1f41c}",fitzpatrick_scale:!1,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:"\u{1f997}",fitzpatrick_scale:!1,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:"\u{1f577}",fitzpatrick_scale:!1,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:"\u{1f982}",fitzpatrick_scale:!1,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:"\u{1f980}",fitzpatrick_scale:!1,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:"\u{1f40d}",fitzpatrick_scale:!1,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:"\u{1f98e}",fitzpatrick_scale:!1,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:"\u{1f996}",fitzpatrick_scale:!1,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:"\u{1f995}",fitzpatrick_scale:!1,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:"\u{1f422}",fitzpatrick_scale:!1,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:"\u{1f420}",fitzpatrick_scale:!1,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:"\u{1f41f}",fitzpatrick_scale:!1,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:"\u{1f421}",fitzpatrick_scale:!1,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:"\u{1f42c}",fitzpatrick_scale:!1,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:"\u{1f988}",fitzpatrick_scale:!1,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:"\u{1f433}",fitzpatrick_scale:!1,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:"\u{1f40b}",fitzpatrick_scale:!1,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:"\u{1f40a}",fitzpatrick_scale:!1,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:"\u{1f406}",fitzpatrick_scale:!1,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:"\u{1f993}",fitzpatrick_scale:!1,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:"\u{1f405}",fitzpatrick_scale:!1,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:"\u{1f403}",fitzpatrick_scale:!1,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:"\u{1f402}",fitzpatrick_scale:!1,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:"\u{1f404}",fitzpatrick_scale:!1,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:"\u{1f98c}",fitzpatrick_scale:!1,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:"\u{1f42a}",fitzpatrick_scale:!1,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:"\u{1f42b}",fitzpatrick_scale:!1,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:"\u{1f992}",fitzpatrick_scale:!1,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:"\u{1f418}",fitzpatrick_scale:!1,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:"\u{1f98f}",fitzpatrick_scale:!1,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:"\u{1f410}",fitzpatrick_scale:!1,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:"\u{1f40f}",fitzpatrick_scale:!1,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:"\u{1f411}",fitzpatrick_scale:!1,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:"\u{1f40e}",fitzpatrick_scale:!1,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:"\u{1f416}",fitzpatrick_scale:!1,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:"\u{1f400}",fitzpatrick_scale:!1,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:"\u{1f401}",fitzpatrick_scale:!1,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:"\u{1f413}",fitzpatrick_scale:!1,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:"\u{1f983}",fitzpatrick_scale:!1,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:"\u{1f54a}",fitzpatrick_scale:!1,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:"\u{1f415}",fitzpatrick_scale:!1,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:"\u{1f429}",fitzpatrick_scale:!1,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:"\u{1f408}",fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:"\u{1f407}",fitzpatrick_scale:!1,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:"\u{1f43f}",fitzpatrick_scale:!1,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:"\u{1f994}",fitzpatrick_scale:!1,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:"\u{1f99d}",fitzpatrick_scale:!1,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:"\u{1f999}",fitzpatrick_scale:!1,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:"\u{1f99b}",fitzpatrick_scale:!1,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:"\u{1f998}",fitzpatrick_scale:!1,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:"\u{1f9a1}",fitzpatrick_scale:!1,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:"\u{1f9a2}",fitzpatrick_scale:!1,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:"\u{1f99a}",fitzpatrick_scale:!1,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:"\u{1f99c}",fitzpatrick_scale:!1,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:"\u{1f99e}",fitzpatrick_scale:!1,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:"\u{1f99f}",fitzpatrick_scale:!1,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:"\u{1f43e}",fitzpatrick_scale:!1,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:"\u{1f409}",fitzpatrick_scale:!1,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:"\u{1f432}",fitzpatrick_scale:!1,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:"\u{1f335}",fitzpatrick_scale:!1,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:"\u{1f384}",fitzpatrick_scale:!1,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:"\u{1f332}",fitzpatrick_scale:!1,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:"\u{1f333}",fitzpatrick_scale:!1,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:"\u{1f334}",fitzpatrick_scale:!1,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:"\u{1f331}",fitzpatrick_scale:!1,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:"\u{1f33f}",fitzpatrick_scale:!1,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:"\u2618",fitzpatrick_scale:!1,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:"\u{1f340}",fitzpatrick_scale:!1,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:"\u{1f38d}",fitzpatrick_scale:!1,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:"\u{1f38b}",fitzpatrick_scale:!1,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:"\u{1f343}",fitzpatrick_scale:!1,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:"\u{1f342}",fitzpatrick_scale:!1,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:"\u{1f341}",fitzpatrick_scale:!1,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:"\u{1f33e}",fitzpatrick_scale:!1,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:"\u{1f33a}",fitzpatrick_scale:!1,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:"\u{1f33b}",fitzpatrick_scale:!1,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:"\u{1f339}",fitzpatrick_scale:!1,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:"\u{1f940}",fitzpatrick_scale:!1,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:"\u{1f337}",fitzpatrick_scale:!1,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:"\u{1f33c}",fitzpatrick_scale:!1,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:"\u{1f338}",fitzpatrick_scale:!1,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:"\u{1f490}",fitzpatrick_scale:!1,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:"\u{1f344}",fitzpatrick_scale:!1,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:"\u{1f330}",fitzpatrick_scale:!1,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:"\u{1f383}",fitzpatrick_scale:!1,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:"\u{1f41a}",fitzpatrick_scale:!1,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:"\u{1f578}",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:"\u{1f30e}",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:"\u{1f30d}",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:"\u{1f30f}",fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:"\u{1f315}",fitzpatrick_scale:!1,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:"\u{1f316}",fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f317}",fitzpatrick_scale:!1,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f318}",fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f311}",fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f312}",fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f313}",fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:"\u{1f314}",fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31a}",fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31d}",fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31b}",fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31c}",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:"\u{1f31e}",fitzpatrick_scale:!1,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:"\u{1f319}",fitzpatrick_scale:!1,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:"\u2b50",fitzpatrick_scale:!1,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:"\u{1f31f}",fitzpatrick_scale:!1,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:"\u{1f4ab}",fitzpatrick_scale:!1,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:"\u2728",fitzpatrick_scale:!1,category:"animals_and_nature"},comet:{keywords:["space"],char:"\u2604",fitzpatrick_scale:!1,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:"\u2600\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:"\u{1f324}",fitzpatrick_scale:!1,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:"\u26c5",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:"\u{1f325}",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:"\u{1f326}",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:"\u2601\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:"\u{1f327}",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:"\u26c8",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:"\u{1f329}",fitzpatrick_scale:!1,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:"\u26a1",fitzpatrick_scale:!1,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:"\u{1f525}",fitzpatrick_scale:!1,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:"\u{1f4a5}",fitzpatrick_scale:!1,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:"\u2744\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:"\u{1f328}",fitzpatrick_scale:!1,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:"\u26c4",fitzpatrick_scale:!1,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:"\u2603",fitzpatrick_scale:!1,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:"\u{1f32c}",fitzpatrick_scale:!1,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:"\u{1f4a8}",fitzpatrick_scale:!1,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:"\u{1f32a}",fitzpatrick_scale:!1,category:"animals_and_nature"},fog:{keywords:["weather"],char:"\u{1f32b}",fitzpatrick_scale:!1,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:"\u2602",fitzpatrick_scale:!1,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:"\u2614",fitzpatrick_scale:!1,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:"\u{1f4a7}",fitzpatrick_scale:!1,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:"\u{1f4a6}",fitzpatrick_scale:!1,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:"\u{1f30a}",fitzpatrick_scale:!1,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:"\u{1f34f}",fitzpatrick_scale:!1,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:"\u{1f34e}",fitzpatrick_scale:!1,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:"\u{1f350}",fitzpatrick_scale:!1,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:"\u{1f34a}",fitzpatrick_scale:!1,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:"\u{1f34b}",fitzpatrick_scale:!1,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:"\u{1f34c}",fitzpatrick_scale:!1,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:"\u{1f349}",fitzpatrick_scale:!1,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:"\u{1f347}",fitzpatrick_scale:!1,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:"\u{1f353}",fitzpatrick_scale:!1,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:"\u{1f348}",fitzpatrick_scale:!1,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:"\u{1f352}",fitzpatrick_scale:!1,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:"\u{1f351}",fitzpatrick_scale:!1,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:"\u{1f34d}",fitzpatrick_scale:!1,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:"\u{1f965}",fitzpatrick_scale:!1,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:"\u{1f95d}",fitzpatrick_scale:!1,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:"\u{1f96d}",fitzpatrick_scale:!1,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:"\u{1f951}",fitzpatrick_scale:!1,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:"\u{1f966}",fitzpatrick_scale:!1,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:"\u{1f345}",fitzpatrick_scale:!1,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:"\u{1f346}",fitzpatrick_scale:!1,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:"\u{1f952}",fitzpatrick_scale:!1,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:"\u{1f955}",fitzpatrick_scale:!1,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:"\u{1f336}",fitzpatrick_scale:!1,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:"\u{1f954}",fitzpatrick_scale:!1,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:"\u{1f33d}",fitzpatrick_scale:!1,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:"\u{1f96c}",fitzpatrick_scale:!1,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:"\u{1f360}",fitzpatrick_scale:!1,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:"\u{1f95c}",fitzpatrick_scale:!1,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:"\u{1f36f}",fitzpatrick_scale:!1,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:"\u{1f950}",fitzpatrick_scale:!1,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:"\u{1f35e}",fitzpatrick_scale:!1,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:"\u{1f956}",fitzpatrick_scale:!1,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:"\u{1f96f}",fitzpatrick_scale:!1,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:"\u{1f968}",fitzpatrick_scale:!1,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:"\u{1f9c0}",fitzpatrick_scale:!1,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:"\u{1f95a}",fitzpatrick_scale:!1,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:"\u{1f953}",fitzpatrick_scale:!1,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:"\u{1f969}",fitzpatrick_scale:!1,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:"\u{1f95e}",fitzpatrick_scale:!1,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:"\u{1f357}",fitzpatrick_scale:!1,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:"\u{1f356}",fitzpatrick_scale:!1,category:"food_and_drink"},bone:{keywords:["skeleton"],char:"\u{1f9b4}",fitzpatrick_scale:!1,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:"\u{1f364}",fitzpatrick_scale:!1,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:"\u{1f373}",fitzpatrick_scale:!1,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:"\u{1f354}",fitzpatrick_scale:!1,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:"\u{1f35f}",fitzpatrick_scale:!1,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:"\u{1f959}",fitzpatrick_scale:!1,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:"\u{1f32d}",fitzpatrick_scale:!1,category:"food_and_drink"},pizza:{keywords:["food","party"],char:"\u{1f355}",fitzpatrick_scale:!1,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:"\u{1f96a}",fitzpatrick_scale:!1,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:"\u{1f96b}",fitzpatrick_scale:!1,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:"\u{1f35d}",fitzpatrick_scale:!1,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:"\u{1f32e}",fitzpatrick_scale:!1,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:"\u{1f32f}",fitzpatrick_scale:!1,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:"\u{1f957}",fitzpatrick_scale:!1,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:"\u{1f958}",fitzpatrick_scale:!1,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:"\u{1f35c}",fitzpatrick_scale:!1,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:"\u{1f372}",fitzpatrick_scale:!1,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:"\u{1f365}",fitzpatrick_scale:!1,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:"\u{1f960}",fitzpatrick_scale:!1,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:"\u{1f363}",fitzpatrick_scale:!1,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:"\u{1f371}",fitzpatrick_scale:!1,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:"\u{1f35b}",fitzpatrick_scale:!1,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:"\u{1f359}",fitzpatrick_scale:!1,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:"\u{1f35a}",fitzpatrick_scale:!1,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:"\u{1f358}",fitzpatrick_scale:!1,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:"\u{1f362}",fitzpatrick_scale:!1,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:"\u{1f361}",fitzpatrick_scale:!1,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:"\u{1f367}",fitzpatrick_scale:!1,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:"\u{1f368}",fitzpatrick_scale:!1,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:"\u{1f366}",fitzpatrick_scale:!1,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:"\u{1f967}",fitzpatrick_scale:!1,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:"\u{1f370}",fitzpatrick_scale:!1,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:"\u{1f9c1}",fitzpatrick_scale:!1,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:"\u{1f96e}",fitzpatrick_scale:!1,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:"\u{1f382}",fitzpatrick_scale:!1,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:"\u{1f36e}",fitzpatrick_scale:!1,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:"\u{1f36c}",fitzpatrick_scale:!1,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:"\u{1f36d}",fitzpatrick_scale:!1,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:"\u{1f36b}",fitzpatrick_scale:!1,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:"\u{1f37f}",fitzpatrick_scale:!1,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:"\u{1f95f}",fitzpatrick_scale:!1,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:"\u{1f369}",fitzpatrick_scale:!1,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:"\u{1f36a}",fitzpatrick_scale:!1,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:"\u{1f95b}",fitzpatrick_scale:!1,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"\u{1f37a}",fitzpatrick_scale:!1,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"\u{1f37b}",fitzpatrick_scale:!1,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:"\u{1f942}",fitzpatrick_scale:!1,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:"\u{1f377}",fitzpatrick_scale:!1,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:"\u{1f943}",fitzpatrick_scale:!1,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:"\u{1f378}",fitzpatrick_scale:!1,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:"\u{1f379}",fitzpatrick_scale:!1,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:"\u{1f37e}",fitzpatrick_scale:!1,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:"\u{1f376}",fitzpatrick_scale:!1,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:"\u{1f375}",fitzpatrick_scale:!1,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:"\u{1f964}",fitzpatrick_scale:!1,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:"\u2615",fitzpatrick_scale:!1,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:"\u{1f37c}",fitzpatrick_scale:!1,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:"\u{1f9c2}",fitzpatrick_scale:!1,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:"\u{1f944}",fitzpatrick_scale:!1,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:"\u{1f374}",fitzpatrick_scale:!1,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:"\u{1f37d}",fitzpatrick_scale:!1,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:"\u{1f963}",fitzpatrick_scale:!1,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:"\u{1f961}",fitzpatrick_scale:!1,category:"food_and_drink"},chopsticks:{keywords:["food"],char:"\u{1f962}",fitzpatrick_scale:!1,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:"\u26bd",fitzpatrick_scale:!1,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:"\u{1f3c0}",fitzpatrick_scale:!1,category:"activity"},football:{keywords:["sports","balls","NFL"],char:"\u{1f3c8}",fitzpatrick_scale:!1,category:"activity"},baseball:{keywords:["sports","balls"],char:"\u26be",fitzpatrick_scale:!1,category:"activity"},softball:{keywords:["sports","balls"],char:"\u{1f94e}",fitzpatrick_scale:!1,category:"activity"},tennis:{keywords:["sports","balls","green"],char:"\u{1f3be}",fitzpatrick_scale:!1,category:"activity"},volleyball:{keywords:["sports","balls"],char:"\u{1f3d0}",fitzpatrick_scale:!1,category:"activity"},rugby_football:{keywords:["sports","team"],char:"\u{1f3c9}",fitzpatrick_scale:!1,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:"\u{1f94f}",fitzpatrick_scale:!1,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:"\u{1f3b1}",fitzpatrick_scale:!1,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:"\u26f3",fitzpatrick_scale:!1,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:"\u{1f3cc}\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"activity"},golfing_man:{keywords:["sports","business"],char:"\u{1f3cc}",fitzpatrick_scale:!0,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:"\u{1f3d3}",fitzpatrick_scale:!1,category:"activity"},badminton:{keywords:["sports"],char:"\u{1f3f8}",fitzpatrick_scale:!1,category:"activity"},goal_net:{keywords:["sports"],char:"\u{1f945}",fitzpatrick_scale:!1,category:"activity"},ice_hockey:{keywords:["sports"],char:"\u{1f3d2}",fitzpatrick_scale:!1,category:"activity"},field_hockey:{keywords:["sports"],char:"\u{1f3d1}",fitzpatrick_scale:!1,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:"\u{1f94d}",fitzpatrick_scale:!1,category:"activity"},cricket:{keywords:["sports"],char:"\u{1f3cf}",fitzpatrick_scale:!1,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:"\u{1f3bf}",fitzpatrick_scale:!1,category:"activity"},skier:{keywords:["sports","winter","snow"],char:"\u26f7",fitzpatrick_scale:!1,category:"activity"},snowboarder:{keywords:["sports","winter"],char:"\u{1f3c2}",fitzpatrick_scale:!0,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:"\u{1f93a}",fitzpatrick_scale:!1,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:"\u{1f93c}\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:"\u{1f93c}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:"\u{1f938}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:"\u{1f938}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},woman_playing_handball:{keywords:["sports"],char:"\u{1f93e}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_playing_handball:{keywords:["sports"],char:"\u{1f93e}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},ice_skate:{keywords:["sports"],char:"\u26f8",fitzpatrick_scale:!1,category:"activity"},curling_stone:{keywords:["sports"],char:"\u{1f94c}",fitzpatrick_scale:!1,category:"activity"},skateboard:{keywords:["board"],char:"\u{1f6f9}",fitzpatrick_scale:!1,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:"\u{1f6f7}",fitzpatrick_scale:!1,category:"activity"},bow_and_arrow:{keywords:["sports"],char:"\u{1f3f9}",fitzpatrick_scale:!1,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:"\u{1f3a3}",fitzpatrick_scale:!1,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:"\u{1f94a}",fitzpatrick_scale:!1,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:"\u{1f94b}",fitzpatrick_scale:!1,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:"\u{1f6a3}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:"\u{1f6a3}",fitzpatrick_scale:!0,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:"\u{1f9d7}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:"\u{1f9d7}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:"\u{1f3ca}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:"\u{1f3ca}",fitzpatrick_scale:!0,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:"\u{1f93d}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:"\u{1f93d}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:"\u{1f9d8}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:"\u{1f9d8}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:"\u{1f3c4}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:"\u{1f3c4}",fitzpatrick_scale:!0,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:"\u{1f6c0}",fitzpatrick_scale:!0,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:"\u26f9\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},basketball_man:{keywords:["sports","human"],char:"\u26f9",fitzpatrick_scale:!0,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:"\u{1f3cb}\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:"\u{1f3cb}",fitzpatrick_scale:!0,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:"\u{1f6b4}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:"\u{1f6b4}",fitzpatrick_scale:!0,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:"\u{1f6b5}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:"\u{1f6b5}",fitzpatrick_scale:!0,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:"\u{1f3c7}",fitzpatrick_scale:!0,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:"\u{1f574}",fitzpatrick_scale:!0,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:"\u{1f3c6}",fitzpatrick_scale:!1,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:"\u{1f3bd}",fitzpatrick_scale:!1,category:"activity"},medal_sports:{keywords:["award","winning"],char:"\u{1f3c5}",fitzpatrick_scale:!1,category:"activity"},medal_military:{keywords:["award","winning","army"],char:"\u{1f396}",fitzpatrick_scale:!1,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:"\u{1f947}",fitzpatrick_scale:!1,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:"\u{1f948}",fitzpatrick_scale:!1,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:"\u{1f949}",fitzpatrick_scale:!1,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:"\u{1f397}",fitzpatrick_scale:!1,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:"\u{1f3f5}",fitzpatrick_scale:!1,category:"activity"},ticket:{keywords:["event","concert","pass"],char:"\u{1f3ab}",fitzpatrick_scale:!1,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:"\u{1f39f}",fitzpatrick_scale:!1,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:"\u{1f3ad}",fitzpatrick_scale:!1,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:"\u{1f3a8}",fitzpatrick_scale:!1,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:"\u{1f3aa}",fitzpatrick_scale:!1,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:"\u{1f939}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:"\u{1f939}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:"\u{1f3a4}",fitzpatrick_scale:!1,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:"\u{1f3a7}",fitzpatrick_scale:!1,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:"\u{1f3bc}",fitzpatrick_scale:!1,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:"\u{1f3b9}",fitzpatrick_scale:!1,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:"\u{1f941}",fitzpatrick_scale:!1,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:"\u{1f3b7}",fitzpatrick_scale:!1,category:"activity"},trumpet:{keywords:["music","brass"],char:"\u{1f3ba}",fitzpatrick_scale:!1,category:"activity"},guitar:{keywords:["music","instrument"],char:"\u{1f3b8}",fitzpatrick_scale:!1,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:"\u{1f3bb}",fitzpatrick_scale:!1,category:"activity"},clapper:{keywords:["movie","film","record"],char:"\u{1f3ac}",fitzpatrick_scale:!1,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:"\u{1f3ae}",fitzpatrick_scale:!1,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:"\u{1f47e}",fitzpatrick_scale:!1,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:"\u{1f3af}",fitzpatrick_scale:!1,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:"\u{1f3b2}",fitzpatrick_scale:!1,category:"activity"},chess_pawn:{keywords:["expendable"],char:"\u265f",fitzpatrick_scale:!1,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:"\u{1f3b0}",fitzpatrick_scale:!1,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:"\u{1f9e9}",fitzpatrick_scale:!1,category:"activity"},bowling:{keywords:["sports","fun","play"],char:"\u{1f3b3}",fitzpatrick_scale:!1,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:"\u{1f697}",fitzpatrick_scale:!1,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:"\u{1f695}",fitzpatrick_scale:!1,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:"\u{1f699}",fitzpatrick_scale:!1,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:"\u{1f68c}",fitzpatrick_scale:!1,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:"\u{1f68e}",fitzpatrick_scale:!1,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:"\u{1f3ce}",fitzpatrick_scale:!1,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:"\u{1f693}",fitzpatrick_scale:!1,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:"\u{1f691}",fitzpatrick_scale:!1,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:"\u{1f692}",fitzpatrick_scale:!1,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:"\u{1f690}",fitzpatrick_scale:!1,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:"\u{1f69a}",fitzpatrick_scale:!1,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:"\u{1f69b}",fitzpatrick_scale:!1,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:"\u{1f69c}",fitzpatrick_scale:!1,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:"\u{1f6f4}",fitzpatrick_scale:!1,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:"\u{1f3cd}",fitzpatrick_scale:!1,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:"\u{1f6b2}",fitzpatrick_scale:!1,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:"\u{1f6f5}",fitzpatrick_scale:!1,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:"\u{1f6a8}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:"\u{1f694}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:"\u{1f68d}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:"\u{1f698}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:"\u{1f696}",fitzpatrick_scale:!1,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:"\u{1f6a1}",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:"\u{1f6a0}",fitzpatrick_scale:!1,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:"\u{1f69f}",fitzpatrick_scale:!1,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:"\u{1f683}",fitzpatrick_scale:!1,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:"\u{1f68b}",fitzpatrick_scale:!1,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:"\u{1f69d}",fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:"\u{1f684}",fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:"\u{1f685}",fitzpatrick_scale:!1,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:"\u{1f688}",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:"\u{1f69e}",fitzpatrick_scale:!1,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:"\u{1f682}",fitzpatrick_scale:!1,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:"\u{1f686}",fitzpatrick_scale:!1,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:"\u{1f687}",fitzpatrick_scale:!1,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:"\u{1f68a}",fitzpatrick_scale:!1,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:"\u{1f689}",fitzpatrick_scale:!1,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:"\u{1f6f8}",fitzpatrick_scale:!1,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:"\u{1f681}",fitzpatrick_scale:!1,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:"\u{1f6e9}",fitzpatrick_scale:!1,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:"\u2708\ufe0f",fitzpatrick_scale:!1,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:"\u{1f6eb}",fitzpatrick_scale:!1,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:"\u{1f6ec}",fitzpatrick_scale:!1,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:"\u26f5",fitzpatrick_scale:!1,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:"\u{1f6e5}",fitzpatrick_scale:!1,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:"\u{1f6a4}",fitzpatrick_scale:!1,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:"\u26f4",fitzpatrick_scale:!1,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:"\u{1f6f3}",fitzpatrick_scale:!1,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:"\u{1f680}",fitzpatrick_scale:!1,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:"\u{1f6f0}",fitzpatrick_scale:!1,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:"\u{1f4ba}",fitzpatrick_scale:!1,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:"\u{1f6f6}",fitzpatrick_scale:!1,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:"\u2693",fitzpatrick_scale:!1,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:"\u{1f6a7}",fitzpatrick_scale:!1,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:"\u26fd",fitzpatrick_scale:!1,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:"\u{1f68f}",fitzpatrick_scale:!1,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:"\u{1f6a6}",fitzpatrick_scale:!1,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:"\u{1f6a5}",fitzpatrick_scale:!1,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:"\u{1f3c1}",fitzpatrick_scale:!1,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:"\u{1f6a2}",fitzpatrick_scale:!1,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:"\u{1f3a1}",fitzpatrick_scale:!1,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:"\u{1f3a2}",fitzpatrick_scale:!1,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:"\u{1f3a0}",fitzpatrick_scale:!1,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:"\u{1f3d7}",fitzpatrick_scale:!1,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:"\u{1f301}",fitzpatrick_scale:!1,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:"\u{1f5fc}",fitzpatrick_scale:!1,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:"\u{1f3ed}",fitzpatrick_scale:!1,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:"\u26f2",fitzpatrick_scale:!1,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:"\u{1f391}",fitzpatrick_scale:!1,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:"\u26f0",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:"\u{1f3d4}",fitzpatrick_scale:!1,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:"\u{1f5fb}",fitzpatrick_scale:!1,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:"\u{1f30b}",fitzpatrick_scale:!1,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:"\u{1f5fe}",fitzpatrick_scale:!1,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:"\u{1f3d5}",fitzpatrick_scale:!1,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:"\u26fa",fitzpatrick_scale:!1,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:"\u{1f3de}",fitzpatrick_scale:!1,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:"\u{1f6e3}",fitzpatrick_scale:!1,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:"\u{1f6e4}",fitzpatrick_scale:!1,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:"\u{1f305}",fitzpatrick_scale:!1,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:"\u{1f304}",fitzpatrick_scale:!1,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:"\u{1f3dc}",fitzpatrick_scale:!1,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:"\u{1f3d6}",fitzpatrick_scale:!1,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:"\u{1f3dd}",fitzpatrick_scale:!1,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:"\u{1f307}",fitzpatrick_scale:!1,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:"\u{1f306}",fitzpatrick_scale:!1,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:"\u{1f3d9}",fitzpatrick_scale:!1,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:"\u{1f303}",fitzpatrick_scale:!1,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:"\u{1f309}",fitzpatrick_scale:!1,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:"\u{1f30c}",fitzpatrick_scale:!1,category:"travel_and_places"},stars:{keywords:["night","photo"],char:"\u{1f320}",fitzpatrick_scale:!1,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:"\u{1f387}",fitzpatrick_scale:!1,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:"\u{1f386}",fitzpatrick_scale:!1,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:"\u{1f308}",fitzpatrick_scale:!1,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:"\u{1f3d8}",fitzpatrick_scale:!1,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:"\u{1f3f0}",fitzpatrick_scale:!1,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:"\u{1f3ef}",fitzpatrick_scale:!1,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:"\u{1f3df}",fitzpatrick_scale:!1,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:"\u{1f5fd}",fitzpatrick_scale:!1,category:"travel_and_places"},house:{keywords:["building","home"],char:"\u{1f3e0}",fitzpatrick_scale:!1,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:"\u{1f3e1}",fitzpatrick_scale:!1,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:"\u{1f3da}",fitzpatrick_scale:!1,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:"\u{1f3e2}",fitzpatrick_scale:!1,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:"\u{1f3ec}",fitzpatrick_scale:!1,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:"\u{1f3e3}",fitzpatrick_scale:!1,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:"\u{1f3e4}",fitzpatrick_scale:!1,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:"\u{1f3e5}",fitzpatrick_scale:!1,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:"\u{1f3e6}",fitzpatrick_scale:!1,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:"\u{1f3e8}",fitzpatrick_scale:!1,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:"\u{1f3ea}",fitzpatrick_scale:!1,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:"\u{1f3eb}",fitzpatrick_scale:!1,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:"\u{1f3e9}",fitzpatrick_scale:!1,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:"\u{1f492}",fitzpatrick_scale:!1,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:"\u{1f3db}",fitzpatrick_scale:!1,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:"\u26ea",fitzpatrick_scale:!1,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:"\u{1f54c}",fitzpatrick_scale:!1,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:"\u{1f54d}",fitzpatrick_scale:!1,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:"\u{1f54b}",fitzpatrick_scale:!1,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:"\u26e9",fitzpatrick_scale:!1,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:"\u231a",fitzpatrick_scale:!1,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:"\u{1f4f1}",fitzpatrick_scale:!1,category:"objects"},calling:{keywords:["iphone","incoming"],char:"\u{1f4f2}",fitzpatrick_scale:!1,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:"\u{1f4bb}",fitzpatrick_scale:!1,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:"\u2328",fitzpatrick_scale:!1,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:"\u{1f5a5}",fitzpatrick_scale:!1,category:"objects"},printer:{keywords:["paper","ink"],char:"\u{1f5a8}",fitzpatrick_scale:!1,category:"objects"},computer_mouse:{keywords:["click"],char:"\u{1f5b1}",fitzpatrick_scale:!1,category:"objects"},trackball:{keywords:["technology","trackpad"],char:"\u{1f5b2}",fitzpatrick_scale:!1,category:"objects"},joystick:{keywords:["game","play"],char:"\u{1f579}",fitzpatrick_scale:!1,category:"objects"},clamp:{keywords:["tool"],char:"\u{1f5dc}",fitzpatrick_scale:!1,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:"\u{1f4bd}",fitzpatrick_scale:!1,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:"\u{1f4be}",fitzpatrick_scale:!1,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:"\u{1f4bf}",fitzpatrick_scale:!1,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:"\u{1f4c0}",fitzpatrick_scale:!1,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:"\u{1f4fc}",fitzpatrick_scale:!1,category:"objects"},camera:{keywords:["gadgets","photography"],char:"\u{1f4f7}",fitzpatrick_scale:!1,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:"\u{1f4f8}",fitzpatrick_scale:!1,category:"objects"},video_camera:{keywords:["film","record"],char:"\u{1f4f9}",fitzpatrick_scale:!1,category:"objects"},movie_camera:{keywords:["film","record"],char:"\u{1f3a5}",fitzpatrick_scale:!1,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:"\u{1f4fd}",fitzpatrick_scale:!1,category:"objects"},film_strip:{keywords:["movie"],char:"\u{1f39e}",fitzpatrick_scale:!1,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:"\u{1f4de}",fitzpatrick_scale:!1,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:"\u260e\ufe0f",fitzpatrick_scale:!1,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:"\u{1f4df}",fitzpatrick_scale:!1,category:"objects"},fax:{keywords:["communication","technology"],char:"\u{1f4e0}",fitzpatrick_scale:!1,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:"\u{1f4fa}",fitzpatrick_scale:!1,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:"\u{1f4fb}",fitzpatrick_scale:!1,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:"\u{1f399}",fitzpatrick_scale:!1,category:"objects"},level_slider:{keywords:["scale"],char:"\u{1f39a}",fitzpatrick_scale:!1,category:"objects"},control_knobs:{keywords:["dial"],char:"\u{1f39b}",fitzpatrick_scale:!1,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:"\u{1f9ed}",fitzpatrick_scale:!1,category:"objects"},stopwatch:{keywords:["time","deadline"],char:"\u23f1",fitzpatrick_scale:!1,category:"objects"},timer_clock:{keywords:["alarm"],char:"\u23f2",fitzpatrick_scale:!1,category:"objects"},alarm_clock:{keywords:["time","wake"],char:"\u23f0",fitzpatrick_scale:!1,category:"objects"},mantelpiece_clock:{keywords:["time"],char:"\u{1f570}",fitzpatrick_scale:!1,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:"\u23f3",fitzpatrick_scale:!1,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:"\u231b",fitzpatrick_scale:!1,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:"\u{1f4e1}",fitzpatrick_scale:!1,category:"objects"},battery:{keywords:["power","energy","sustain"],char:"\u{1f50b}",fitzpatrick_scale:!1,category:"objects"},electric_plug:{keywords:["charger","power"],char:"\u{1f50c}",fitzpatrick_scale:!1,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:"\u{1f4a1}",fitzpatrick_scale:!1,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:"\u{1f526}",fitzpatrick_scale:!1,category:"objects"},candle:{keywords:["fire","wax"],char:"\u{1f56f}",fitzpatrick_scale:!1,category:"objects"},fire_extinguisher:{keywords:["quench"],char:"\u{1f9ef}",fitzpatrick_scale:!1,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:"\u{1f5d1}",fitzpatrick_scale:!1,category:"objects"},oil_drum:{keywords:["barrell"],char:"\u{1f6e2}",fitzpatrick_scale:!1,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:"\u{1f4b8}",fitzpatrick_scale:!1,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:"\u{1f4b5}",fitzpatrick_scale:!1,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:"\u{1f4b4}",fitzpatrick_scale:!1,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:"\u{1f4b6}",fitzpatrick_scale:!1,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:"\u{1f4b7}",fitzpatrick_scale:!1,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:"\u{1f4b0}",fitzpatrick_scale:!1,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:"\u{1f4b3}",fitzpatrick_scale:!1,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:"\u{1f48e}",fitzpatrick_scale:!1,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:"\u2696",fitzpatrick_scale:!1,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:"\u{1f9f0}",fitzpatrick_scale:!1,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:"\u{1f527}",fitzpatrick_scale:!1,category:"objects"},hammer:{keywords:["tools","build","create"],char:"\u{1f528}",fitzpatrick_scale:!1,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:"\u2692",fitzpatrick_scale:!1,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:"\u{1f6e0}",fitzpatrick_scale:!1,category:"objects"},pick:{keywords:["tools","dig"],char:"\u26cf",fitzpatrick_scale:!1,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:"\u{1f529}",fitzpatrick_scale:!1,category:"objects"},gear:{keywords:["cog"],char:"\u2699",fitzpatrick_scale:!1,category:"objects"},brick:{keywords:["bricks"],char:"\u{1f9f1}",fitzpatrick_scale:!1,category:"objects"},chains:{keywords:["lock","arrest"],char:"\u26d3",fitzpatrick_scale:!1,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:"\u{1f9f2}",fitzpatrick_scale:!1,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:"\u{1f52b}",fitzpatrick_scale:!1,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:"\u{1f4a3}",fitzpatrick_scale:!1,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:"\u{1f9e8}",fitzpatrick_scale:!1,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:"\u{1f52a}",fitzpatrick_scale:!1,category:"objects"},dagger:{keywords:["weapon"],char:"\u{1f5e1}",fitzpatrick_scale:!1,category:"objects"},crossed_swords:{keywords:["weapon"],char:"\u2694",fitzpatrick_scale:!1,category:"objects"},shield:{keywords:["protection","security"],char:"\u{1f6e1}",fitzpatrick_scale:!1,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:"\u{1f6ac}",fitzpatrick_scale:!1,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:"\u2620",fitzpatrick_scale:!1,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:"\u26b0",fitzpatrick_scale:!1,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:"\u26b1",fitzpatrick_scale:!1,category:"objects"},amphora:{keywords:["vase","jar"],char:"\u{1f3fa}",fitzpatrick_scale:!1,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:"\u{1f52e}",fitzpatrick_scale:!1,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:"\u{1f4ff}",fitzpatrick_scale:!1,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:"\u{1f9ff}",fitzpatrick_scale:!1,category:"objects"},barber:{keywords:["hair","salon","style"],char:"\u{1f488}",fitzpatrick_scale:!1,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:"\u2697",fitzpatrick_scale:!1,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:"\u{1f52d}",fitzpatrick_scale:!1,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:"\u{1f52c}",fitzpatrick_scale:!1,category:"objects"},hole:{keywords:["embarrassing"],char:"\u{1f573}",fitzpatrick_scale:!1,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:"\u{1f48a}",fitzpatrick_scale:!1,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:"\u{1f489}",fitzpatrick_scale:!1,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:"\u{1f9ec}",fitzpatrick_scale:!1,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:"\u{1f9a0}",fitzpatrick_scale:!1,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:"\u{1f9eb}",fitzpatrick_scale:!1,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:"\u{1f9ea}",fitzpatrick_scale:!1,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:"\u{1f321}",fitzpatrick_scale:!1,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:"\u{1f9f9}",fitzpatrick_scale:!1,category:"objects"},basket:{keywords:["laundry"],char:"\u{1f9fa}",fitzpatrick_scale:!1,category:"objects"},toilet_paper:{keywords:["roll"],char:"\u{1f9fb}",fitzpatrick_scale:!1,category:"objects"},label:{keywords:["sale","tag"],char:"\u{1f3f7}",fitzpatrick_scale:!1,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:"\u{1f516}",fitzpatrick_scale:!1,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:"\u{1f6bd}",fitzpatrick_scale:!1,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:"\u{1f6bf}",fitzpatrick_scale:!1,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:"\u{1f6c1}",fitzpatrick_scale:!1,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:"\u{1f9fc}",fitzpatrick_scale:!1,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:"\u{1f9fd}",fitzpatrick_scale:!1,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:"\u{1f9f4}",fitzpatrick_scale:!1,category:"objects"},key:{keywords:["lock","door","password"],char:"\u{1f511}",fitzpatrick_scale:!1,category:"objects"},old_key:{keywords:["lock","door","password"],char:"\u{1f5dd}",fitzpatrick_scale:!1,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:"\u{1f6cb}",fitzpatrick_scale:!1,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:"\u{1f6cc}",fitzpatrick_scale:!0,category:"objects"},bed:{keywords:["sleep","rest"],char:"\u{1f6cf}",fitzpatrick_scale:!1,category:"objects"},door:{keywords:["house","entry","exit"],char:"\u{1f6aa}",fitzpatrick_scale:!1,category:"objects"},bellhop_bell:{keywords:["service"],char:"\u{1f6ce}",fitzpatrick_scale:!1,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:"\u{1f9f8}",fitzpatrick_scale:!1,category:"objects"},framed_picture:{keywords:["photography"],char:"\u{1f5bc}",fitzpatrick_scale:!1,category:"objects"},world_map:{keywords:["location","direction"],char:"\u{1f5fa}",fitzpatrick_scale:!1,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:"\u26f1",fitzpatrick_scale:!1,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:"\u{1f5ff}",fitzpatrick_scale:!1,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:"\u{1f6cd}",fitzpatrick_scale:!1,category:"objects"},shopping_cart:{keywords:["trolley"],char:"\u{1f6d2}",fitzpatrick_scale:!1,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:"\u{1f388}",fitzpatrick_scale:!1,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:"\u{1f38f}",fitzpatrick_scale:!1,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:"\u{1f380}",fitzpatrick_scale:!1,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:"\u{1f381}",fitzpatrick_scale:!1,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:"\u{1f38a}",fitzpatrick_scale:!1,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:"\u{1f389}",fitzpatrick_scale:!1,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:"\u{1f38e}",fitzpatrick_scale:!1,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:"\u{1f390}",fitzpatrick_scale:!1,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:"\u{1f38c}",fitzpatrick_scale:!1,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:"\u{1f3ee}",fitzpatrick_scale:!1,category:"objects"},red_envelope:{keywords:["gift"],char:"\u{1f9e7}",fitzpatrick_scale:!1,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:"\u2709\ufe0f",fitzpatrick_scale:!1,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:"\u{1f4e9}",fitzpatrick_scale:!1,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:"\u{1f4e8}",fitzpatrick_scale:!1,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:"\u{1f4e7}",fitzpatrick_scale:!1,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:"\u{1f48c}",fitzpatrick_scale:!1,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:"\u{1f4ee}",fitzpatrick_scale:!1,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:"\u{1f4ea}",fitzpatrick_scale:!1,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:"\u{1f4eb}",fitzpatrick_scale:!1,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:"\u{1f4ec}",fitzpatrick_scale:!1,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:"\u{1f4ed}",fitzpatrick_scale:!1,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:"\u{1f4e6}",fitzpatrick_scale:!1,category:"objects"},postal_horn:{keywords:["instrument","music"],char:"\u{1f4ef}",fitzpatrick_scale:!1,category:"objects"},inbox_tray:{keywords:["email","documents"],char:"\u{1f4e5}",fitzpatrick_scale:!1,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:"\u{1f4e4}",fitzpatrick_scale:!1,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:"\u{1f4dc}",fitzpatrick_scale:!1,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:"\u{1f4c3}",fitzpatrick_scale:!1,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:"\u{1f4d1}",fitzpatrick_scale:!1,category:"objects"},receipt:{keywords:["accounting","expenses"],char:"\u{1f9fe}",fitzpatrick_scale:!1,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:"\u{1f4ca}",fitzpatrick_scale:!1,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:"\u{1f4c8}",fitzpatrick_scale:!1,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:"\u{1f4c9}",fitzpatrick_scale:!1,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:"\u{1f4c4}",fitzpatrick_scale:!1,category:"objects"},date:{keywords:["calendar","schedule"],char:"\u{1f4c5}",fitzpatrick_scale:!1,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:"\u{1f4c6}",fitzpatrick_scale:!1,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:"\u{1f5d3}",fitzpatrick_scale:!1,category:"objects"},card_index:{keywords:["business","stationery"],char:"\u{1f4c7}",fitzpatrick_scale:!1,category:"objects"},card_file_box:{keywords:["business","stationery"],char:"\u{1f5c3}",fitzpatrick_scale:!1,category:"objects"},ballot_box:{keywords:["election","vote"],char:"\u{1f5f3}",fitzpatrick_scale:!1,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:"\u{1f5c4}",fitzpatrick_scale:!1,category:"objects"},clipboard:{keywords:["stationery","documents"],char:"\u{1f4cb}",fitzpatrick_scale:!1,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:"\u{1f5d2}",fitzpatrick_scale:!1,category:"objects"},file_folder:{keywords:["documents","business","office"],char:"\u{1f4c1}",fitzpatrick_scale:!1,category:"objects"},open_file_folder:{keywords:["documents","load"],char:"\u{1f4c2}",fitzpatrick_scale:!1,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:"\u{1f5c2}",fitzpatrick_scale:!1,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:"\u{1f5de}",fitzpatrick_scale:!1,category:"objects"},newspaper:{keywords:["press","headline"],char:"\u{1f4f0}",fitzpatrick_scale:!1,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:"\u{1f4d3}",fitzpatrick_scale:!1,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:"\u{1f4d5}",fitzpatrick_scale:!1,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:"\u{1f4d7}",fitzpatrick_scale:!1,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:"\u{1f4d8}",fitzpatrick_scale:!1,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:"\u{1f4d9}",fitzpatrick_scale:!1,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:"\u{1f4d4}",fitzpatrick_scale:!1,category:"objects"},ledger:{keywords:["notes","paper"],char:"\u{1f4d2}",fitzpatrick_scale:!1,category:"objects"},books:{keywords:["literature","library","study"],char:"\u{1f4da}",fitzpatrick_scale:!1,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:"\u{1f4d6}",fitzpatrick_scale:!1,category:"objects"},safety_pin:{keywords:["diaper"],char:"\u{1f9f7}",fitzpatrick_scale:!1,category:"objects"},link:{keywords:["rings","url"],char:"\u{1f517}",fitzpatrick_scale:!1,category:"objects"},paperclip:{keywords:["documents","stationery"],char:"\u{1f4ce}",fitzpatrick_scale:!1,category:"objects"},paperclips:{keywords:["documents","stationery"],char:"\u{1f587}",fitzpatrick_scale:!1,category:"objects"},scissors:{keywords:["stationery","cut"],char:"\u2702\ufe0f",fitzpatrick_scale:!1,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:"\u{1f4d0}",fitzpatrick_scale:!1,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:"\u{1f4cf}",fitzpatrick_scale:!1,category:"objects"},abacus:{keywords:["calculation"],char:"\u{1f9ee}",fitzpatrick_scale:!1,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:"\u{1f4cc}",fitzpatrick_scale:!1,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:"\u{1f4cd}",fitzpatrick_scale:!1,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:"\u{1f6a9}",fitzpatrick_scale:!1,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:"\u{1f3f3}",fitzpatrick_scale:!1,category:"objects"},black_flag:{keywords:["pirate"],char:"\u{1f3f4}",fitzpatrick_scale:!1,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:"\u{1f3f3}\ufe0f\u200d\u{1f308}",fitzpatrick_scale:!1,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:"\u{1f510}",fitzpatrick_scale:!1,category:"objects"},lock:{keywords:["security","password","padlock"],char:"\u{1f512}",fitzpatrick_scale:!1,category:"objects"},unlock:{keywords:["privacy","security"],char:"\u{1f513}",fitzpatrick_scale:!1,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:"\u{1f50f}",fitzpatrick_scale:!1,category:"objects"},pen:{keywords:["stationery","writing","write"],char:"\u{1f58a}",fitzpatrick_scale:!1,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:"\u{1f58b}",fitzpatrick_scale:!1,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:"\u2712\ufe0f",fitzpatrick_scale:!1,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:"\u{1f4dd}",fitzpatrick_scale:!1,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:"\u270f\ufe0f",fitzpatrick_scale:!1,category:"objects"},crayon:{keywords:["drawing","creativity"],char:"\u{1f58d}",fitzpatrick_scale:!1,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:"\u{1f58c}",fitzpatrick_scale:!1,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:"\u{1f50d}",fitzpatrick_scale:!1,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:"\u{1f50e}",fitzpatrick_scale:!1,category:"objects"},heart:{keywords:["love","like","valentines"],char:"\u2764\ufe0f",fitzpatrick_scale:!1,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f9e1}",fitzpatrick_scale:!1,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f49b}",fitzpatrick_scale:!1,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f49a}",fitzpatrick_scale:!1,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f499}",fitzpatrick_scale:!1,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f49c}",fitzpatrick_scale:!1,category:"symbols"},black_heart:{keywords:["evil"],char:"\u{1f5a4}",fitzpatrick_scale:!1,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:"\u{1f494}",fitzpatrick_scale:!1,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:"\u2763",fitzpatrick_scale:!1,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:"\u{1f495}",fitzpatrick_scale:!1,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:"\u{1f49e}",fitzpatrick_scale:!1,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:"\u{1f493}",fitzpatrick_scale:!1,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:"\u{1f497}",fitzpatrick_scale:!1,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f496}",fitzpatrick_scale:!1,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:"\u{1f498}",fitzpatrick_scale:!1,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:"\u{1f49d}",fitzpatrick_scale:!1,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:"\u{1f49f}",fitzpatrick_scale:!1,category:"symbols"},peace_symbol:{keywords:["hippie"],char:"\u262e",fitzpatrick_scale:!1,category:"symbols"},latin_cross:{keywords:["christianity"],char:"\u271d",fitzpatrick_scale:!1,category:"symbols"},star_and_crescent:{keywords:["islam"],char:"\u262a",fitzpatrick_scale:!1,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"\u{1f549}",fitzpatrick_scale:!1,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"\u2638",fitzpatrick_scale:!1,category:"symbols"},star_of_david:{keywords:["judaism"],char:"\u2721",fitzpatrick_scale:!1,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:"\u{1f52f}",fitzpatrick_scale:!1,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:"\u{1f54e}",fitzpatrick_scale:!1,category:"symbols"},yin_yang:{keywords:["balance"],char:"\u262f",fitzpatrick_scale:!1,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:"\u2626",fitzpatrick_scale:!1,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:"\u{1f6d0}",fitzpatrick_scale:!1,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:"\u26ce",fitzpatrick_scale:!1,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u2648",fitzpatrick_scale:!1,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:"\u2649",fitzpatrick_scale:!1,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264a",fitzpatrick_scale:!1,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264b",fitzpatrick_scale:!1,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u264c",fitzpatrick_scale:!1,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264d",fitzpatrick_scale:!1,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u264e",fitzpatrick_scale:!1,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:"\u264f",fitzpatrick_scale:!1,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u2650",fitzpatrick_scale:!1,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u2651",fitzpatrick_scale:!1,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u2652",fitzpatrick_scale:!1,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:"\u2653",fitzpatrick_scale:!1,category:"symbols"},id:{keywords:["purple-square","words"],char:"\u{1f194}",fitzpatrick_scale:!1,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:"\u269b",fitzpatrick_scale:!1,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:"\u{1f233}",fitzpatrick_scale:!1,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:"\u{1f239}",fitzpatrick_scale:!1,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:"\u2622",fitzpatrick_scale:!1,category:"symbols"},biohazard:{keywords:["danger"],char:"\u2623",fitzpatrick_scale:!1,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:"\u{1f4f4}",fitzpatrick_scale:!1,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:"\u{1f4f3}",fitzpatrick_scale:!1,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:"\u{1f236}",fitzpatrick_scale:!1,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:"\u{1f21a}",fitzpatrick_scale:!1,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:"\u{1f238}",fitzpatrick_scale:!1,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:"\u{1f23a}",fitzpatrick_scale:!1,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:"\u{1f237}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:"\u2734\ufe0f",fitzpatrick_scale:!1,category:"symbols"},vs:{keywords:["words","orange-square"],char:"\u{1f19a}",fitzpatrick_scale:!1,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:"\u{1f251}",fitzpatrick_scale:!1,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:"\u{1f4ae}",fitzpatrick_scale:!1,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:"\u{1f250}",fitzpatrick_scale:!1,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:"\u3299\ufe0f",fitzpatrick_scale:!1,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:"\u3297\ufe0f",fitzpatrick_scale:!1,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:"\u{1f234}",fitzpatrick_scale:!1,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:"\u{1f235}",fitzpatrick_scale:!1,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:"\u{1f232}",fitzpatrick_scale:!1,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:"\u{1f170}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:"\u{1f171}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:"\u{1f18e}",fitzpatrick_scale:!1,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:"\u{1f191}",fitzpatrick_scale:!1,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:"\u{1f17e}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:"\u{1f198}",fitzpatrick_scale:!1,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:"\u26d4",fitzpatrick_scale:!1,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:"\u{1f4db}",fitzpatrick_scale:!1,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:"\u{1f6ab}",fitzpatrick_scale:!1,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:"\u274c",fitzpatrick_scale:!1,category:"symbols"},o:{keywords:["circle","round"],char:"\u2b55",fitzpatrick_scale:!1,category:"symbols"},stop_sign:{keywords:["stop"],char:"\u{1f6d1}",fitzpatrick_scale:!1,category:"symbols"},anger:{keywords:["angry","mad"],char:"\u{1f4a2}",fitzpatrick_scale:!1,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:"\u2668\ufe0f",fitzpatrick_scale:!1,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:"\u{1f6b7}",fitzpatrick_scale:!1,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:"\u{1f6af}",fitzpatrick_scale:!1,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:"\u{1f6b3}",fitzpatrick_scale:!1,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:"\u{1f6b1}",fitzpatrick_scale:!1,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:"\u{1f51e}",fitzpatrick_scale:!1,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:"\u{1f4f5}",fitzpatrick_scale:!1,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:"\u2757",fitzpatrick_scale:!1,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:"\u2755",fitzpatrick_scale:!1,category:"symbols"},question:{keywords:["doubt","confused"],char:"\u2753",fitzpatrick_scale:!1,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:"\u2754",fitzpatrick_scale:!1,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:"\u203c\ufe0f",fitzpatrick_scale:!1,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:"\u2049\ufe0f",fitzpatrick_scale:!1,category:"symbols"},100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:"\u{1f4af}",fitzpatrick_scale:!1,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:"\u{1f505}",fitzpatrick_scale:!1,category:"symbols"},high_brightness:{keywords:["sun","light"],char:"\u{1f506}",fitzpatrick_scale:!1,category:"symbols"},trident:{keywords:["weapon","spear"],char:"\u{1f531}",fitzpatrick_scale:!1,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:"\u269c",fitzpatrick_scale:!1,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:"\u303d\ufe0f",fitzpatrick_scale:!1,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:"\u26a0\ufe0f",fitzpatrick_scale:!1,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:"\u{1f6b8}",fitzpatrick_scale:!1,category:"symbols"},beginner:{keywords:["badge","shield"],char:"\u{1f530}",fitzpatrick_scale:!1,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:"\u267b\ufe0f",fitzpatrick_scale:!1,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:"\u{1f22f}",fitzpatrick_scale:!1,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:"\u{1f4b9}",fitzpatrick_scale:!1,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:"\u2747\ufe0f",fitzpatrick_scale:!1,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:"\u2733\ufe0f",fitzpatrick_scale:!1,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:"\u274e",fitzpatrick_scale:!1,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:"\u2705",fitzpatrick_scale:!1,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:"\u{1f4a0}",fitzpatrick_scale:!1,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:"\u{1f300}",fitzpatrick_scale:!1,category:"symbols"},loop:{keywords:["tape","cassette"],char:"\u27bf",fitzpatrick_scale:!1,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:"\u{1f310}",fitzpatrick_scale:!1,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:"\u24c2\ufe0f",fitzpatrick_scale:!1,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:"\u{1f3e7}",fitzpatrick_scale:!1,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:"\u{1f202}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:"\u{1f6c2}",fitzpatrick_scale:!1,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:"\u{1f6c3}",fitzpatrick_scale:!1,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:"\u{1f6c4}",fitzpatrick_scale:!1,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:"\u{1f6c5}",fitzpatrick_scale:!1,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:"\u267f",fitzpatrick_scale:!1,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:"\u{1f6ad}",fitzpatrick_scale:!1,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:"\u{1f6be}",fitzpatrick_scale:!1,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:"\u{1f17f}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:"\u{1f6b0}",fitzpatrick_scale:!1,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:"\u{1f6b9}",fitzpatrick_scale:!1,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:"\u{1f6ba}",fitzpatrick_scale:!1,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:"\u{1f6bc}",fitzpatrick_scale:!1,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:"\u{1f6bb}",fitzpatrick_scale:!1,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:"\u{1f6ae}",fitzpatrick_scale:!1,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:"\u{1f3a6}",fitzpatrick_scale:!1,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:"\u{1f4f6}",fitzpatrick_scale:!1,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:"\u{1f201}",fitzpatrick_scale:!1,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:"\u{1f196}",fitzpatrick_scale:!1,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:"\u{1f197}",fitzpatrick_scale:!1,category:"symbols"},up:{keywords:["blue-square","above","high"],char:"\u{1f199}",fitzpatrick_scale:!1,category:"symbols"},cool:{keywords:["words","blue-square"],char:"\u{1f192}",fitzpatrick_scale:!1,category:"symbols"},new:{keywords:["blue-square","words","start"],char:"\u{1f195}",fitzpatrick_scale:!1,category:"symbols"},free:{keywords:["blue-square","words"],char:"\u{1f193}",fitzpatrick_scale:!1,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:"0\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:"1\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:"2\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:"3\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:"4\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:"5\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:"6\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:"7\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:"8\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:"9\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:"\u{1f51f}",fitzpatrick_scale:!1,category:"symbols"},asterisk:{keywords:["star","keycap"],char:"*\u20e3",fitzpatrick_scale:!1,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:"\u{1f522}",fitzpatrick_scale:!1,category:"symbols"},eject_button:{keywords:["blue-square"],char:"\u23cf\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:"\u25b6\ufe0f",fitzpatrick_scale:!1,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:"\u23f8",fitzpatrick_scale:!1,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:"\u23ed",fitzpatrick_scale:!1,category:"symbols"},stop_button:{keywords:["blue-square"],char:"\u23f9",fitzpatrick_scale:!1,category:"symbols"},record_button:{keywords:["blue-square"],char:"\u23fa",fitzpatrick_scale:!1,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:"\u23ef",fitzpatrick_scale:!1,category:"symbols"},previous_track_button:{keywords:["backward"],char:"\u23ee",fitzpatrick_scale:!1,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:"\u23e9",fitzpatrick_scale:!1,category:"symbols"},rewind:{keywords:["play","blue-square"],char:"\u23ea",fitzpatrick_scale:!1,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:"\u{1f500}",fitzpatrick_scale:!1,category:"symbols"},repeat:{keywords:["loop","record"],char:"\u{1f501}",fitzpatrick_scale:!1,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:"\u{1f502}",fitzpatrick_scale:!1,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:"\u25c0\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:"\u{1f53c}",fitzpatrick_scale:!1,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:"\u{1f53d}",fitzpatrick_scale:!1,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:"\u23eb",fitzpatrick_scale:!1,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:"\u23ec",fitzpatrick_scale:!1,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:"\u27a1\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:"\u2b05\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:"\u2b06\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:"\u2b07\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:"\u2197\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:"\u2198\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:"\u2199\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:"\u2196\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:"\u2195\ufe0f",fitzpatrick_scale:!1,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:"\u2194\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:"\u{1f504}",fitzpatrick_scale:!1,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:"\u21aa\ufe0f",fitzpatrick_scale:!1,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:"\u21a9\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:"\u2934\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:"\u2935\ufe0f",fitzpatrick_scale:!1,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:"#\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:"\u2139\ufe0f",fitzpatrick_scale:!1,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:"\u{1f524}",fitzpatrick_scale:!1,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:"\u{1f521}",fitzpatrick_scale:!1,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:"\u{1f520}",fitzpatrick_scale:!1,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:"\u{1f523}",fitzpatrick_scale:!1,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:"\u{1f3b5}",fitzpatrick_scale:!1,category:"symbols"},notes:{keywords:["music","score"],char:"\u{1f3b6}",fitzpatrick_scale:!1,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:"\u3030\ufe0f",fitzpatrick_scale:!1,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:"\u27b0",fitzpatrick_scale:!1,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:"\u2714\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:"\u{1f503}",fitzpatrick_scale:!1,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:"\u2795",fitzpatrick_scale:!1,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:"\u2796",fitzpatrick_scale:!1,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:"\u2797",fitzpatrick_scale:!1,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:"\u2716\ufe0f",fitzpatrick_scale:!1,category:"symbols"},infinity:{keywords:["forever"],char:"\u267e",fitzpatrick_scale:!1,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:"\u{1f4b2}",fitzpatrick_scale:!1,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:"\u{1f4b1}",fitzpatrick_scale:!1,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:"\xa9\ufe0f",fitzpatrick_scale:!1,category:"symbols"},registered:{keywords:["alphabet","circle"],char:"\xae\ufe0f",fitzpatrick_scale:!1,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:"\u2122\ufe0f",fitzpatrick_scale:!1,category:"symbols"},end:{keywords:["words","arrow"],char:"\u{1f51a}",fitzpatrick_scale:!1,category:"symbols"},back:{keywords:["arrow","words","return"],char:"\u{1f519}",fitzpatrick_scale:!1,category:"symbols"},on:{keywords:["arrow","words"],char:"\u{1f51b}",fitzpatrick_scale:!1,category:"symbols"},top:{keywords:["words","blue-square"],char:"\u{1f51d}",fitzpatrick_scale:!1,category:"symbols"},soon:{keywords:["arrow","words"],char:"\u{1f51c}",fitzpatrick_scale:!1,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:"\u2611\ufe0f",fitzpatrick_scale:!1,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:"\u{1f518}",fitzpatrick_scale:!1,category:"symbols"},white_circle:{keywords:["shape","round"],char:"\u26aa",fitzpatrick_scale:!1,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:"\u26ab",fitzpatrick_scale:!1,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:"\u{1f534}",fitzpatrick_scale:!1,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:"\u{1f535}",fitzpatrick_scale:!1,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f538}",fitzpatrick_scale:!1,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f539}",fitzpatrick_scale:!1,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f536}",fitzpatrick_scale:!1,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f537}",fitzpatrick_scale:!1,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:"\u{1f53a}",fitzpatrick_scale:!1,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:"\u25aa\ufe0f",fitzpatrick_scale:!1,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:"\u25ab\ufe0f",fitzpatrick_scale:!1,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:"\u2b1b",fitzpatrick_scale:!1,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:"\u2b1c",fitzpatrick_scale:!1,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:"\u{1f53b}",fitzpatrick_scale:!1,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:"\u25fc\ufe0f",fitzpatrick_scale:!1,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:"\u25fb\ufe0f",fitzpatrick_scale:!1,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:"\u25fe",fitzpatrick_scale:!1,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:"\u25fd",fitzpatrick_scale:!1,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:"\u{1f532}",fitzpatrick_scale:!1,category:"symbols"},white_square_button:{keywords:["shape","input"],char:"\u{1f533}",fitzpatrick_scale:!1,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:"\u{1f508}",fitzpatrick_scale:!1,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:"\u{1f509}",fitzpatrick_scale:!1,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:"\u{1f50a}",fitzpatrick_scale:!1,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:"\u{1f507}",fitzpatrick_scale:!1,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:"\u{1f4e3}",fitzpatrick_scale:!1,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:"\u{1f4e2}",fitzpatrick_scale:!1,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:"\u{1f514}",fitzpatrick_scale:!1,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:"\u{1f515}",fitzpatrick_scale:!1,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:"\u{1f0cf}",fitzpatrick_scale:!1,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:"\u{1f004}",fitzpatrick_scale:!1,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:"\u2660\ufe0f",fitzpatrick_scale:!1,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:"\u2663\ufe0f",fitzpatrick_scale:!1,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:"\u2665\ufe0f",fitzpatrick_scale:!1,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:"\u2666\ufe0f",fitzpatrick_scale:!1,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:"\u{1f3b4}",fitzpatrick_scale:!1,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:"\u{1f4ad}",fitzpatrick_scale:!1,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:"\u{1f5ef}",fitzpatrick_scale:!1,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:"\u{1f4ac}",fitzpatrick_scale:!1,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:"\u{1f5e8}",fitzpatrick_scale:!1,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:"\u{1f550}",fitzpatrick_scale:!1,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:"\u{1f551}",fitzpatrick_scale:!1,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:"\u{1f552}",fitzpatrick_scale:!1,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:"\u{1f553}",fitzpatrick_scale:!1,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:"\u{1f554}",fitzpatrick_scale:!1,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:"\u{1f555}",fitzpatrick_scale:!1,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:"\u{1f556}",fitzpatrick_scale:!1,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:"\u{1f557}",fitzpatrick_scale:!1,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:"\u{1f558}",fitzpatrick_scale:!1,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:"\u{1f559}",fitzpatrick_scale:!1,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:"\u{1f55a}",fitzpatrick_scale:!1,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:"\u{1f55b}",fitzpatrick_scale:!1,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:"\u{1f55c}",fitzpatrick_scale:!1,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:"\u{1f55d}",fitzpatrick_scale:!1,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:"\u{1f55e}",fitzpatrick_scale:!1,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:"\u{1f55f}",fitzpatrick_scale:!1,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:"\u{1f560}",fitzpatrick_scale:!1,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:"\u{1f561}",fitzpatrick_scale:!1,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:"\u{1f562}",fitzpatrick_scale:!1,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:"\u{1f563}",fitzpatrick_scale:!1,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:"\u{1f564}",fitzpatrick_scale:!1,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:"\u{1f565}",fitzpatrick_scale:!1,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:"\u{1f566}",fitzpatrick_scale:!1,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:"\u{1f567}",fitzpatrick_scale:!1,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},aland_islands:{keywords:["\xc5land","islands","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:"\u{1f1e8}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},curacao:{keywords:["cura\xe7ao","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:"\u{1f1ea}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:"\u{1f1eb}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:"\u{1f1e9}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:"\u{1f1ef}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:"\u{1f1ef}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:"\u{1f1ef}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:"\u{1f1ef}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:"\u{1f1fd}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:"\u{1f1fe}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:"\u{1f1f0}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:"\u{1f1f4}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:"\u{1f1f6}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},reunion:{keywords:["r\xe9union","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},st_barthelemy:{keywords:["saint","barth\xe9lemy","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:"\u{1f1fc}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:"\u{1f1ff}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:"\u{1f1f0}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:"\u{1f1ec}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},england:{keywords:["flag","english"],char:"\u{1f3f4}\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}",fitzpatrick_scale:!1,category:"flags"},scotland:{keywords:["flag","scottish"],char:"\u{1f3f4}\u{e0067}\u{e0062}\u{e0073}\u{e0063}\u{e0074}\u{e007f}",fitzpatrick_scale:!1,category:"flags"},wales:{keywords:["flag","welsh"],char:"\u{1f3f4}\u{e0067}\u{e0062}\u{e0077}\u{e006c}\u{e0073}\u{e007f}",fitzpatrick_scale:!1,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:"\u{1f1fc}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:"\u{1f1fe}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:"\u{1f1ff}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:"\u{1f1ff}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:"\u{1f1fa}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:"\u{1f3f4}\u200d\u2620\ufe0f",fitzpatrick_scale:!1,category:"flags"}}); \ No newline at end of file diff --git a/static/js/tinymce/plugins/emoticons/plugin.min.js b/static/js/tinymce/plugins/emoticons/plugin.min.js new file mode 100644 index 0000000..f47f5f2 --- /dev/null +++ b/static/js/tinymce/plugins/emoticons/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>t===e,o=e(null),n=e(void 0),s=()=>{},r=()=>!1;class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return null==t?a.none():a.some(t)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const i=(t,e)=>{const o=t.length,n=new Array(o);for(let s=0;s{let e=t;return{get:()=>e,set:t=>{e=t}}},c=Object.keys,u=Object.hasOwnProperty,g=(t,e)=>{const o=c(t);for(let n=0,s=o.length;nu.call(t,e),d=(h=(t,e)=>e,(...t)=>{if(0===t.length)throw new Error("Can't merge zero objects");const e={};for(let o=0;o{const t=(t=>{const e=l(a.none()),o=()=>e.get().each(t);return{clear:()=>{o(),e.set(a.none())},isSet:()=>e.get().isSome(),get:()=>e.get(),set:t=>{o(),e.set(a.some(t))}}})(s);return{...t,on:e=>t.get().each(e)}},v=(t,e,o=0,s)=>{const r=t.indexOf(e,o);return-1!==r&&(!!n(s)||r+e.length<=s)};var y=tinymce.util.Tools.resolve("tinymce.Resource");const f=t=>e=>e.options.get(t),b=f("emoticons_database"),w=f("emoticons_database_url"),C=f("emoticons_database_id"),_=f("emoticons_append"),j=f("emoticons_images_url"),k="All",A={symbols:"Symbols",people:"People",animals_and_nature:"Animals and Nature",food_and_drink:"Food and Drink",activity:"Activity",travel_and_places:"Travel and Places",objects:"Objects",flags:"Flags",user:"User Defined"},O=(t,e)=>m(t,e)?t[e]:e,x=t=>{const e=_(t);return o=t=>({keywords:[],category:"user",...t}),((t,e)=>{const o={};return g(t,((t,n)=>{const s=e(t,n);o[s.k]=s.v})),o})(e,((t,e)=>({k:e,v:o(t)})));var o},E=(t,e)=>v(t.title.toLowerCase(),e)||((t,o)=>{for(let o=0,s=t.length;o{const n=[],s=e.toLowerCase(),a=o.fold((()=>r),(t=>e=>e>=t));for(let o=0;o{const n={pattern:"",results:S(e.listAll(),"",a.some(300))},s=l(k),r=((t,e)=>{let n=null;const s=()=>{o(n)||(clearTimeout(n),n=null)};return{cancel:s,throttle:(...e)=>{s(),n=setTimeout((()=>{n=null,t.apply(null,e)}),200)}}})((t=>{(t=>{const o=t.getData(),n=s.get(),r=e.listCategory(n),i=S(r,o[L],n===k?a.some(300):a.none());t.setData({results:i})})(t)})),c={label:"Search",type:"input",name:L},u={type:"collection",name:"results"},g=()=>({title:"Emojis",size:"normal",body:{type:"tabpanel",tabs:i(e.listCategories(),(t=>({title:t,name:t,items:[c,u]})))},initialData:n,onTabChange:(t,e)=>{s.set(e.newTabName),r.throttle(t)},onChange:r.throttle,onAction:(e,o)=>{"results"===o.name&&(((t,e)=>{t.insertContent(e)})(t,o.value),e.close())},buttons:[{type:"cancel",text:"Close",primary:!0}]}),m=t.windowManager.open(g());m.focus(L),e.hasLoaded()||(m.block("Loading emojis..."),e.waitForLoad().then((()=>{m.redial(g()),r.throttle(m),m.focus(L),m.unblock()})).catch((t=>{m.redial({title:"Emojis",body:{type:"panel",items:[{type:"alertbanner",level:"error",icon:"warning",text:"Could not load emojis"}]},buttons:[{type:"cancel",text:"Close",primary:!0}],initialData:{pattern:"",results:[]}}),m.focus(L),m.unblock()})))},T=t=>e=>{const o=()=>{e.setEnabled(t.selection.isEditable())};return t.on("NodeChange",o),o(),()=>{t.off("NodeChange",o)}};t.add("emoticons",((t,e)=>{((t,e)=>{const o=t.options.register;o("emoticons_database",{processor:"string",default:"emojis"}),o("emoticons_database_url",{processor:"string",default:`${e}/js/${b(t)}${t.suffix}.js`}),o("emoticons_database_id",{processor:"string",default:"tinymce.plugins.emoticons"}),o("emoticons_append",{processor:"object",default:{}}),o("emoticons_images_url",{processor:"string",default:"https://twemoji.maxcdn.com/v/13.0.1/72x72/"})})(t,e);const o=((t,e,o)=>{const n=p(),s=p(),r=j(t),i=t=>{return o="=4&&e.substr(0,4)===o?t.char.replace(/src="([^"]+)"/,((t,e)=>`src="${r}${e}"`)):t.char;var e,o};t.on("init",(()=>{y.load(o,e).then((e=>{const o=x(t);(t=>{const e={},o=[];g(t,((t,n)=>{const s={title:n,keywords:t.keywords,char:i(t),category:O(A,t.category)},r=void 0!==e[s.category]?e[s.category]:[];e[s.category]=r.concat([s]),o.push(s)})),n.set(e),s.set(o)})(d(e,o))}),(t=>{console.log(`Failed to load emojis: ${t}`),n.set({}),s.set([])}))}));const l=()=>s.get().getOr([]),u=()=>n.isSet()&&s.isSet();return{listCategories:()=>[k].concat(c(n.get().getOr({}))),hasLoaded:u,waitForLoad:()=>u()?Promise.resolve(!0):new Promise(((t,o)=>{let n=15;const s=setInterval((()=>{u()?(clearInterval(s),t(!0)):(n--,n<0&&(console.log("Could not load emojis from url: "+e),clearInterval(s),o(!1)))}),100)})),listAll:l,listCategory:t=>t===k?l():n.get().bind((e=>a.from(e[t]))).getOr([])}})(t,w(t),C(t));((t,e)=>{t.addCommand("mceEmoticons",(()=>N(t,e)))})(t,o),(t=>{const e=()=>t.execCommand("mceEmoticons");t.ui.registry.addButton("emoticons",{tooltip:"Emojis",icon:"emoji",onAction:e,onSetup:T(t)}),t.ui.registry.addMenuItem("emoticons",{text:"Emojis...",icon:"emoji",onAction:e,onSetup:T(t)})})(t),((t,e)=>{t.ui.registry.addAutocompleter("emoticons",{trigger:":",columns:"auto",minChars:2,fetch:(t,o)=>e.waitForLoad().then((()=>{const n=e.listAll();return S(n,t,a.some(o))})),onAction:(e,o,n)=>{t.selection.setRng(o),t.insertContent(n),e.hide()}})})(t,o),(t=>{t.on("PreInit",(()=>{t.parser.addAttributeFilter("data-emoticon",(t=>{((t,e)=>{for(let e=0,n=t.length;e{let t=e;return{get:()=>t,set:e=>{t=e}}};var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const n=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=r=e,(o=String).prototype.isPrototypeOf(n)||(null===(s=r.constructor)||void 0===s?void 0:s.name)===o.name)?"string":t;var n,r,o,s})(t)===e,r=e=>t=>typeof t===e,o=e=>t=>e===t,s=n("string"),i=n("array"),l=o(null),a=r("boolean"),c=o(void 0),u=e=>!(e=>null==e)(e),d=r("function"),m=r("number"),h=()=>{},g=e=>()=>e;function p(e,...t){return(...n)=>{const r=t.concat(n);return e.apply(null,r)}}const f=g(!1),v=g(!0);class w{constructor(e,t){this.tag=e,this.value=t}static some(e){return new w(!0,e)}static none(){return w.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?w.some(e(this.value)):w.none()}bind(e){return this.tag?e(this.value):w.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:w.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return u(e)?w.some(e):w.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}w.singletonNone=new w(!1);const y=t=>{const n=e(w.none()),r=()=>n.get().each(t);return{clear:()=>{r(),n.set(w.none())},isSet:()=>n.get().isSome(),get:()=>n.get(),set:e=>{r(),n.set(w.some(e))}}},b=()=>y((e=>e.unbind())),S=Array.prototype.push,x=(e,t)=>{const n=e.length,r=new Array(n);for(let o=0;o{for(let n=0,r=e.length;n{const n=[];for(let r=0,o=e.length;r((e,t,n)=>{for(let r=0,o=e.length;r{const o=e.indexOf(t,n);return-1!==o&&(!!c(r)||o+t.length<=r)},C=e=>void 0!==e.style&&d(e.style.getPropertyValue),A=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},R=A;"undefined"!=typeof window?window:Function("return this;")();const L=e=>t=>(e=>e.dom.nodeType)(t)===e,M=L(1),N=L(3),P=L(9),D=L(11),W=(e,t)=>{const n=e.dom;if(1!==n.nodeType)return!1;{const e=n;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},q=e=>R(e.dom.ownerDocument),H=e=>x(e.dom.childNodes,R),I=d(Element.prototype.attachShadow)&&d(Node.prototype.getRootNode),B=g(I),V=I?e=>R(e.dom.getRootNode()):e=>P(e)?e:q(e),_=e=>{const t=V(e);return D(n=t)&&u(n.dom.host)?w.some(t):w.none();var n},j=e=>R(e.dom.host),z=e=>{const t=N(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const n=t.ownerDocument;return _(R(t)).fold((()=>n.body.contains(t)),(r=z,o=j,e=>r(o(e))));var r,o},$=(e,t)=>{const n=e.dom.getAttribute(t);return null===n?void 0:n},U=(e,t)=>{e.dom.removeAttribute(t)},K=(e,t)=>{const n=e.dom;((e,t)=>{const n=T(e);for(let r=0,o=n.length;r{((e,t,n)=>{if(!s(n))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",n,":: Element ",e),new Error("CSS value must be a string: "+n);C(e)&&e.style.setProperty(t,n)})(n,t,e)}))},X=e=>{const t=R((e=>{if(B()&&u(e.target)){const t=R(e.target);if(M(t)&&u(t.dom.shadowRoot)&&e.composed&&e.composedPath){const t=e.composedPath();if(t)return((e,t)=>0e.stopPropagation(),r=()=>e.preventDefault(),o=(s=r,i=n,(...e)=>s(i.apply(null,e)));var s,i;return((e,t,n,r,o,s,i)=>({target:e,x:t,y:n,stop:r,prevent:o,kill:s,raw:i}))(t,e.clientX,e.clientY,n,r,o,e)},Y=(e,t,n,r)=>{e.dom.removeEventListener(t,n,r)},G=v,J=(e,t,n)=>((e,t,n,r)=>((e,t,n,r,o)=>{const s=((e,t)=>n=>{e(n)&&t(X(n))})(n,r);return e.dom.addEventListener(t,s,o),{unbind:p(Y,e,t,s,o)}})(e,t,n,r,!1))(e,t,G,n),Q=()=>Z(0,0),Z=(e,t)=>({major:e,minor:t}),ee={nu:Z,detect:(e,t)=>{const n=String(t).toLowerCase();return 0===e.length?Q():((e,t)=>{const n=((e,t)=>{for(let n=0;nNumber(t.replace(n,"$"+e));return Z(r(1),r(2))})(e,n)},unknown:Q},te=(e,t)=>{const n=String(t).toLowerCase();return O(e,(e=>e.search(n)))},ne=/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,re=e=>t=>k(t,e),oe=[{name:"Edge",versionRegexes:[/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],search:e=>k(e,"edge/")&&k(e,"chrome")&&k(e,"safari")&&k(e,"applewebkit")},{name:"Chromium",brand:"Chromium",versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/,ne],search:e=>k(e,"chrome")&&!k(e,"chromeframe")},{name:"IE",versionRegexes:[/.*?msie\ ?([0-9]+)\.([0-9]+).*/,/.*?rv:([0-9]+)\.([0-9]+).*/],search:e=>k(e,"msie")||k(e,"trident")},{name:"Opera",versionRegexes:[ne,/.*?opera\/([0-9]+)\.([0-9]+).*/],search:re("opera")},{name:"Firefox",versionRegexes:[/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],search:re("firefox")},{name:"Safari",versionRegexes:[ne,/.*?cpu os ([0-9]+)_([0-9]+).*/],search:e=>(k(e,"safari")||k(e,"mobile/"))&&k(e,"applewebkit")}],se=[{name:"Windows",search:re("win"),versionRegexes:[/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]},{name:"iOS",search:e=>k(e,"iphone")||k(e,"ipad"),versionRegexes:[/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,/.*cpu os ([0-9]+)_([0-9]+).*/,/.*cpu iphone os ([0-9]+)_([0-9]+).*/]},{name:"Android",search:re("android"),versionRegexes:[/.*?android\ ?([0-9]+)\.([0-9]+).*/]},{name:"macOS",search:re("mac os x"),versionRegexes:[/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]},{name:"Linux",search:re("linux"),versionRegexes:[]},{name:"Solaris",search:re("sunos"),versionRegexes:[]},{name:"FreeBSD",search:re("freebsd"),versionRegexes:[]},{name:"ChromeOS",search:re("cros"),versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/]}],ie={browsers:g(oe),oses:g(se)},le="Edge",ae="Chromium",ce="Opera",ue="Firefox",de="Safari",me=e=>{const t=e.current,n=e.version,r=e=>()=>t===e;return{current:t,version:n,isEdge:r(le),isChromium:r(ae),isIE:r("IE"),isOpera:r(ce),isFirefox:r(ue),isSafari:r(de)}},he=()=>me({current:void 0,version:ee.unknown()}),ge=me,pe=(g(le),g(ae),g("IE"),g(ce),g(ue),g(de),"Windows"),fe="Android",ve="Linux",we="macOS",ye="Solaris",be="FreeBSD",Se="ChromeOS",xe=e=>{const t=e.current,n=e.version,r=e=>()=>t===e;return{current:t,version:n,isWindows:r(pe),isiOS:r("iOS"),isAndroid:r(fe),isMacOS:r(we),isLinux:r(ve),isSolaris:r(ye),isFreeBSD:r(be),isChromeOS:r(Se)}},Ee=()=>xe({current:void 0,version:ee.unknown()}),Fe=xe,Oe=(g(pe),g("iOS"),g(fe),g(ve),g(we),g(ye),g(be),g(Se),(e,t,n)=>{const r=ie.browsers(),o=ie.oses(),s=t.bind((e=>((e,t)=>((e,t)=>{for(let n=0;n{const n=t.brand.toLowerCase();return O(e,(e=>{var t;return n===(null===(t=e.brand)||void 0===t?void 0:t.toLowerCase())})).map((e=>({current:e.name,version:ee.nu(parseInt(t.version,10),0)})))})))(r,e))).orThunk((()=>((e,t)=>te(e,t).map((e=>{const n=ee.detect(e.versionRegexes,t);return{current:e.name,version:n}})))(r,e))).fold(he,ge),i=((e,t)=>te(e,t).map((e=>{const n=ee.detect(e.versionRegexes,t);return{current:e.name,version:n}})))(o,e).fold(Ee,Fe),l=((e,t,n,r)=>{const o=e.isiOS()&&!0===/ipad/i.test(n),s=e.isiOS()&&!o,i=e.isiOS()||e.isAndroid(),l=i||r("(pointer:coarse)"),a=o||!s&&i&&r("(min-device-width:768px)"),c=s||i&&!a,u=t.isSafari()&&e.isiOS()&&!1===/safari/i.test(n),d=!c&&!a&&!u;return{isiPad:g(o),isiPhone:g(s),isTablet:g(a),isPhone:g(c),isTouch:g(l),isAndroid:e.isAndroid,isiOS:e.isiOS,isWebView:g(u),isDesktop:g(d)}})(i,s,e,n);return{browser:s,os:i,deviceType:l}}),Te=e=>window.matchMedia(e).matches;let ke=(e=>{let t,n=!1;return(...r)=>(n||(n=!0,t=e.apply(null,r)),t)})((()=>Oe(navigator.userAgent,w.from(navigator.userAgentData),Te)));const Ce=(e,t)=>({left:e,top:t,translate:(n,r)=>Ce(e+n,t+r)}),Ae=Ce,Re=e=>{const t=void 0===e?window:e;return ke().browser.isFirefox()?w.none():w.from(t.visualViewport)},Le=(e,t,n,r)=>({x:e,y:t,width:n,height:r,right:e+n,bottom:t+r}),Me=e=>{const t=void 0===e?window:e,n=t.document,r=(e=>{const t=void 0!==e?e.dom:document,n=t.body.scrollLeft||t.documentElement.scrollLeft,r=t.body.scrollTop||t.documentElement.scrollTop;return Ae(n,r)})(R(n));return Re(t).fold((()=>{const e=t.document.documentElement,n=e.clientWidth,o=e.clientHeight;return Le(r.left,r.top,n,o)}),(e=>Le(Math.max(e.pageLeft,r.left),Math.max(e.pageTop,r.top),e.width,e.height)))},Ne=(e,t,n)=>Re(n).map((n=>{const r=e=>t(X(e));return n.addEventListener(e,r),{unbind:()=>n.removeEventListener(e,r)}})).getOrThunk((()=>({unbind:h})));var Pe=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),De=tinymce.util.Tools.resolve("tinymce.Env");const We=(e,t)=>{e.dispatch("FullscreenStateChanged",{state:t}),e.dispatch("ResizeEditor")},qe=("fullscreen_native",e=>e.options.get("fullscreen_native"));const He=e=>{return e.dom===(void 0!==(t=q(e).dom).fullscreenElement?t.fullscreenElement:void 0!==t.msFullscreenElement?t.msFullscreenElement:void 0!==t.webkitFullscreenElement?t.webkitFullscreenElement:null);var t},Ie=(e,t,n)=>((e,t,n)=>F(((e,t)=>{const n=d(t)?t:f;let r=e.dom;const o=[];for(;null!==r.parentNode&&void 0!==r.parentNode;){const e=r.parentNode,t=R(e);if(o.push(t),!0===n(t))break;r=e}return o})(e,n),t))(e,(e=>W(e,t)),n),Be=(e,t)=>((e,n)=>{return F((e=>w.from(e.dom.parentNode).map(R))(r=e).map(H).map((e=>F(e,(e=>{return t=e,!(r.dom===t.dom);var t})))).getOr([]),(e=>W(e,t)));var r})(e),Ve="data-ephox-mobile-fullscreen-style",_e="position:absolute!important;",je="top:0!important;left:0!important;margin:0!important;padding:0!important;width:100%!important;height:100%!important;overflow:visible!important;",ze=De.os.isAndroid(),$e=e=>{const t=((e,t)=>{const n=e.dom,r=window.getComputedStyle(n).getPropertyValue(t);return""!==r||z(e)?r:((e,t)=>C(e)?e.style.getPropertyValue(t):"")(n,t)})(e,"background-color");return void 0!==t&&""!==t?"background-color:"+t+"!important":"background-color:rgb(255,255,255)!important;"},Ue=Pe.DOM,Ke=Re().fold((()=>({bind:h,unbind:h})),(e=>{const t=(()=>{const e=y(h);return{...e,on:t=>e.get().each(t)}})(),n=b(),r=b(),o=((e,t)=>{let n=null;return{cancel:()=>{l(n)||(clearTimeout(n),n=null)},throttle:(...t)=>{l(n)&&(n=setTimeout((()=>{n=null,e.apply(null,t)}),50))}}})((()=>{document.body.scrollTop=0,document.documentElement.scrollTop=0,window.requestAnimationFrame((()=>{t.on((t=>K(t,{top:e.offsetTop+"px",left:e.offsetLeft+"px",height:e.height+"px",width:e.width+"px"})))}))}));return{bind:e=>{t.set(e),o.throttle(),n.set(Ne("resize",o.throttle)),r.set(Ne("scroll",o.throttle))},unbind:()=>{t.on((()=>{n.clear(),r.clear()})),t.clear()}}})),Xe=(e,t)=>{const n=document.body,r=document.documentElement,o=e.getContainer(),l=R(o),c=(e=>{const t=R(e.getElement());return _(t).map(j).getOrThunk((()=>(e=>{const t=e.dom.body;if(null==t)throw new Error("Body is not available yet");return R(t)})(q(t))))})(e),u=t.get(),d=R(e.getBody()),h=De.deviceType.isTouch(),g=o.style,p=e.iframeElement,f=null==p?void 0:p.style,v=e=>{e(n,"tox-fullscreen"),e(r,"tox-fullscreen"),e(o,"tox-fullscreen"),_(l).map((e=>j(e).dom)).each((t=>{e(t,"tox-fullscreen"),e(t,"tox-shadowhost")}))},y=()=>{h&&(e=>{const t=((e,t)=>{const n=document;return 1!==(r=n).nodeType&&9!==r.nodeType&&11!==r.nodeType||0===r.childElementCount?[]:x(n.querySelectorAll(e),R);var r})("["+Ve+"]");E(t,(t=>{const n=$(t,Ve);n&&"no-styles"!==n?K(t,e.parseStyle(n)):U(t,"style"),U(t,Ve)}))})(e.dom),v(Ue.removeClass),Ke.unbind(),w.from(t.get()).each((e=>e.fullscreenChangeHandler.unbind()))};if(u)u.fullscreenChangeHandler.unbind(),qe(e)&&He(c)&&(e=>{const t=e.dom;t.exitFullscreen?t.exitFullscreen():t.msExitFullscreen?t.msExitFullscreen():t.webkitCancelFullScreen&&t.webkitCancelFullScreen()})(q(c)),f.width=u.iframeWidth,f.height=u.iframeHeight,g.width=u.containerWidth,g.height=u.containerHeight,g.top=u.containerTop,g.left=u.containerLeft,y(),b=u.scrollPos,window.scrollTo(b.x,b.y),t.set(null),We(e,!1),e.off("remove",y);else{const n=J(q(c),void 0!==document.fullscreenElement?"fullscreenchange":void 0!==document.msFullscreenElement?"MSFullscreenChange":void 0!==document.webkitFullscreenElement?"webkitfullscreenchange":"fullscreenchange",(n=>{qe(e)&&(He(c)||null===t.get()||Xe(e,t))})),r={scrollPos:Me(window),containerWidth:g.width,containerHeight:g.height,containerTop:g.top,containerLeft:g.left,iframeWidth:f.width,iframeHeight:f.height,fullscreenChangeHandler:n};h&&((e,t,n)=>{const r=t=>n=>{const r=$(n,"style"),o=void 0===r?"no-styles":r.trim();o!==t&&(((e,t,n)=>{((e,t,n)=>{if(!(s(n)||a(n)||m(n)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",n,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,n+"")})(e.dom,t,n)})(n,Ve,o),K(n,e.parseStyle(t)))},o=Ie(t,"*"),l=(e=>{const t=[];for(let n=0,r=e.length;nBe(e,"*:not(.tox-silver-sink)")))),c=$e(n);E(l,r("display:none!important;")),E(o,r(_e+je+c)),r((!0===ze?"":_e)+je+c)(t)})(e.dom,l,d),f.width=f.height="100%",g.width=g.height="",v(Ue.addClass),Ke.bind(l),e.on("remove",y),t.set(r),qe(e)&&(e=>{const t=e.dom;t.requestFullscreen?t.requestFullscreen():t.msRequestFullscreen?t.msRequestFullscreen():t.webkitRequestFullScreen&&t.webkitRequestFullScreen()})(c),We(e,!0)}var b},Ye=(e,t)=>n=>{n.setActive(null!==t.get());const r=e=>n.setActive(e.state);return e.on("FullscreenStateChanged",r),()=>e.off("FullscreenStateChanged",r)};t.add("fullscreen",(t=>{const n=e(null);return t.inline||((e=>{(0,e.options.register)("fullscreen_native",{processor:"boolean",default:!1})})(t),((e,t)=>{e.addCommand("mceFullScreen",(()=>{Xe(e,t)}))})(t,n),((e,t)=>{const n=()=>e.execCommand("mceFullScreen");e.ui.registry.addToggleMenuItem("fullscreen",{text:"Fullscreen",icon:"fullscreen",shortcut:"Meta+Shift+F",onAction:n,onSetup:Ye(e,t)}),e.ui.registry.addToggleButton("fullscreen",{tooltip:"Fullscreen",icon:"fullscreen",onAction:n,onSetup:Ye(e,t)})})(t,n),t.addShortcut("Meta+Shift+F","","mceFullScreen")),(e=>({isFullscreen:()=>null!==e.get()}))(n)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ar.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ar.js new file mode 100644 index 0000000..e6ae0e8 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ar.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ar', +'

      بدء التنقل بواسطة لوحة المÙاتيح

      \n' + + '\n' + + '
      \n' + + '
      التركيز على شريط القوائم
      \n' + + '
      نظاما التشغيل Windows أو Linux: Alt + F9
      \n' + + '
      نظام التشغيل macOS: ⌥F9
      \n' + + '
      التركيز على شريط الأدوات
      \n' + + '
      نظاما التشغيل Windows أو Linux: Alt + F10
      \n' + + '
      نظام التشغيل macOS: ⌥F10
      \n' + + '
      التركيز على التذييل
      \n' + + '
      نظاما التشغيل Windows أو Linux: Alt + F11
      \n' + + '
      نظام التشغيل macOS: ⌥F11
      \n' + + '
      التركيز على شريط أدوات السياق
      \n' + + '
      أنظمة التشغيل Windows أو Linux أو macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      سيبدأ التنقل عند عنصر واجهة المستخدم الأول، والذي سيتم تمييزه أو تسطيره ÙÙŠ حالة العنصر الأول ÙÙŠ\n' + + ' مسار عنصر التذييل.

      \n' + + '\n' + + '

      التنقل بين أقسام واجهة المستخدم

      \n' + + '\n' + + '

      للانتقال من أحد أقسام واجهة المستخدم إلى القسم التالي، اضغط على Tab.

      \n' + + '\n' + + '

      للانتقال من أحد أقسام واجهة المستخدم إلى القسم السابق، اضغط على Shift+Tab.

      \n' + + '\n' + + '

      ترتيب علامات Tab لأقسام واجهة المستخدم هذه هو:\n' + + '\n' + + '

        \n' + + '
      1. شريط القوائم
      2. \n' + + '
      3. كل مجموعة شريط الأدوات
      4. \n' + + '
      5. الشريط الجانبي
      6. \n' + + '
      7. مسار العنصر ÙÙŠ التذييل
      8. \n' + + '
      9. زر تبديل عدد الكلمات ÙÙŠ التذييل
      10. \n' + + '
      11. رابط إدراج العلامة التجارية ÙÙŠ التذييل
      12. \n' + + '
      13. مؤشر تغيير حجم المحرر ÙÙŠ التذييل
      14. \n' + + '
      \n' + + '\n' + + '

      إذا لم يكن قسم واجهة المستخدم موجودًا، Ùسيتم تخطيه.

      \n' + + '\n' + + '

      إذا كان التذييل يحتوي على التركيز على â€â€«Ø§Ù„تنقل بواسطة لوحة المÙاتيح، ولا يوجد شريط جانبي مرئي، Ùإن الضغط على Shift+Tab\n' + + ' ينقل التركيز إلى مجموعة شريط الأدوات الأولى، وليس الأخيرة.\n' + + '\n' + + '

      التنقل بين أقسام واجهة المستخدم

      \n' + + '\n' + + '

      للانتقال من أحد عناصر واجهة المستخدم إلى العنصر التالي، اضغط على Ù…Ùتاح السهم المناسب.

      \n' + + '\n' + + '

      Ù…Ùتاحا السهمين اليسار‎ واليمين‎

      \n' + + '\n' + + '
        \n' + + '
      • التنقل بين القوائم ÙÙŠ شريط القوائم.
      • \n' + + '
      • Ùتح قائمة Ùرعية ÙÙŠ القائمة.
      • \n' + + '
      • التنقل بين الأزرار ÙÙŠ مجموعة شريط الأدوات.
      • \n' + + '
      • التنقل بين العناصر ÙÙŠ مسار عنصر التذييل.
      • \n' + + '
      \n' + + '\n' + + '

      Ù…Ùتاحا السهمين لأسÙل‎ ولأعلى‎\n' + + '\n' + + '

        \n' + + '
      • التنقل بين عناصر القائمة ÙÙŠ القائمة.
      • \n' + + '
      • التنقل بين العناصر ÙÙŠ قائمة شريط الأدوات المنبثقة.
      • \n' + + '
      \n' + + '\n' + + '

      دورة Ù…Ùاتيح الأسهم‎ داخل قسم واجهة المستخدم التي تم التركيز عليها.

      \n' + + '\n' + + '

      لإغلاق قائمة Ù…Ùتوحة أو قائمة Ùرعية Ù…Ùتوحة أو قائمة منبثقة Ù…Ùتوحة، اضغط على Ù…Ùتاح Esc.\n' + + '\n' + + '

      إذا كان التركيز الحالي على "الجزء العلوي" من قسم معين لواجهة المستخدم، Ùإن الضغط على Ù…Ùتاح Esc يؤدي أيضًا إلى الخروج\n' + + ' من التنقل بواسطة لوحة المÙاتيح بالكامل.

      \n' + + '\n' + + '

      تنÙيذ عنصر قائمة أو زر شريط أدوات

      \n' + + '\n' + + '

      عندما يتم تمييز عنصر القائمة المطلوب أو زر شريط الأدوات، اضغط على زر ReturnØŒ أو EnterØŒ\n' + + ' أو Ù…Ùتاح المساÙØ© لتنÙيذ العنصر.\n' + + '\n' + + '

      التنقل ÙÙŠ مربعات الحوار غير المبوبة

      \n' + + '\n' + + '

      ÙÙŠ مربعات الحوار غير المبوبة، يتم التركيز على المكون التÙاعلي الأول عند Ùتح مربع الحوار.

      \n' + + '\n' + + '

      التنقل بين مكونات الحوار التÙاعلي بالضغط على زر Tab أو Shift+Tab.

      \n' + + '\n' + + '

      التنقل ÙÙŠ مربعات الحوار المبوبة

      \n' + + '\n' + + '

      ÙÙŠ مربعات الحوار المبوبة، يتم التركيز على الزر الأول ÙÙŠ قائمة علامات التبويب عند Ùتح مربع الحوار.

      \n' + + '\n' + + '

      التنقل بين المكونات التÙاعلية لعلامة التبويب لمربع الحوار هذه بالضغط على زر Tab أو\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      التبديل إلى علامة تبويب أخرى لمربع الحوار من خلال التركيز على قائمة علامة التبويب ثم الضغط على زر السهم المناسب\n' + + ' Ù…Ùتاح للتنقل بين علامات التبويب المتاحة.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/bg_BG.js b/static/js/tinymce/plugins/help/js/i18n/keynav/bg_BG.js new file mode 100644 index 0000000..ad904db --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/bg_BG.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.bg_BG', +'

      Ðачало на навигациÑта Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ð°Ñ‚Ð°

      \n' + + '\n' + + '
      \n' + + '
      ФокуÑиране върху лентата Ñ Ð¼ÐµÐ½ÑŽÑ‚Ð°
      \n' + + '
      Windows или Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      ФокуÑиране върху лентата Ñ Ð¸Ð½Ñтрументи
      \n' + + '
      Windows или Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      ФокуÑиране върху Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ»
      \n' + + '
      Windows или Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      ФокуÑиране върху контекÑтуалната лента Ñ Ð¸Ð½Ñтрументи
      \n' + + '
      Windows, Linux или macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      ÐавигациÑта ще започне Ñ Ð¿ÑŠÑ€Ð²Ð¸Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚ на ПИ, който ще бъде маркиран или подчертан в ÑÐ»ÑƒÑ‡Ð°Ñ Ð½Ð° Ð¿ÑŠÑ€Ð²Ð¸Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚ в\n' + + ' Ð¿ÑŠÑ‚Ñ Ð´Ð¾ елемента в Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ».

      \n' + + '\n' + + '

      Ðавигиране между раздели на ПИ

      \n' + + '\n' + + '

      За да преминете от един раздел на ПИ към ÑледващиÑ, натиÑнете Tab.

      \n' + + '\n' + + '

      За да преминете от един раздел на ПИ към предишниÑ, натиÑнете Shift+Tab.

      \n' + + '\n' + + '

      Редът за обхождане Ñ Ñ‚Ð°Ð±ÑƒÐ»Ð°Ñ†Ð¸Ñ Ð½Ð° тези раздели на ПИ е:\n' + + '\n' + + '

        \n' + + '
      1. Лентата Ñ Ð¼ÐµÐ½ÑŽÑ‚Ð°
      2. \n' + + '
      3. Ð’ÑÑка група на лентата Ñ Ð¸Ð½Ñтрументи
      4. \n' + + '
      5. Страничната лента
      6. \n' + + '
      7. ПътÑÑ‚ до елемента в Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ»
      8. \n' + + '
      9. Бутонът за превключване на Ð±Ñ€Ð¾Ñ Ð½Ð° думите в Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ»
      10. \n' + + '
      11. Връзката за търговÑка марка в Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ»
      12. \n' + + '
      13. Манипулаторът за преоразмерÑване на редактора в Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ»
      14. \n' + + '
      \n' + + '\n' + + '

      Ðко нÑкой раздел на ПИ липÑва, той Ñе пропуÑка.

      \n' + + '\n' + + '

      Ðко долниÑÑ‚ колонтитул има Ñ„Ð¾ÐºÑƒÑ Ð·Ð° Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ð°Ñ‚Ð° и нÑма Ñтранична лента, натиÑкането на Shift+Tab\n' + + ' премеÑтва фокуÑа към първата група на лентата Ñ Ð¸Ð½Ñтрументи, а не към поÑледната.\n' + + '\n' + + '

      Ðавигиране в разделите на ПИ

      \n' + + '\n' + + '

      За да преминете от един елемент на ПИ към ÑледващиÑ, натиÑнете ÑÑŠÐ¾Ñ‚Ð²ÐµÑ‚Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ ÑÑŠÑ Ñтрелка.

      \n' + + '\n' + + '

      С клавишите ÑÑŠÑ Ñтрелка налÑво и надÑÑно

      \n' + + '\n' + + '
        \n' + + '
      • Ñе придвижвате между менютата в лентата Ñ Ð¼ÐµÐ½ÑŽÑ‚Ð¾;
      • \n' + + '
      • отварÑте подменю в меню;
      • \n' + + '
      • Ñе придвижвате между бутоните в група на лентата Ñ Ð¸Ð½Ñтрументи;
      • \n' + + '
      • Ñе придвижвате между елементи в Ð¿ÑŠÑ‚Ñ Ð´Ð¾ елемент в Ð´Ð¾Ð»Ð½Ð¸Ñ ÐºÐ¾Ð»Ð¾Ð½Ñ‚Ð¸Ñ‚ÑƒÐ».
      • \n' + + '
      \n' + + '\n' + + '

      С клавишите ÑÑŠÑ Ñтрелка надолу и нагоре\n' + + '\n' + + '

        \n' + + '
      • Ñе придвижвате между елементите от менюто в дадено меню;
      • \n' + + '
      • Ñе придвижвате между елементите в изÑкачащо меню на лентата Ñ Ð¸Ð½Ñтрументи.
      • \n' + + '
      \n' + + '\n' + + '

      Клавишите ÑÑŠÑ Ñтрелки Ñе придвижват в рамките на фокуÑÐ¸Ñ€Ð°Ð½Ð¸Ñ Ñ€Ð°Ð·Ð´ÐµÐ» на ПИ.

      \n' + + '\n' + + '

      За да затворите отворено меню, подменю или изÑкачащо меню, натиÑнете клавиша Esc.\n' + + '\n' + + '

      Ðко текущиÑÑ‚ Ñ„Ð¾ÐºÑƒÑ Ðµ върху „горната чаÑт“ на конкретен раздел на ПИ, натиÑкането на клавиша Esc Ñъщо излиза\n' + + ' напълно от навигациÑта Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ð°Ñ‚Ð°.

      \n' + + '\n' + + '

      Изпълнение на елемент от менюто или бутон от лентата Ñ Ð¸Ð½Ñтрументи

      \n' + + '\n' + + '

      Когато желаниÑÑ‚ елемент от менюто или бутон от лентата Ñ Ð¸Ð½Ñтрументи е маркиран, натиÑнете Return, Enter\n' + + ' или клавиша за интервал, за да изпълните елемента.\n' + + '\n' + + '

      Ðавигиране в диалогови прозорци без раздели

      \n' + + '\n' + + '

      Ð’ диалоговите прозорци без раздели първиÑÑ‚ интерактивен компонент Ñе фокуÑира, когато Ñе отвори диалоговиÑÑ‚ прозорец.

      \n' + + '\n' + + '

      Ðавигирайте между интерактивните компоненти на Ð´Ð¸Ð°Ð»Ð¾Ð³Ð¾Ð²Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ†, като натиÑнете Tab или Shift+Tab.

      \n' + + '\n' + + '

      Ðавигиране в диалогови прозорци Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸

      \n' + + '\n' + + '

      Ð’ диалоговите прозорци Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸ първиÑÑ‚ бутон в менюто Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸ Ñе фокуÑира, когато Ñе отвори диалоговиÑÑ‚ прозорец.

      \n' + + '\n' + + '

      Ðавигирайте между интерактивните компоненти на този диалогов раздел, като натиÑнете Tab или\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Превключете към друг диалогов раздел, като фокуÑирате върху менюто Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸ и Ñлед това натиÑнете ÑÑŠÐ¾Ñ‚Ð²ÐµÑ‚Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ ÑÑŠÑ Ñтрелка,\n' + + ' за да преминете през наличните раздели.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ca.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ca.js new file mode 100644 index 0000000..6d9ad54 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ca.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ca', +'

      Inici de la navegació amb el teclat

      \n' + + '\n' + + '
      \n' + + '
      Enfocar la barra de menús
      \n' + + '
      Windows o Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + "
      Enfocar la barra d'eines
      \n" + + '
      Windows o Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Enfocar el peu de pàgina
      \n' + + '
      Windows o Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + "
      Enfocar una barra d'eines contextual
      \n" + + '
      Windows, Linux o macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + "

      La navegació començarà en el primer element de la interfície d'usuari, que es ressaltarà o subratllarà per al primer element a\n" + + " la ruta de l'element de peu de pàgina.

      \n" + + '\n' + + "

      Navegació entre seccions de la interfície d'usuari

      \n" + + '\n' + + "

      Per desplaçar-vos des d'una secció de la interfície d'usuari a la següent, premeu la tecla Tab.

      \n" + + '\n' + + "

      Per desplaçar-vos des d'una secció de la interfície d'usuari a l'anterior, premeu les tecles Maj+Tab.

      \n" + + '\n' + + "

      L'ordre en prémer la tecla Tab d'aquestes secciones de la interfície d'usuari és:\n" + + '\n' + + '

        \n' + + '
      1. Barra de menús
      2. \n' + + "
      3. Cada grup de la barra d'eines
      4. \n" + + '
      5. Barra lateral
      6. \n' + + "
      7. Ruta de l'element del peu de pàgina
      8. \n" + + '
      9. Botó de commutació de recompte de paraules al peu de pàgina
      10. \n' + + '
      11. Enllaç de marca del peu de pàgina
      12. \n' + + "
      13. Control de canvi de mida de l'editor al peu de pàgina
      14. \n" + + '
      \n' + + '\n' + + "

      Si no hi ha una secció de la interfície d'usuari, s'ometrà.

      \n" + + '\n' + + '

      Si el peu de pàgina té el focus de navegació del teclat i no hi ha cap barra lateral visible, en prémer Maj+Tab\n' + + " el focus es mou al primer grup de la barra d'eines, no l'últim.\n" + + '\n' + + "

      Navegació dins de les seccions de la interfície d'usuari

      \n" + + '\n' + + "

      Per desplaçar-vos des d'un element de la interfície d'usuari al següent, premeu la tecla de Fletxa adequada.

      \n" + + '\n' + + '

      Les tecles de fletxa Esquerra i Dreta

      \n' + + '\n' + + '
        \n' + + '
      • us permeten desplaçar-vos entre menús de la barra de menús.
      • \n' + + '
      • obren un submenú en un menú.
      • \n' + + "
      • us permeten desplaçar-vos entre botons d'un grup de la barra d'eines.
      • \n" + + "
      • us permeten desplaçar-vos entre elements de la ruta d'elements del peu de pàgina.
      • \n" + + '
      \n' + + '\n' + + '

      Les tecles de fletxa Avall i Amunt\n' + + '\n' + + '

        \n' + + "
      • us permeten desplaçar-vos entre elements de menú d'un menú.
      • \n" + + "
      • us permeten desplaçar-vos entre elements d'un menú emergent de la barra d'eines.
      • \n" + + '
      \n' + + '\n' + + "

      Les tecles de Fletxa us permeten desplaçar-vos dins de la secció de la interfície d'usuari que té el focus.

      \n" + + '\n' + + '

      Per tancar un menú, un submenú o un menú emergent oberts, premeu la tecla Esc.\n' + + '\n' + + "

      Si el focus actual es troba a la ‘part superior’ d'una secció específica de la interfície d'usuari, en prémer la tecla Esc també es tanca\n" + + ' completament la navegació amb el teclat.

      \n' + + '\n' + + "

      Execució d'un element de menú o d'un botó de la barra d'eines

      \n" + + '\n' + + "

      Quan l'element del menú o el botó de la barra d'eines que desitgeu estigui ressaltat, premeu Retorn, Intro\n" + + " o la barra d'espai per executar l'element.\n" + + '\n' + + '

      Navegació per quadres de diàleg sense pestanyes

      \n' + + '\n' + + "

      En els quadres de diàleg sense pestanyes, el primer component interactiu pren el focus quan s'obre el quadre diàleg.

      \n" + + '\n' + + '

      Premeu la tecla Tab o les tecles Maj+Tab per desplaçar-vos entre components interactius del quadre de diàleg.

      \n' + + '\n' + + '

      Navegació per quadres de diàleg amb pestanyes

      \n' + + '\n' + + "

      En els quadres de diàleg amb pestanyes, el primer botó del menú de la pestanya pren el focus quan s'obre el quadre diàleg.

      \n" + + '\n' + + "

      Per desplaçar-vos entre components interactius d'aquest quadre de diàleg, premeu la tecla Tab o\n" + + ' les tecles Maj+Tab.

      \n' + + '\n' + + "

      Canvieu a la pestanya d'un altre quadre de diàleg, tot enfocant el menú de la pestanya, i després premeu la tecla Fletxa adequada\n" + + ' per canviar entre les pestanyes disponibles.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/cs.js b/static/js/tinymce/plugins/help/js/i18n/keynav/cs.js new file mode 100644 index 0000000..e9103f7 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/cs.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.cs', +'

      ZaÄínáme navigovat pomocí klávesnice

      \n' + + '\n' + + '
      \n' + + '
      Přejít na řádek nabídek
      \n' + + '
      Windows nebo Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Přejít na panel nástrojů
      \n' + + '
      Windows nebo Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Přejít na zápatí
      \n' + + '
      Windows nebo Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Přejít na kontextový panel nástrojů
      \n' + + '
      Windows, Linux nebo macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigace zaÄne u první položky uživatelského rozhraní, která bude zvýraznÄ›na nebo v případÄ› první položky\n' + + ' cesty k prvku zápatí podtržena.

      \n' + + '\n' + + '

      Navigace mezi oddíly uživatelského rozhraní

      \n' + + '\n' + + '

      Stisknutím klávesy Tab se posunete z jednoho oddílu uživatelského rozhraní na další.

      \n' + + '\n' + + '

      Stisknutím kláves Shift+Tab se posunete z jednoho oddílu uživatelského rozhraní na předchozí.

      \n' + + '\n' + + '

      Pořadí přepínání mezi oddíly uživatelského rozhraní pomocí klávesy Tab:\n' + + '\n' + + '

        \n' + + '
      1. Řádek nabídek
      2. \n' + + '
      3. Každá skupina panelu nástrojů
      4. \n' + + '
      5. BoÄní panel
      6. \n' + + '
      7. Cesta k prvku v zápatí.
      8. \n' + + '
      9. TlaÄítko pÅ™epínaÄe poÄtu slov v zápatí
      10. \n' + + '
      11. Odkaz na informace o znaÄce v zápatí
      12. \n' + + '
      13. Úchyt pro změnu velikosti editoru v zápatí
      14. \n' + + '
      \n' + + '\n' + + '

      Pokud nÄ›jaký oddíl uživatelského rozhraní není přítomen, je pÅ™eskoÄen.

      \n' + + '\n' + + '

      Pokud je zápatí vybrané pro navigaci pomocí klávesnice a není zobrazen žádný boÄní panel, stisknutím kláves Shift+Tab\n' + + ' pÅ™ejdete na první skupinu panelu nástrojů, nikoli na poslední.\n' + + '\n' + + '

      Navigace v rámci oddílů uživatelského rozhraní

      \n' + + '\n' + + '

      Chcete-li se přesunout z jednoho prvku uživatelského rozhraní na další, stiskněte příslušnou klávesu s šipkou.

      \n' + + '\n' + + '

      Klávesy s šipkou vlevo a vpravo

      \n' + + '\n' + + '
        \n' + + '
      • umožňují pÅ™esun mezi nabídkami na řádku nabídek;
      • \n' + + '
      • otevírají podnabídku nabídky;
      • \n' + + '
      • umožňují pÅ™esun mezi tlaÄítky ve skupinÄ› panelu nástrojů;
      • \n' + + '
      • umožňují pÅ™esun mezi položkami cesty prvku v zápatí.
      • \n' + + '
      \n' + + '\n' + + '

      Klávesy se šipkou dolů a nahoru\n' + + '\n' + + '

        \n' + + '
      • umožňují pÅ™esun mezi položkami nabídky;
      • \n' + + '
      • umožňují pÅ™esun mezi položkami místní nabídky panelu nástrojů.
      • \n' + + '
      \n' + + '\n' + + '

      Šipky provádí přepínání v rámci vybraného oddílu uživatelského rozhraní.

      \n' + + '\n' + + '

      Chcete-li zavřít otevřenou nabídku, podnabídku nebo místní nabídku, stiskněte klávesu Esc.\n' + + '\n' + + '

      Pokud je aktuálnÄ› vybrána horní Äást oddílu uživatelského rozhraní, stisknutím klávesy Esc zcela ukonÄíte také\n' + + ' navigaci pomocí klávesnice.

      \n' + + '\n' + + '

      Provedení příkazu položky nabídky nebo tlaÄítka panelu nástrojů

      \n' + + '\n' + + '

      Pokud je zvýraznÄ›na požadovaná položka nabídky nebo tlaÄítko panelu nástrojů, stisknutím klávesy Return, Enter\n' + + ' nebo mezerníku provedete přísluÅ¡ný příkaz.\n' + + '\n' + + '

      Navigace v dialogových oknech bez záložek

      \n' + + '\n' + + '

      Při otevření dialogových oken bez záložek přejdete na první interaktivní komponentu.

      \n' + + '\n' + + '

      Přecházet mezi interaktivními komponentami dialogového okna můžete stisknutím klávesy Tab nebo kombinace Shift+Tab.

      \n' + + '\n' + + '

      Navigace v dialogových oknech se záložkami

      \n' + + '\n' + + '

      PÅ™i otevÅ™ení dialogových oken se záložkami pÅ™ejdete na první tlaÄítko v nabídce záložek.

      \n' + + '\n' + + '

      Přecházet mezi interaktivními komponentami této záložky dialogového okna můžete stisknutím klávesy Tab nebo\n' + + ' kombinace Shift+Tab.

      \n' + + '\n' + + '

      Chcete-li přepnout na další záložku dialogového okna, přejděte na nabídku záložek a poté můžete stisknutím požadované šipky\n' + + ' přepínat mezi dostupnými záložkami.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/da.js b/static/js/tinymce/plugins/help/js/i18n/keynav/da.js new file mode 100644 index 0000000..f3989a0 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/da.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.da', +'

      Start tastaturnavigation

      \n' + + '\n' + + '
      \n' + + '
      Fokuser på menulinjen
      \n' + + '
      Windows eller Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokuser på værktøjslinjen
      \n' + + '
      Windows eller Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokuser på sidefoden
      \n' + + '
      Windows eller Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokuser på kontekstuel værktøjslinje
      \n' + + '
      Windows, Linux eller macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigationen starter ved det første UI-element, som fremhæves eller understreges hvad angår det første element i\n' + + ' sidefodens sti til elementet.

      \n' + + '\n' + + '

      Naviger mellem UI-sektioner

      \n' + + '\n' + + '

      Gå fra én UI-sektion til den næste ved at trykke på Tab.

      \n' + + '\n' + + '

      Gå fra én UI-sektion til den forrige ved at trykke på Shift+Tab.

      \n' + + '\n' + + '

      Tab-rækkefølgen af disse UI-sektioner er:\n' + + '\n' + + '

        \n' + + '
      1. Menulinje
      2. \n' + + '
      3. Hver værktøjsgruppe
      4. \n' + + '
      5. Sidepanel
      6. \n' + + '
      7. Sti til elementet i sidefoden
      8. \n' + + '
      9. Til/fra-knap for ordoptælling i sidefoden
      10. \n' + + '
      11. Brandinglink i sidefoden
      12. \n' + + '
      13. Tilpasningshåndtag for editor i sidefoden
      14. \n' + + '
      \n' + + '\n' + + '

      Hvis en UI-sektion ikke er til stede, springes den over.

      \n' + + '\n' + + '

      Hvis sidefoden har fokus til tastaturnavigation, og der ikke er noget synligt sidepanel, kan der trykkes på Shift+Tab\n' + + ' for at flytte fokus til den første værktøjsgruppe, ikke den sidste.\n' + + '\n' + + '

      Naviger inden for UI-sektioner

      \n' + + '\n' + + '

      Gå fra ét UI-element til det næste ved at trykke på den relevante piletast.

      \n' + + '\n' + + '

      Venstre og højre piletast

      \n' + + '\n' + + '
        \n' + + '
      • flytter mellem menuerne i menulinjen.
      • \n' + + '
      • Ã¥bner en undermenu i en menu.
      • \n' + + '
      • flytter mellem knapperne i en værktøjsgruppe.
      • \n' + + '
      • flytter mellem elementer i sidefodens sti til elementet.
      • \n' + + '
      \n' + + '\n' + + '

      Pil ned og op\n' + + '\n' + + '

        \n' + + '
      • flytter mellem menupunkterne i en menu.
      • \n' + + '
      • flytter mellem punkterne i en genvejsmenu i værktøjslinjen.
      • \n' + + '
      \n' + + '\n' + + '

      Piletasterne kører rundt inden for UI-sektionen, der fokuseres på.

      \n' + + '\n' + + '

      For at lukke en åben menu, en åben undermenu eller en åben genvejsmenu trykkes der på Esc-tasten.\n' + + '\n' + + "

      Hvis det aktuelle fokus er i 'toppen' af en bestemt UI-sektion, vil tryk på Esc-tasten også afslutte\n" + + ' tastaturnavigationen helt.

      \n' + + '\n' + + '

      Udfør et menupunkt eller en værktøjslinjeknap

      \n' + + '\n' + + '

      Når det ønskede menupunkt eller den ønskede værktøjslinjeknap er fremhævet, trykkes der på Retur, Enter\n' + + ' eller mellemrumstasten for at udføre elementet.\n' + + '\n' + + '

      Naviger i ikke-faneopdelte dialogbokse

      \n' + + '\n' + + '

      I ikke-faneopdelte dialogbokse får den første interaktive komponent fokus, når dialogboksen åbnes.

      \n' + + '\n' + + '

      Naviger mellem interaktive dialogbokskomponenter ved at trykke på Tab eller Shift+Tab.

      \n' + + '\n' + + '

      Naviger i faneopdelte dialogbokse

      \n' + + '\n' + + '

      I faneopdelte dialogbokse får den første knap i fanemenuen fokus, når dialogboksen åbnes.

      \n' + + '\n' + + '

      Naviger mellem interaktive komponenter i denne dialogboksfane ved at trykke på Tab eller\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Skift til en anden dialogboksfane ved at fokusere på fanemenuen og derefter trykke på den relevante piletast\n' + + ' for at køre igennem de tilgængelige faner.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/de.js b/static/js/tinymce/plugins/help/js/i18n/keynav/de.js new file mode 100644 index 0000000..9f0c5db --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/de.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.de', +'

      Grundlagen der Tastaturnavigation

      \n' + + '\n' + + '
      \n' + + '
      Fokus auf Menüleiste
      \n' + + '
      Windows oder Linux: ALT+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokus auf Symbolleiste
      \n' + + '
      Windows oder Linux: ALT+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokus auf Fußzeile
      \n' + + '
      Windows oder Linux: ALT+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokus auf kontextbezogene Symbolleiste
      \n' + + '
      Windows, Linux oder macOS: STRG+F9\n' + + '
      \n' + + '\n' + + '

      Die Navigation beginnt beim ersten Benutzeroberflächenelement, welches hervorgehoben ist. Falls sich das erste Element im Pfad der Fußzeile befindet,\n' + + ' ist es unterstrichen.

      \n' + + '\n' + + '

      Zwischen Abschnitten der Benutzeroberfläche navigieren

      \n' + + '\n' + + '

      Um von einem Abschnitt der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie TAB.

      \n' + + '\n' + + '

      Um von einem Abschnitt der Benutzeroberfläche zum vorherigen zu wechseln, drücken Sie UMSCHALT+TAB.

      \n' + + '\n' + + '

      Die Abschnitte der Benutzeroberfläche haben folgende TAB-Reihenfolge:\n' + + '\n' + + '

        \n' + + '
      1. Menüleiste
      2. \n' + + '
      3. Einzelne Gruppen der Symbolleiste
      4. \n' + + '
      5. Randleiste
      6. \n' + + '
      7. Elementpfad in der Fußzeile
      8. \n' + + '
      9. Umschaltfläche „Wörter zählen“ in der Fußzeile
      10. \n' + + '
      11. Branding-Link in der Fußzeile
      12. \n' + + '
      13. Editor-Ziehpunkt zur Größenänderung in der Fußzeile
      14. \n' + + '
      \n' + + '\n' + + '

      Falls ein Abschnitt der Benutzeroberflächen nicht vorhanden ist, wird er übersprungen.

      \n' + + '\n' + + '

      Wenn in der Fußzeile die Tastaturnavigation fokussiert ist und keine Randleiste angezeigt wird, wechselt der Fokus durch Drücken von UMSCHALT+TAB\n' + + ' zur ersten Gruppe der Symbolleiste, nicht zur letzten.\n' + + '\n' + + '

      Innerhalb von Abschnitten der Benutzeroberfläche navigieren

      \n' + + '\n' + + '

      Um von einem Element der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie die entsprechende Pfeiltaste.

      \n' + + '\n' + + '

      Die Pfeiltasten Links und Rechts

      \n' + + '\n' + + '
        \n' + + '
      • wechseln zwischen Menüs in der Menüleiste.
      • \n' + + '
      • öffnen das Untermenü eines Menüs.
      • \n' + + '
      • wechseln zwischen Schaltflächen in einer Gruppe der Symbolleiste.
      • \n' + + '
      • wechseln zwischen Elementen im Elementpfad der Fußzeile.
      • \n' + + '
      \n' + + '\n' + + '

      Die Pfeiltasten Abwärts und Aufwärts\n' + + '\n' + + '

        \n' + + '
      • wechseln zwischen Menüelementen in einem Menü.
      • \n' + + '
      • wechseln zwischen Elementen in einem Popupmenü der Symbolleiste.
      • \n' + + '
      \n' + + '\n' + + '

      Die Pfeiltasten rotieren innerhalb des fokussierten Abschnitts der Benutzeroberfläche.

      \n' + + '\n' + + '

      Um ein geöffnetes Menü, ein geöffnetes Untermenü oder ein geöffnetes Popupmenü zu schließen, drücken Sie die ESC-Taste.\n' + + '\n' + + '

      Wenn sich der aktuelle Fokus ganz oben in einem bestimmten Abschnitt der Benutzeroberfläche befindet, wird durch Drücken der ESC-Taste auch\n' + + ' die Tastaturnavigation beendet.

      \n' + + '\n' + + '

      Ein Menüelement oder eine Symbolleistenschaltfläche ausführen

      \n' + + '\n' + + '

      Wenn das gewünschte Menüelement oder die gewünschte Symbolleistenschaltfläche hervorgehoben ist, drücken Sie Zurück, Eingabe\n' + + ' oder die Leertaste, um das Element auszuführen.\n' + + '\n' + + '

      In Dialogfeldern ohne Registerkarten navigieren

      \n' + + '\n' + + '

      In Dialogfeldern ohne Registerkarten ist beim Öffnen eines Dialogfelds die erste interaktive Komponente fokussiert.

      \n' + + '\n' + + '

      Navigieren Sie zwischen den interaktiven Komponenten eines Dialogfelds, indem Sie TAB oder UMSCHALT+TAB drücken.

      \n' + + '\n' + + '

      In Dialogfeldern mit Registerkarten navigieren

      \n' + + '\n' + + '

      In Dialogfeldern mit Registerkarten ist beim Öffnen eines Dialogfelds die erste Schaltfläche eines Registerkartenmenüs fokussiert.

      \n' + + '\n' + + '

      Navigieren Sie zwischen den interaktiven Komponenten auf dieser Registerkarte des Dialogfelds, indem Sie TAB oder\n' + + ' UMSCHALT+TAB drücken.

      \n' + + '\n' + + '

      Wechseln Sie zu einer anderen Registerkarte des Dialogfelds, indem Sie den Fokus auf das Registerkartenmenü legen und dann die entsprechende Pfeiltaste\n' + + ' drücken, um durch die verfügbaren Registerkarten zu rotieren.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/el.js b/static/js/tinymce/plugins/help/js/i18n/keynav/el.js new file mode 100644 index 0000000..df75ec2 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/el.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.el', +'

      ΈναÏξη πλοήγησης μέσω πληκτÏολογίου

      \n' + + '\n' + + '
      \n' + + '
      Εστίαση στη γÏαμμή μενοÏ
      \n' + + '
      Windows ή Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Εστίαση στη γÏαμμή εÏγαλείων
      \n' + + '
      Windows ή Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Εστίαση στο υποσέλιδο
      \n' + + '
      Windows ή Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Εστίαση σε γÏαμμή εÏγαλείων βάσει πεÏιεχομένου
      \n' + + '
      Windows, Linux ή macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Η πλοήγηση θα ξεκινήσει από το Ï€Ïώτο στοιχείο πεÏιβάλλοντος χÏήστη, που θα επισημαίνεται ή θα είναι υπογÏαμμισμένο,\n' + + ' όπως στην πεÏίπτωση της διαδÏομής του στοιχείου Υποσέλιδου.

      \n' + + '\n' + + '

      Πλοήγηση Î¼ÎµÏ„Î±Î¾Ï ÎµÎ½Î¿Ï„Î®Ï„Ï‰Î½ του πεÏιβάλλοντος χÏήστη

      \n' + + '\n' + + '

      Για να μετακινηθείτε από μια ενότητα πεÏιβάλλοντος χÏήστη στην επόμενη, πιέστε το πλήκτÏο Tab.

      \n' + + '\n' + + '

      Για να μετακινηθείτε από μια ενότητα πεÏιβάλλοντος χÏήστη στην Ï€ÏοηγοÏμενη, πιέστε τα πλήκτÏα Shift+Tab.

      \n' + + '\n' + + '

      Η σειÏά Tab αυτών των ενοτήτων πεÏιβάλλοντος χÏήστη είναι η εξής:\n' + + '\n' + + '

        \n' + + '
      1. ΓÏαμμή μενοÏ
      2. \n' + + '
      3. Κάθε ομάδα γÏαμμής εÏγαλείων
      4. \n' + + '
      5. Πλαϊνή γÏαμμή
      6. \n' + + '
      7. ΔιαδÏομή στοιχείου στο υποσέλιδο
      8. \n' + + '
      9. Κουμπί εναλλαγής μέτÏησης λέξεων στο υποσέλιδο
      10. \n' + + '
      11. ΣÏνδεσμος επωνυμίας στο υποσέλιδο
      12. \n' + + '
      13. Λαβή αλλαγής μεγέθους Ï€ÏογÏάμματος επεξεÏγασίας στο υποσέλιδο
      14. \n' + + '
      \n' + + '\n' + + '

      Εάν δεν εμφανίζεται ενότητα πεÏιβάλλοντος χÏήστη, παÏαλείπεται.

      \n' + + '\n' + + '

      Εάν η εστίαση πλοήγησης βÏίσκεται στο πληκτÏολόγιο και δεν υπάÏχει εμφανής πλαϊνή γÏαμμή, εάν πιέσετε Shift+Tab\n' + + ' η εστίαση μετακινείται στην Ï€Ïώτη ομάδα γÏαμμής εÏγαλείων, όχι στην τελευταία.\n' + + '\n' + + '

      Πλοήγηση εντός των ενοτήτων του πεÏιβάλλοντος χÏήστη

      \n' + + '\n' + + '

      Για να μετακινηθείτε από ένα στοιχείο πεÏιβάλλοντος χÏήστη στο επόμενο, πιέστε το αντίστοιχο πλήκτÏο βέλους.

      \n' + + '\n' + + '

      Με τα πλήκτÏα αÏιστεÏÎ¿Ï ÎºÎ±Î¹ Î´ÎµÎ¾Î¹Î¿Ï Î²Î­Î»Î¿Ï…Ï‚

      \n' + + '\n' + + '
        \n' + + '
      • γίνεται μετακίνηση Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ Î¼ÎµÎ½Î¿Ï ÏƒÏ„Î· γÏαμμή μενοÏ.
      • \n' + + '
      • ανοίγει ένα Ï…Ï€Î¿Î¼ÎµÎ½Î¿Ï ÏƒÎµ ένα μενοÏ.
      • \n' + + '
      • γίνεται μετακίνηση Î¼ÎµÏ„Î±Î¾Ï ÎºÎ¿Ï…Î¼Ï€Î¹ÏŽÎ½ σε μια ομάδα γÏαμμής εÏγαλείων.
      • \n' + + '
      • γίνεται μετακίνηση Î¼ÎµÏ„Î±Î¾Ï ÏƒÏ„Î¿Î¹Ï‡ÎµÎ¯Ï‰Î½ στη διαδÏομή στοιχείου στο υποσέλιδο.
      • \n' + + '
      \n' + + '\n' + + '

      Με τα πλήκτÏα επάνω και κάτω βέλους\n' + + '\n' + + '

        \n' + + '
      • γίνεται μετακίνηση Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ στοιχείων Î¼ÎµÎ½Î¿Ï ÏƒÎµ ένα μενοÏ.
      • \n' + + '
      • γίνεται μετακίνηση Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ στοιχείων Î¼ÎµÎ½Î¿Ï ÏƒÎµ ένα αναδυόμενο Î¼ÎµÎ½Î¿Ï Î³Ïαμμής εÏγαλείων.
      • \n' + + '
      \n' + + '\n' + + '

      Με τα πλήκτÏα βέλους γίνεται κυκλική μετακίνηση εντός της εστιασμένης ενότητας πεÏιβάλλοντος χÏήστη.

      \n' + + '\n' + + '

      Για να κλείσετε ένα ανοιχτό μενοÏ, ένα ανοιχτό Ï…Ï€Î¿Î¼ÎµÎ½Î¿Ï Î® ένα ανοιχτό αναδυόμενο μενοÏ, πιέστε το πλήκτÏο Esc.\n' + + '\n' + + '

      Εάν η Ï„Ïέχουσα εστίαση βÏίσκεται στην κοÏυφή μιας ενότητας πεÏιβάλλοντος χÏήστη, πιέζοντας το πλήκτÏο Esc,\n' + + ' γίνεται επίσης πλήÏης έξοδος από την πλοήγηση μέσω πληκτÏολογίου.

      \n' + + '\n' + + '

      Εκτέλεση ενός στοιχείου Î¼ÎµÎ½Î¿Ï Î® ÎºÎ¿Ï…Î¼Ï€Î¹Î¿Ï Î³Ïαμμής εÏγαλείων

      \n' + + '\n' + + '

      Όταν το επιθυμητό στοιχείο Î¼ÎµÎ½Î¿Ï Î® κουμπί γÏαμμής εÏγαλείων είναι επισημασμένο, πιέστε τα πλήκτÏα Return, Enter,\n' + + ' ή το πλήκτÏο διαστήματος για να εκτελέσετε το στοιχείο.\n' + + '\n' + + '

      Πλοήγηση σε παÏάθυÏα διαλόγου χωÏίς καÏτέλες

      \n' + + '\n' + + '

      Σε παÏάθυÏα διαλόγου χωÏίς καÏτέλες, το Ï€Ïώτο αλληλεπιδÏαστικό στοιχείο λαμβάνει την εστίαση όταν ανοίγει το παÏάθυÏο διαλόγου.

      \n' + + '\n' + + '

      ΜποÏείτε να πλοηγηθείτε Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ αλληλεπιδÏαστικών στοιχείων παÏαθÏÏων διαλόγων πιέζοντας τα πλήκτÏα Tab ή Shift+Tab.

      \n' + + '\n' + + '

      Πλοήγηση σε παÏάθυÏα διαλόγου με καÏτέλες

      \n' + + '\n' + + '

      Σε παÏάθυÏα διαλόγου με καÏτέλες, το Ï€Ïώτο κουμπί στο Î¼ÎµÎ½Î¿Ï ÎºÎ±Ïτέλας λαμβάνει την εστίαση όταν ανοίγει το παÏάθυÏο διαλόγου.

      \n' + + '\n' + + '

      ΜποÏείτε να πλοηγηθείτε Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ αλληλεπιδÏαστικών στοιχείων αυτής της καÏτέλα διαλόγου πιέζοντας τα πλήκτÏα Tab ή\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      ΜποÏείτε να κάνετε εναλλαγή σε άλλη καÏτέλα του παÏαθÏÏου διαλόγου, μεταφέÏοντας την εστίαση στο Î¼ÎµÎ½Î¿Ï ÎºÎ±Ïτέλας και πιέζοντας το κατάλληλο πλήκτÏο βέλους\n' + + ' για να μετακινηθείτε κυκλικά στις διαθέσιμες καÏτέλες.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/en.js b/static/js/tinymce/plugins/help/js/i18n/keynav/en.js new file mode 100644 index 0000000..7f0fca7 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/en.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.en', +'

      Begin keyboard navigation

      \n' + + '\n' + + '
      \n' + + '
      Focus the Menu bar
      \n' + + '
      Windows or Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Focus the Toolbar
      \n' + + '
      Windows or Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Focus the footer
      \n' + + '
      Windows or Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Focus a contextual toolbar
      \n' + + '
      Windows, Linux or macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigation will start at the first UI item, which will be highlighted, or underlined in the case of the first item in\n' + + ' the Footer element path.

      \n' + + '\n' + + '

      Navigate between UI sections

      \n' + + '\n' + + '

      To move from one UI section to the next, press Tab.

      \n' + + '\n' + + '

      To move from one UI section to the previous, press Shift+Tab.

      \n' + + '\n' + + '

      The Tab order of these UI sections is:\n' + + '\n' + + '

        \n' + + '
      1. Menu bar
      2. \n' + + '
      3. Each toolbar group
      4. \n' + + '
      5. Sidebar
      6. \n' + + '
      7. Element path in the footer
      8. \n' + + '
      9. Word count toggle button in the footer
      10. \n' + + '
      11. Branding link in the footer
      12. \n' + + '
      13. Editor resize handle in the footer
      14. \n' + + '
      \n' + + '\n' + + '

      If a UI section is not present, it is skipped.

      \n' + + '\n' + + '

      If the footer has keyboard navigation focus, and there is no visible sidebar, pressing Shift+Tab\n' + + ' moves focus to the first toolbar group, not the last.\n' + + '\n' + + '

      Navigate within UI sections

      \n' + + '\n' + + '

      To move from one UI element to the next, press the appropriate Arrow key.

      \n' + + '\n' + + '

      The Left and Right arrow keys

      \n' + + '\n' + + '
        \n' + + '
      • move between menus in the menu bar.
      • \n' + + '
      • open a sub-menu in a menu.
      • \n' + + '
      • move between buttons in a toolbar group.
      • \n' + + '
      • move between items in the footer’s element path.
      • \n' + + '
      \n' + + '\n' + + '

      The Down and Up arrow keys\n' + + '\n' + + '

        \n' + + '
      • move between menu items in a menu.
      • \n' + + '
      • move between items in a toolbar pop-up menu.
      • \n' + + '
      \n' + + '\n' + + '

      Arrow keys cycle within the focused UI section.

      \n' + + '\n' + + '

      To close an open menu, an open sub-menu, or an open pop-up menu, press the Esc key.\n' + + '\n' + + '

      If the current focus is at the ‘top’ of a particular UI section, pressing the Esc key also exits\n' + + ' keyboard navigation entirely.

      \n' + + '\n' + + '

      Execute a menu item or toolbar button

      \n' + + '\n' + + '

      When the desired menu item or toolbar button is highlighted, press Return, Enter,\n' + + ' or the Space bar to execute the item.\n' + + '\n' + + '

      Navigate non-tabbed dialogs

      \n' + + '\n' + + '

      In non-tabbed dialogs, the first interactive component takes focus when the dialog opens.

      \n' + + '\n' + + '

      Navigate between interactive dialog components by pressing Tab or Shift+Tab.

      \n' + + '\n' + + '

      Navigate tabbed dialogs

      \n' + + '\n' + + '

      In tabbed dialogs, the first button in the tab menu takes focus when the dialog opens.

      \n' + + '\n' + + '

      Navigate between interactive components of this dialog tab by pressing Tab or\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Switch to another dialog tab by giving the tab menu focus and then pressing the appropriate Arrow\n' + + ' key to cycle through the available tabs.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/es.js b/static/js/tinymce/plugins/help/js/i18n/keynav/es.js new file mode 100644 index 0000000..008382c --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/es.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.es', +'

      Iniciar la navegación con el teclado

      \n' + + '\n' + + '
      \n' + + '
      Enfocar la barra de menús
      \n' + + '
      Windows o Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Enfocar la barra de herramientas
      \n' + + '
      Windows o Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Enfocar el pie de página
      \n' + + '
      Windows o Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Enfocar una barra de herramientas contextual
      \n' + + '
      Windows, Linux o macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      La navegación comenzará por el primer elemento de la interfaz de usuario (IU), de tal manera que se resaltará, o bien se subrayará si se trata del primer elemento de\n' + + ' la ruta de elemento del pie de página.

      \n' + + '\n' + + '

      Navegar entre las secciones de la IU

      \n' + + '\n' + + '

      Para pasar de una sección de la IU a la siguiente, pulse la tecla Tab.

      \n' + + '\n' + + '

      Para pasar de una sección de la IU a la anterior, pulse Mayús+Tab.

      \n' + + '\n' + + '

      El orden de tabulación de estas secciones de la IU es:\n' + + '\n' + + '

        \n' + + '
      1. Barra de menús
      2. \n' + + '
      3. Cada grupo de barra de herramientas
      4. \n' + + '
      5. Barra lateral
      6. \n' + + '
      7. Ruta del elemento en el pie de página
      8. \n' + + '
      9. Botón de alternancia de recuento de palabras en el pie de página
      10. \n' + + '
      11. Enlace de personalización de marca en el pie de página
      12. \n' + + '
      13. Controlador de cambio de tamaño en el pie de página
      14. \n' + + '
      \n' + + '\n' + + '

      Si una sección de la IU no está presente, esta se omite.

      \n' + + '\n' + + '

      Si el pie de página tiene un enfoque de navegación con el teclado y no hay ninguna barra lateral visible, al pulsar Mayús+Tab,\n' + + ' el enfoque se moverá al primer grupo de barra de herramientas, en lugar de al último.\n' + + '\n' + + '

      Navegar dentro de las secciones de la IU

      \n' + + '\n' + + '

      Para pasar de un elemento de la IU al siguiente, pulse la tecla de flecha correspondiente.

      \n' + + '\n' + + '

      Las teclas de flecha izquierda y derecha permiten

      \n' + + '\n' + + '
        \n' + + '
      • desplazarse entre los menús de la barra de menús.
      • \n' + + '
      • abrir el submenú de un menú.
      • \n' + + '
      • desplazarse entre los botones de un grupo de barra de herramientas.
      • \n' + + '
      • desplazarse entre los elementos de la ruta de elemento del pie de página.
      • \n' + + '
      \n' + + '\n' + + '

      Las teclas de flecha abajo y arriba permiten\n' + + '\n' + + '

        \n' + + '
      • desplazarse entre los elementos de menú de un menú.
      • \n' + + '
      • desplazarse entre los elementos de un menú emergente de una barra de herramientas.
      • \n' + + '
      \n' + + '\n' + + '

      Las teclas de flecha van cambiando dentro de la sección de la IU enfocada.

      \n' + + '\n' + + '

      Para cerrar un menú, un submenú o un menú emergente que estén abiertos, pulse la tecla Esc.\n' + + '\n' + + '

      Si el enfoque actual se encuentra en la parte superior de una sección de la IU determinada, al pulsar la tecla Esc saldrá\n' + + ' de la navegación con el teclado por completo.

      \n' + + '\n' + + '

      Ejecutar un elemento de menú o un botón de barra de herramientas

      \n' + + '\n' + + '

      Si el elemento de menú o el botón de barra de herramientas deseado está resaltado, pulse la tecla Retorno o Entrar,\n' + + ' o la barra espaciadora para ejecutar el elemento.\n' + + '\n' + + '

      Navegar por cuadros de diálogo sin pestañas

      \n' + + '\n' + + '

      En los cuadros de diálogo sin pestañas, el primer componente interactivo se enfoca al abrirse el cuadro de diálogo.

      \n' + + '\n' + + '

      Para navegar entre los componentes interactivos del cuadro de diálogo, pulse las teclas Tab o Mayús+Tab.

      \n' + + '\n' + + '

      Navegar por cuadros de diálogo con pestañas

      \n' + + '\n' + + '

      En los cuadros de diálogo con pestañas, el primer botón del menú de pestaña se enfoca al abrirse el cuadro de diálogo.

      \n' + + '\n' + + '

      Para navegar entre componentes interactivos de esta pestaña del cuadro de diálogo, pulse las teclas Tab o\n' + + ' Mayús+Tab.

      \n' + + '\n' + + '

      Si desea cambiar a otra pestaña del cuadro de diálogo, enfoque el menú de pestañas y, a continuación, pulse la tecla de flecha\n' + + ' correspondiente para moverse por las pestañas disponibles.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/eu.js b/static/js/tinymce/plugins/help/js/i18n/keynav/eu.js new file mode 100644 index 0000000..637a389 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/eu.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.eu', +'

      Hasi teklatuaren nabigazioa

      \n' + + '\n' + + '
      \n' + + '
      Fokuratu menu-barra
      \n' + + '
      Windows edo Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokuratu tresna-barra
      \n' + + '
      Windows edo Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokuratu orri-oina
      \n' + + '
      Windows edo Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokuratu testuinguruaren tresna-barra
      \n' + + '
      Windows, Linux edo macOS: Ktrl+F9\n' + + '
      \n' + + '\n' + + '

      Nabigazioa EIko lehen elementuan hasiko da: elementu hori nabarmendu egingo da, edo azpimarratu lehen elementua bada\n' + + ' orri-oineko elementuaren bidea.

      \n' + + '\n' + + '

      Nabigatu EIko atalen artean

      \n' + + '\n' + + '

      EIko atal batetik hurrengora mugitzeko, sakatu Tabuladorea.

      \n' + + '\n' + + '

      EIko atal batetik aurrekora mugitzeko, sakatu Maius+Tabuladorea.

      \n' + + '\n' + + '

      EIko atal hauen Tabuladorea da:\n' + + '\n' + + '

        \n' + + '
      1. Menu-barra
      2. \n' + + '
      3. Tresna-barraren talde bakoitza
      4. \n' + + '
      5. Alboko barra
      6. \n' + + '
      7. Orri-oineko elementuaren bidea
      8. \n' + + '
      9. Orri-oneko urrats-kontaketa txandakatzeko botoia
      10. \n' + + '
      11. Orri-oineko marken esteka
      12. \n' + + '
      13. Orri-oineko editorearen tamaina aldatzeko heldulekua
      14. \n' + + '
      \n' + + '\n' + + '

      EIko atal bat ez badago, saltatu egin da.

      \n' + + '\n' + + '

      Orri-oinak teklatuaren nabigazioa fokuratuta badago, eta alboko barra ikusgai ez badago, Maius+Tabuladorea sakatuz gero,\n' + + ' fokua tresna-barrako lehen taldera eramaten da, ez azkenera.\n' + + '\n' + + '

      Nabigatu EIko atalen barruan

      \n' + + '\n' + + '

      EIko elementu batetik hurrengora mugitzeko, sakatu dagokion Gezia tekla.

      \n' + + '\n' + + '

      Ezkerrera eta Eskuinera gezi-teklak

      \n' + + '\n' + + '
        \n' + + '
      • menu-barrako menuen artean mugitzen da.
      • \n' + + '
      • ireki azpimenu bat menuan.
      • \n' + + '
      • mugitu botoi batetik bestera tresna-barren talde batean.
      • \n' + + '
      • mugitu orri-oineko elementuaren bideko elementu batetik bestera.
      • \n' + + '
      \n' + + '\n' + + '

      Gora eta Behera gezi-teklak\n' + + '\n' + + '

        \n' + + '
      • mugitu menu bateko menu-elementuen artean.
      • \n' + + '
      • mugitu tresna-barrako menu gainerakor bateko menu-elementuen artean.
      • \n' + + '
      \n' + + '\n' + + '

      Gezia teklen zikloa nabarmendutako EI atalen barruan.

      \n' + + '\n' + + '

      Irekitako menu bat ixteko, ireki azpimenua, edo ireki menu gainerakorra, sakatu Ihes tekla.\n' + + '\n' + + '

      Une horretan fokuratzea EIko atal jakin baten "goialdean" badago, Ihes tekla sakatuz gero \n' + + ' teklatuaren nabigaziotik irtengo zara.

      \n' + + '\n' + + '

      Exekutatu menuko elementu bat edo tresna-barrako botoi bat

      \n' + + '\n' + + '

      Nahi den menuaren elementua edo tresna-barraren botoia nabarmenduta dagoenean, sakatu Itzuli, Sartu\n' + + ' edo Zuriune-barra elementua exekutatzeko.\n' + + '\n' + + '

      Nabigatu fitxarik gabeko elkarrizketak

      \n' + + '\n' + + '

      Fitxarik gabeko elkarrizketetan, lehen osagai interaktiboa fokuratzen da elkarrizketa irekitzen denean.

      \n' + + '\n' + + '

      Nabigatu elkarrizketa interaktiboko osagai batetik bestera Tabuladorea edo Maius+Tabuladorea sakatuta.

      \n' + + '\n' + + '

      Nabigatu fitxadun elkarrizketak

      \n' + + '\n' + + '

      Fitxadun elkarrizketetan, fitxa-menuko lehen botoia fokuratzen da elkarrizketa irekitzen denean.

      \n' + + '\n' + + '

      Nabigatu elkarrizketa-fitxa honen interaktiboko osagai batetik bestera Tabuladorea edo\n' + + ' Maius+Tabuladorea sakatuta.

      \n' + + '\n' + + '

      Aldatu beste elkarrizketa-fitxa batera fitxa-menua fokuratu eta dagokion Gezia\n' + + ' tekla sakatzeko, erabilgarri dauden fitxa batetik bestera txandakatzeko.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/fa.js b/static/js/tinymce/plugins/help/js/i18n/keynav/fa.js new file mode 100644 index 0000000..8ffcc86 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/fa.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.fa', +'

      شروع پیمایش صÙحه‌کلید

      \n' + + '\n' + + '
      \n' + + '
      تمرکز بر نوار منو
      \n' + + '
      Windows یا Linux:‎â€: Alt+F9
      \n' + + '
      ‎â€macOS: ⌥F9‎â€
      \n' + + '
      تمرکز بر نوار ابزار
      \n' + + '
      Windows یا Linux‎â€: Alt+F10
      \n' + + '
      ‎â€macOS: ⌥F10‎â€
      \n' + + '
      تمرکز بر پانویس
      \n' + + '
      Windows یا Linux‎â€: Alt+F11
      \n' + + '
      ‎â€macOS: ⌥F11‎â€
      \n' + + '
      تمرکز بر نوار ابزار باÙتاری
      \n' + + '
      Windows ،Linux یا macOS:†Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      پیمایش در اولین مورد رابط کاربری شروع می‌شود و درخصوص اولین مورد در\n' + + ' مسیر عنصر پانویس، برجسته یا زیرخط‌دار می‌شود.

      \n' + + '\n' + + '

      پیمایش بین بخش‌های رابط کاربری

      \n' + + '\n' + + '

      برای جابجایی از یک بخش رابط کاربری به بخش بعدی، Tab را Ùشار دهید.

      \n' + + '\n' + + '

      برای جابجایی از یک بخش رابط کاربری به بخش قبلی، Shift+Tab را Ùشار دهید.

      \n' + + '\n' + + '

      ترتیب Tab این بخش‌های رابط کاربری عبارتند از:\n' + + '\n' + + '

        \n' + + '
      1. نوار منو
      2. \n' + + '
      3. هر گروه نوار ابزار
      4. \n' + + '
      5. نوار کناری
      6. \n' + + '
      7. مسیر عنصر در پانویس
      8. \n' + + '
      9. دکمه تغییر وضعیت تعداد کلمات در پانویس
      10. \n' + + '
      11. پیوند نمانام‌سازی در پانویس
      12. \n' + + '
      13. دسته تغییر اندازه ویرایشگر در پانویس
      14. \n' + + '
      \n' + + '\n' + + '

      اگر بخشی از رابط کاربری موجود نباشد، رد می‌شود.

      \n' + + '\n' + + '

      اگر پانویس دارای تمرکز بر پیمایش صÙحه‌کلید باشد،‌ Ùˆ نوار کناری قابل‌مشاهده وجود ندارد، Ùشردن Shift+Tab\n' + + ' تمرکز را به گروه نوار ابزار اول می‌برد، نه آخر.\n' + + '\n' + + '

      پیمایش در بخش‌های رابط کاربری

      \n' + + '\n' + + '

      برای جابجایی از یک عنصر رابط کاربری به بعدی، کلید جهت‌نمای مناسب را Ùشار دهید.

      \n' + + '\n' + + '

      کلیدهای جهت‌نمای چپ و راست

      \n' + + '\n' + + '
        \n' + + '
      • جابجایی بین منوها در نوار منو.
      • \n' + + '
      • باز کردن منوی Ùرعی در یک منو.
      • \n' + + '
      • جابجایی بین دکمه‌ها در یک گروه نوار ابزار.
      • \n' + + '
      • جابجایی بین موارد در مسیر عنصر پانویس.
      • \n' + + '
      \n' + + '\n' + + '

      کلیدهای جهت‌نمای پایین و بالا\n' + + '\n' + + '

        \n' + + '
      • جابجایی بین موارد منو در یک منو.
      • \n' + + '
      • جابجایی بین موارد در یک منوی بازشوی نوار ابزار.
      • \n' + + '
      \n' + + '\n' + + '

      کلیدهایجهت‌نما در بخش رابط کاربری متمرکز می‌چرخند.

      \n' + + '\n' + + '

      برای بستن یک منوی باز، یک منوی Ùرعی باز، یا یک منوی بازشوی باز، کلید Esc را Ùشار دهید.\n' + + '\n' + + '

      اگر تمرکز Ùعلی در «بالای» یک بخش رابط کاربری خاص است، Ùشردن کلید Esc نیز موجب\n' + + ' خروج کامل از پیمایش صÙحه‌کلید می‌شود.

      \n' + + '\n' + + '

      اجرای یک مورد منو یا دکمه نوار ابزار

      \n' + + '\n' + + '

      وقتی مورد منو یا دکمه نوار ابزار مورد نظر هایلایت شد، دکمه بازگشت، EnterØŒ\n' + + ' یا نوار Space را Ùشار دهید تا مورد را اجرا کنید.\n' + + '\n' + + '

      پیمایش در کادرهای Ú¯Ùتگوی بدون زبانه

      \n' + + '\n' + + '

      در کادرهای Ú¯Ùتگوی بدون زبانه، وقتی کادر Ú¯Ùتگو باز می‌شود، اولین جزء تعاملی متمرکز می‌شود.

      \n' + + '\n' + + '

      با Ùشردن Tab یا Shift+TabØŒ بین اجزای کادر Ú¯Ùتگوی تعاملی پیمایش کنید.

      \n' + + '\n' + + '

      پیمایش کادرهای Ú¯Ùتگوی زبانه‌دار

      \n' + + '\n' + + '

      در کادرهای Ú¯Ùتگوی زبانه‌دار، وقتی کادر Ú¯Ùتگو باز می‌شود، اولین دکمه در منوی زبانه متمرکز می‌شود.

      \n' + + '\n' + + '

      با Ùشردن Tab یا\n' + + ' Shift+TabØŒ بین اجزای تعاملی این زبانه کادر Ú¯Ùتگو پیمایش کنید.

      \n' + + '\n' + + '

      با دادن تمرکز به منوی زبانه Ùˆ سپس Ùشار دادن کلید جهت‌نمای\n' + + ' مناسب برای چرخش میان زبانه‌های موجود، به زبانه کادر Ú¯Ùتگوی دیگری بروید.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/fi.js b/static/js/tinymce/plugins/help/js/i18n/keynav/fi.js new file mode 100644 index 0000000..319074e --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/fi.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.fi', +'

      Näppäimistönavigoinnin aloittaminen

      \n' + + '\n' + + '
      \n' + + '
      Siirrä kohdistus valikkopalkkiin
      \n' + + '
      Windows tai Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Siirrä kohdistus työkalupalkkiin
      \n' + + '
      Windows tai Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Siirrä kohdistus alatunnisteeseen
      \n' + + '
      Windows tai Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Siirrä kohdistus kontekstuaaliseen työkalupalkkiin
      \n' + + '
      Windows, Linux tai macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigointi aloitetaan ensimmäisestä käyttöliittymän kohteesta, joka joko korostetaan tai alleviivataan, jos\n' + + ' kyseessä on Alatunniste-elementin polun ensimmäinen kohde.

      \n' + + '\n' + + '

      Käyttöliittymän eri osien välillä navigointi

      \n' + + '\n' + + '

      Paina sarkainnäppäintä siirtyäksesi käyttöliittymän osasta seuraavaan.

      \n' + + '\n' + + '

      Jos haluat siirtyä edelliseen käyttöliittymän osaan, paina Shift+sarkainnäppäin.

      \n' + + '\n' + + '

      Sarkainnäppäin siirtää sinua näissä käyttöliittymän osissa tässä järjestyksessä:\n' + + '\n' + + '

        \n' + + '
      1. Valikkopalkki
      2. \n' + + '
      3. Työkalupalkin ryhmät
      4. \n' + + '
      5. Sivupalkki
      6. \n' + + '
      7. Elementin polku alatunnisteessa
      8. \n' + + '
      9. Sanalaskurin vaihtopainike alatunnisteessa
      10. \n' + + '
      11. Brändäyslinkki alatunnisteessa
      12. \n' + + '
      13. Editorin koon muuttamisen kahva alatunnisteessa
      14. \n' + + '
      \n' + + '\n' + + '

      Jos jotakin käyttöliittymän osaa ei ole, se ohitetaan.

      \n' + + '\n' + + '

      Jos kohdistus on siirretty alatunnisteeseen näppäimistönavigoinnilla eikä sivupalkkia ole näkyvissä, Shift+sarkainnäppäin\n' + + ' siirtää kohdistuksen työkalupalkin ensimmäiseen ryhmään, eikä viimeiseen.\n' + + '\n' + + '

      Käyttöliittymän eri osien sisällä navigointi

      \n' + + '\n' + + '

      Paina nuolinäppäimiä siirtyäksesi käyttöliittymäelementistä seuraavaan.

      \n' + + '\n' + + '

      Vasen- ja Oikea-nuolinäppäimet

      \n' + + '\n' + + '
        \n' + + '
      • siirtävät sinua valikkopalkin valikoiden välillä.
      • \n' + + '
      • avaavat valikon alavalikon.
      • \n' + + '
      • siirtävät sinua työkalupalkin ryhmän painikkeiden välillä.
      • \n' + + '
      • siirtävät sinua kohteiden välillä alatunnisteen elementin polussa.
      • \n' + + '
      \n' + + '\n' + + '

      Alas- ja Ylös-nuolinäppäimet\n' + + '\n' + + '

        \n' + + '
      • siirtävät sinua valikon valikkokohteiden välillä.
      • \n' + + '
      • siirtävät sinua työkalupalkin ponnahdusvalikon kohteiden välillä.
      • \n' + + '
      \n' + + '\n' + + '

      Nuolinäppäimet siirtävät sinua käyttöliittymän korostetun osan sisällä syklissä.

      \n' + + '\n' + + '

      Paina Esc-näppäintä sulkeaksesi avoimen valikon, avataksesi alavalikon tai avataksesi ponnahdusvalikon.\n' + + '\n' + + '

      Jos kohdistus on käyttöliittymän tietyn osion ylälaidassa, Esc-näppäimen painaminen\n' + + ' poistuu myös näppäimistönavigoinnista kokonaan.

      \n' + + '\n' + + '

      Suorita valikkokohde tai työkalupalkin painike

      \n' + + '\n' + + '

      Kun haluamasi valikkokohde tai työkalupalkin painike on korostettuna, paina Return-, Enter-\n' + + ' tai välilyöntinäppäintä suorittaaksesi kohteen.\n' + + '\n' + + '

      Välilehdittömissä valintaikkunoissa navigointi

      \n' + + '\n' + + '

      Kun välilehdetön valintaikkuna avautuu, kohdistus siirtyy sen ensimmäiseen interaktiiviseen komponenttiin.

      \n' + + '\n' + + '

      Voit siirtyä valintaikkunan interaktiivisten komponenttien välillä painamalla sarkainnäppäintä tai Shift+sarkainnäppäin.

      \n' + + '\n' + + '

      Välilehdellisissä valintaikkunoissa navigointi

      \n' + + '\n' + + '

      Kun välilehdellinen valintaikkuna avautuu, kohdistus siirtyy välilehtivalikon ensimmäiseen painikkeeseen.

      \n' + + '\n' + + '

      Voit siirtyä valintaikkunan välilehden interaktiivisen komponenttien välillä painamalla sarkainnäppäintä tai\n' + + ' Shift+sarkainnäppäin.

      \n' + + '\n' + + '

      Voit siirtyä valintaikkunan toiseen välilehteen siirtämällä kohdistuksen välilehtivalikkoon ja painamalla sopivaa nuolinäppäintä\n' + + ' siirtyäksesi käytettävissä olevien välilehtien välillä syklissä.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/fr_FR.js b/static/js/tinymce/plugins/help/js/i18n/keynav/fr_FR.js new file mode 100644 index 0000000..f8e208d --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/fr_FR.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.fr_FR', +'

      Débuter la navigation au clavier

      \n' + + '\n' + + '
      \n' + + '
      Cibler la barre du menu
      \n' + + '
      Windows ou Linux : Alt+F9
      \n' + + '
      macOS : ⌥F9
      \n' + + "
      Cibler la barre d'outils
      \n" + + '
      Windows ou Linux : Alt+F10
      \n' + + '
      macOS : ⌥F10
      \n' + + '
      Cibler le pied de page
      \n' + + '
      Windows ou Linux : Alt+F11
      \n' + + '
      macOS : ⌥F11
      \n' + + "
      Cibler une barre d'outils contextuelle
      \n" + + '
      Windows, Linux ou macOS : Ctrl+F9\n' + + '
      \n' + + '\n' + + "

      La navigation débutera sur le premier élément de l'interface utilisateur, qui sera mis en surbrillance ou bien souligné dans le cas du premier élément du\n" + + " chemin d'éléments du pied de page.

      \n" + + '\n' + + "

      Naviguer entre les sections de l'interface utilisateur

      \n" + + '\n' + + "

      Pour passer d'une section de l'interface utilisateur à la suivante, appuyez sur Tabulation.

      \n" + + '\n' + + "

      Pour passer d'une section de l'interface utilisateur à la précédente, appuyez sur Maj+Tabulation.

      \n" + + '\n' + + "

      L'ordre de Tabulation de ces sections de l'interface utilisateur est le suivant :\n" + + '\n' + + '

        \n' + + '
      1. Barre du menu
      2. \n' + + "
      3. Chaque groupe de barres d'outils
      4. \n" + + '
      5. Barre latérale
      6. \n' + + "
      7. Chemin d'éléments du pied de page
      8. \n" + + "
      9. Bouton d'activation du compteur de mots dans le pied de page
      10. \n" + + '
      11. Lien de marque dans le pied de page
      12. \n' + + "
      13. Poignée de redimensionnement de l'éditeur dans le pied de page
      14. \n" + + '
      \n' + + '\n' + + "

      Si une section de l'interface utilisateur n'est pas présente, elle sera ignorée.

      \n" + + '\n' + + "

      Si le pied de page comporte un ciblage par navigation au clavier et qu'il n'y a aucune barre latérale visible, appuyer sur Maj+Tabulation\n" + + " déplace le ciblage vers le premier groupe de barres d'outils et non le dernier.\n" + + '\n' + + "

      Naviguer au sein des sections de l'interface utilisateur

      \n" + + '\n' + + "

      Pour passer d'un élément de l'interface utilisateur au suivant, appuyez sur la Flèche appropriée.

      \n" + + '\n' + + '

      Les touches fléchées Gauche et Droite

      \n' + + '\n' + + '
        \n' + + '
      • se déplacent entre les menus de la barre des menus.
      • \n' + + "
      • ouvrent un sous-menu au sein d'un menu.
      • \n" + + "
      • se déplacent entre les boutons d'un groupe de barres d'outils.
      • \n" + + "
      • se déplacent entre les éléments du chemin d'éléments du pied de page.
      • \n" + + '
      \n' + + '\n' + + '

      Les touches fléchées Bas et Haut\n' + + '\n' + + '

        \n' + + "
      • se déplacent entre les éléments de menu au sein d'un menu.
      • \n" + + "
      • se déplacent entre les éléments au sein d'un menu contextuel de barre d'outils.
      • \n" + + '
      \n' + + '\n' + + "

      Les Flèches parcourent la section de l'interface utilisateur ciblée.

      \n" + + '\n' + + '

      Pour fermer un menu ouvert, un sous-menu ouvert ou un menu contextuel ouvert, appuyez sur Echap.\n' + + '\n' + + "

      Si l'actuel ciblage se trouve en « haut » d'une section spécifique de l'interface utilisateur, appuyer sur Echap permet également de quitter\n" + + ' entièrement la navigation au clavier.

      \n' + + '\n' + + "

      Exécuter un élément de menu ou un bouton de barre d'outils

      \n" + + '\n' + + "

      Lorsque l'élément de menu ou le bouton de barre d'outils désiré est mis en surbrillance, appuyez sur la touche Retour arrière, Entrée\n" + + " ou la Barre d'espace pour exécuter l'élément.\n" + + '\n' + + '

      Naviguer au sein de dialogues sans onglets

      \n' + + '\n' + + "

      Dans les dialogues sans onglets, le premier composant interactif est ciblé lorsque le dialogue s'ouvre.

      \n" + + '\n' + + '

      Naviguez entre les composants du dialogue interactif en appuyant sur Tabulation ou Maj+Tabulation.

      \n' + + '\n' + + '

      Naviguer au sein de dialogues avec onglets

      \n' + + '\n' + + "

      Dans les dialogues avec onglets, le premier bouton du menu de l'onglet est ciblé lorsque le dialogue s'ouvre.

      \n" + + '\n' + + '

      Naviguez entre les composants interactifs de cet onglet de dialogue en appuyant sur Tabulation ou\n' + + ' Maj+Tabulation.

      \n' + + '\n' + + "

      Passez à un autre onglet de dialogue en ciblant le menu de l'onglet et en appuyant sur la Flèche\n" + + ' appropriée pour parcourir les onglets disponibles.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/he_IL.js b/static/js/tinymce/plugins/help/js/i18n/keynav/he_IL.js new file mode 100644 index 0000000..37876d5 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/he_IL.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.he_IL', +'

      התחל ניווט במקלדת

      \n' + + '\n' + + '
      \n' + + '
      התמקד בשורת התפריטי×
      \n' + + '
      Windows ×ו Linux:†Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      העבר מיקוד לסרגל הכלי×
      \n' + + '
      Windows ×ו Linux:†Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      העבר מיקוד לכותרת התחתונה
      \n' + + '
      Windows ×ו Linux:†Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      העבר מיקוד לסרגל ×›×œ×™× ×”×§×©×¨×™
      \n' + + '
      Windowsâ€, Linux ×ו macOS:†Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      הניווט יתחיל ברכיב הר×שון במשך, שיודגש ×ו שיהיה מתחתיו קו תחתון במקרה של הפריט הר×שון\n' + + ' הנתיב של רכיב הכותרת התחתונה.

      \n' + + '\n' + + '

      עבור בין ×ž×§×˜×¢×™× ×‘×ž×¡×š

      \n' + + '\n' + + '

      כדי לעבור בין ×”×ž×§×˜×¢×™× ×‘×ž×¡×š, הקש Tab.

      \n' + + '\n' + + '

      כדי לעבור למקטע ×”×§×•×“× ×‘×ž×¡×š, הקש Shift+Tab.

      \n' + + '\n' + + '

      הסדר מבחינת מקש Tab של ×”×¨×›×™×‘×™× ×‘×ž×¡×š:\n' + + '\n' + + '

        \n' + + '
      1. שורת התפריטי×
      2. \n' + + '
      3. כל קבוצה בסרגל הכלי×
      4. \n' + + '
      5. הסרגל הצידי
      6. \n' + + '
      7. נתיב של רכיב בכותרת התחתונה
      8. \n' + + '
      9. לחצן לספירת ×ž×™×œ×™× ×‘×›×•×ª×¨×ª התחתונה
      10. \n' + + '
      11. קישור של המותג בכותרת התחתונה
      12. \n' + + '
      13. ידית לשינוי גודל עבור העורך בכותרת התחתונה
      14. \n' + + '
      \n' + + '\n' + + '

      ×× ×¨×›×™×‘ כלשהו במסך ×œ× ×ž×•×¤×™×¢, המערכת תדלג עליו.

      \n' + + '\n' + + '

      ×× ×‘×›×•×ª×¨×ª התחתונה יש מיקוד של ניווט במקלדת, ×•×œ× ×ž×•×¤×™×¢ סרגל בצד, יש להקיש Shift+Tab\n' + + ' מעביר ×ת המיקוד לקבוצה הר×שונה בסרגל הכלי×, ×œ× ×”×חרונה.\n' + + '\n' + + '

      עבור בתוך ×ž×§×˜×¢×™× ×‘×ž×¡×š

      \n' + + '\n' + + '

      כדי לעבור מרכיב ×חד לרכיב ×חר במסך, הקש על מקש ×”×—×¥ המת××™×.

      \n' + + '\n' + + '

      מקשי ×”×—×™×¦×™× ×©×ž×לה וימינה

      \n' + + '\n' + + '
        \n' + + '
      • עבור בין ×ª×¤×¨×™×˜×™× ×‘×©×•×¨×ª התפריטי×.
      • \n' + + '
      • פתח תפריט משני בתפריט.
      • \n' + + '
      • עבור בין ×œ×—×¦× ×™× ×‘×§×‘×•×¦×” בסרגל הכלי×.
      • \n' + + '
      • עבור בין ×¤×¨×™×˜×™× ×‘×¨×›×™×‘ בכותרת התחתונה.
      • \n' + + '
      \n' + + '\n' + + '

      מקשי ×”×—×™×¦×™× ×œ×ž×˜×” ולמעלה\n' + + '\n' + + '

        \n' + + '
      • עבור בין ×¤×¨×™×˜×™× ×‘×ª×¤×¨×™×˜.
      • \n' + + '
      • עבור בין ×¤×¨×™×˜×™× ×‘×—×œ×•×Ÿ הקובץ של סרגל הכלי×.
      • \n' + + '
      \n' + + '\n' + + '

      מקשי ×”×—×¦×™× ×ž×©×ª× ×™× ×‘×ª×•×š המקטע במסך שעליו × ×ž×¦× ×”×ž×™×§×•×“.

      \n' + + '\n' + + '

      כדי לסגור תפריט פתוח, תפריט משני פתוח ×ו חלון קופץ, הקש על Esc.\n' + + '\n' + + "

      ×× ×”×ž×™×§×•×“ ×”×•× ×¢×œ החלק 'העליון' של מקטע ×ž×¡×•×™× ×‘×ž×¡×š, הקשה על Esc מבי××” ×’× ×œ×™×¦×™××”\n" + + ' מהניווט במקלדת לחלוטין.

      \n' + + '\n' + + '

      הפעל פריט בתפריט ×ו לחצן בסרגל הכלי×

      \n' + + '\n' + + '

      ×›×שר הפריט הרצוי בתפריט ×ו הלחצן בסרגל ×”×›×œ×™× ×ž×•×“×’×©×™×, הקש על Return, Enter,\n' + + ' ×ו על מקש הרווח כדי להפעיל ×ת הפריט.\n' + + '\n' + + '

      ניווט בחלונות דו-שיח בלי כרטיסיות

      \n' + + '\n' + + '

      בחלונות דו-שיח בלי כרטיסיות, הרכיב ×”×ינטר×קטיבי הר×שון מקבל ×ת המיקוד ×›×שר החלון נפתח.

      \n' + + '\n' + + '

      עבור בין ×¨×›×™×‘×™× ×ינטר××§×˜×™×‘×™×™× ×‘×—×œ×•×Ÿ על ידי הקשה על Tab ×ו Shift+Tab.

      \n' + + '\n' + + '

      ניווט בחלונות דו-שיח ×¢× ×›×¨×˜×™×¡×™×•×ª

      \n' + + '\n' + + '

      בחלונות דו-שיח ×¢× ×›×¨×˜×™×¡×™×•×ª, הלחצן הר×שון בתפריט מקבל ×ת המיקוד ×›×שר החלון נפתח.

      \n' + + '\n' + + '

      עבור בין ×¨×›×™×‘×™× ×ינטר××§×˜×™×‘×™×™× ×‘×—×œ×•×Ÿ על ידי הקשה על Tab ×ו\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      עבור לכרטיסיה ×חרת בחלון על ידי העברת המיקוד לתפריט הכרטיסיות והקשה על החץהמת××™×\n' + + ' כדי לעבור בין הכרטיסיות הזמינות.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/hi.js b/static/js/tinymce/plugins/help/js/i18n/keynav/hi.js new file mode 100644 index 0000000..584085a --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/hi.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.hi', +'

      कीबोरà¥à¤¡ नेविगेशन शà¥à¤°à¥‚ करें

      \n' + + '\n' + + '
      \n' + + '
      मेनà¥à¤¯à¥‚ बार पर फ़ोकस करें
      \n' + + '
      Windows या Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      टूलबार पर फ़ोकस करें
      \n' + + '
      Windows या Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      फ़à¥à¤Ÿà¤° पर फ़ोकस करें
      \n' + + '
      Windows या Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      पà¥à¤°à¤¾à¤¸à¤‚गिक टूलबार पर फ़ोकस करें
      \n' + + '
      Windows, Linux या macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      नेविगेशन पहले UI आइटम पर शà¥à¤°à¥‚ होगा, जिसे हाइलाइट किया जाà¤à¤—ा या पहले आइटम के मामले में फ़à¥à¤Ÿà¤° ततà¥à¤µ पथ में\n' + + ' रेखांकित किया जाà¤à¤—ा।

      \n' + + '\n' + + '

      UI सेकà¥à¤¶à¤¨ के बीच नेविगेट करें

      \n' + + '\n' + + '

      à¤à¤• UI सेकà¥à¤¶à¤¨ से दूसरे सेकà¥à¤¶à¤¨ में जाने के लिà¤, Tab दबाà¤à¤‚।

      \n' + + '\n' + + '

      à¤à¤• UI सेकà¥à¤¶à¤¨ से पिछले सेकà¥à¤¶à¤¨ में जाने के लिà¤, Shift+Tab दबाà¤à¤‚।

      \n' + + '\n' + + '

      इन UI सेकà¥à¤¶à¤¨ का Tab कà¥à¤°à¤® नीचे दिया गया है:\n' + + '\n' + + '

        \n' + + '
      1. मेनà¥à¤¯à¥‚ बार
      2. \n' + + '
      3. पà¥à¤°à¤¤à¥à¤¯à¥‡à¤• टूलबार समूह
      4. \n' + + '
      5. साइडबार
      6. \n' + + '
      7. फ़à¥à¤Ÿà¤° में ततà¥à¤µ पथ
      8. \n' + + '
      9. फ़à¥à¤Ÿà¤° में शबà¥à¤¦ गणना टॉगल बटन
      10. \n' + + '
      11. फ़à¥à¤Ÿà¤° में बà¥à¤°à¤¾à¤‚डिंग लिंक
      12. \n' + + '
      13. फ़à¥à¤Ÿà¤° में संपादक का आकार बदलने का हैंडल
      14. \n' + + '
      \n' + + '\n' + + '

      अगर कोई UI सेकà¥à¤¶à¤¨ मौजूद नहीं है, तो उसे छोड़ दिया जाता है।

      \n' + + '\n' + + '

      अगर फ़à¥à¤Ÿà¤° में कीबोरà¥à¤¡ नेविगेशन फ़ोकस है, और कोई दिखा देने वाला साइडबार नहीं है, तो Shift+Tab दबाने से\n' + + ' फ़ोकस पहले टूलबार समूह पर चला जाता है, पिछले पर नहीं।\n' + + '\n' + + '

      UI सेकà¥à¤¶à¤¨ के भीतर नेविगेट करें

      \n' + + '\n' + + '

      à¤à¤• UI ततà¥à¤µ से दूसरे में जाने के लिठउपयà¥à¤•à¥à¤¤ à¤à¤°à¥‹ कà¥à¤‚जी दबाà¤à¤‚।

      \n' + + '\n' + + '

      बाà¤à¤‚ और दाà¤à¤‚ à¤à¤°à¥‹ कà¥à¤‚जियां

      \n' + + '\n' + + '
        \n' + + '
      • मेनà¥à¤¯à¥‚ बार में मेनà¥à¤¯à¥‚ के बीच ले जाती हैं।
      • \n' + + '
      • मेनà¥à¤¯à¥‚ में à¤à¤• सब-मेनà¥à¤¯à¥‚ खोलें।
      • \n' + + '
      • टूलबार समूह में बटनों के बीच ले जाà¤à¤‚।
      • \n' + + '
      • फ़à¥à¤Ÿà¤° के ततà¥à¤µ पथ में आइटम के बीच ले जाà¤à¤‚।
      • \n' + + '
      \n' + + '\n' + + '

      नीचे और ऊपर à¤à¤°à¥‹ कà¥à¤‚जियां\n' + + '\n' + + '

        \n' + + '
      • मेनà¥à¤¯à¥‚ में मेनà¥à¤¯à¥‚ आइटम के बीच ले जाती हैं।
      • \n' + + '
      • टूलबार पॉप-अप मेनà¥à¤¯à¥‚ में आइटम के बीच ले जाà¤à¤‚।
      • \n' + + '
      \n' + + '\n' + + '

      फ़ोकस वाले UI सेकà¥à¤¶à¤¨ के भीतर à¤à¤°à¥‹ कà¥à¤‚जियां चलाती रहती हैं।

      \n' + + '\n' + + '

      कोई खà¥à¤²à¤¾ मेनà¥à¤¯à¥‚, कोई खà¥à¤²à¤¾ सब-मेनà¥à¤¯à¥‚ या कोई खà¥à¤²à¤¾ पॉप-अप मेनà¥à¤¯à¥‚ बंद करने के लिठEsc कà¥à¤‚जी दबाà¤à¤‚।\n' + + '\n' + + "

      अगर मौजूदा फ़ोकस किसी विशेष UI सेकà¥à¤¶à¤¨ के 'शीरà¥à¤·' पर है, तो Esc कà¥à¤‚जी दबाने से भी\n" + + ' कीबोरà¥à¤¡ नेविगेशन पूरी तरह से बाहर हो जाता है।

      \n' + + '\n' + + '

      मेनà¥à¤¯à¥‚ आइटम या टूलबार बटन निषà¥à¤ªà¤¾à¤¦à¤¿à¤¤ करें

      \n' + + '\n' + + '

      जब वांछित मेनà¥à¤¯à¥‚ आइटम या टूलबार बटन हाइलाइट किया जाता है, तो आइटम को निषà¥à¤ªà¤¾à¤¦à¤¿à¤¤ करने के लिठReturn, Enter,\n' + + ' या Space bar दबाà¤à¤‚।\n' + + '\n' + + '

      गैर-टैब वाले डायलॉग पर नेविगेट करें

      \n' + + '\n' + + '

      गैर-टैब वाले डायलॉग में, डायलॉग खà¥à¤²à¤¨à¥‡ पर पहला इंटरैकà¥à¤Ÿà¤¿à¤µ घटक फ़ोकस लेता है।

      \n' + + '\n' + + '

      Tab or Shift+Tab दबाकर इंटरैकà¥à¤Ÿà¤¿à¤µ डायलॉग घटकों के बीच नेविगेट करें।

      \n' + + '\n' + + '

      टैब किठगठडायलॉग पर नेविगेट करें

      \n' + + '\n' + + '

      टैब किठगठडायलॉग में, डायलॉग खà¥à¤²à¤¨à¥‡ पर टैब मेनà¥à¤¯à¥‚ में पहला बटन फ़ोकस लेता है।

      \n' + + '\n' + + '

      इस डायलॉग टैब के इंटरैकà¥à¤Ÿà¤¿à¤µ घटकों के बीच नेविगेट करने के लिठTab या\n' + + ' Shift+Tab दबाà¤à¤‚।

      \n' + + '\n' + + '

      टैब मेनà¥à¤¯à¥‚ को फ़ोकस देकर और फिर उपलबà¥à¤§ टैब में के बीच जाने के लिठउपयà¥à¤•à¥à¤¤ à¤à¤°à¥‹\n' + + ' कà¥à¤‚जी दबाकर दूसरे डायलॉग टैब पर सà¥à¤µà¤¿à¤š करें।

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/hr.js b/static/js/tinymce/plugins/help/js/i18n/keynav/hr.js new file mode 100644 index 0000000..3c1f833 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/hr.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.hr', +'

      PoÄetak navigacije na tipkovnici

      \n' + + '\n' + + '
      \n' + + '
      Fokusiranje trake izbornika
      \n' + + '
      Windows ili Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokusiranje alatne trake
      \n' + + '
      Windows ili Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokusiranje podnožja
      \n' + + '
      Windows ili Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokusiranje kontekstne alatne trake
      \n' + + '
      Windows, Linux ili macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigacija će zapoÄeti kod prve stavke na korisniÄkom suÄelju, koja će biti istaknuta ili podcrtana ako se radi o prvoj stavci u\n' + + ' putu elementa u podnožju.

      \n' + + '\n' + + '

      Navigacija izmeÄ‘u dijelova korisniÄkog suÄelja

      \n' + + '\n' + + '

      Za pomicanje s jednog dijela korisniÄkog suÄelja na drugi pritisnite tabulator.

      \n' + + '\n' + + '

      Za pomicanje s jednog dijela korisniÄkog suÄelja na prethodni pritisnite Shift + tabulator.

      \n' + + '\n' + + '

      Ovo je redoslijed pomicanja tabulatora po dijelovima korisniÄkog suÄelja:\n' + + '\n' + + '

        \n' + + '
      1. Traka izbornika
      2. \n' + + '
      3. PojedinaÄne grupe na alatnoj traci
      4. \n' + + '
      5. BoÄna traka
      6. \n' + + '
      7. Put elemenata u podnožju
      8. \n' + + '
      9. Gumb za pomicanje po broju rijeÄi u podnožju
      10. \n' + + '
      11. Veza na brand u podnožju
      12. \n' + + '
      13. ZnaÄajka za promjenu veliÄine alata za ureÄ‘ivanje u podnožju
      14. \n' + + '
      \n' + + '\n' + + '

      Ako neki dio korisniÄkog suÄelja nije naveden, on se preskaÄe.

      \n' + + '\n' + + '

      Ako u podnožju postoji fokus za navigaciju na tipkovnici, a nema vidljive boÄne trake, pritiskom na Shift + tabulator\n' + + ' fokus se prebacuje na prvu skupinu na alatnoj traci, ne na zadnju.\n' + + '\n' + + '

      Navigacija unutar dijelova korisniÄkog suÄelja

      \n' + + '\n' + + '

      Za pomicanje s jednog elementa korisniÄkog suÄelja na drugi pritisnite tipku s odgovarajućom strelicom.

      \n' + + '\n' + + '

      Tipke s lijevom i desnom strelicom

      \n' + + '\n' + + '
        \n' + + '
      • služe za pomicanje izmeÄ‘u izbornika na alatnoj traci.
      • \n' + + '
      • otvaraju podizbornik unutar izbornika.
      • \n' + + '
      • služe za pomicanje izmeÄ‘u gumba unutar skupina na alatnoj traci.
      • \n' + + '
      • služe za pomicanje izmeÄ‘u stavki na elementu puta u podnožju.
      • \n' + + '
      \n' + + '\n' + + '

      Tipke s donjom i gornjom strelicom\n' + + '\n' + + '

        \n' + + '
      • služe za pomicanje izmeÄ‘u stavki unutar izbornika.
      • \n' + + '
      • služe za pomicanje izmeÄ‘u stavki na alatnoj traci skoÄnog izbornika.
      • \n' + + '
      \n' + + '\n' + + '

      Tipkama strelica kružno se pomiÄete unutar dijela korisniÄkog suÄelja koji je u fokusu.

      \n' + + '\n' + + '

      Za zatvaranje otvorenog izbornika, otvorenog podizbornika ili otvorenog skoÄnog izbornika pritisnite tipku Esc.\n' + + '\n' + + '

      Ako je fokus trenutaÄno postavljen na vrh pojedinaÄnog dijela korisniÄkog suÄelja, pritiskom na tipku Esc takoÄ‘er\n' + + ' u potpunosti zatvarate navigaciju na tipkovnici.

      \n' + + '\n' + + '

      Izvršavanje radnji putem stavki izbornika ili gumba na alatnoj traci

      \n' + + '\n' + + '

      Nakon što se istakne stavka izbornika ili gumb na alatnoj traci s radnjom koju želite izvršiti, pritisnite tipku Return, Enter\n' + + ' ili razmak da biste pokrenuli željenu radnju.\n' + + '\n' + + '

      Navigacija dijaloškim okvirima izvan kartica

      \n' + + '\n' + + '

      Prilikom otvaranja dijaloških okvira izvan kartica fokus se nalazi na prvoj interaktivnoj komponenti.

      \n' + + '\n' + + '

      Navigaciju između interaktivnih dijaloških komponenata vršite pritiskom na tabulator ili Shift + tabulator.

      \n' + + '\n' + + '

      Navigacija dijaloškim okvirima u karticama

      \n' + + '\n' + + '

      Prilikom otvaranja dijaloških okvira u karticama fokus se nalazi na prvom gumbu u izborniku unutar kartice.

      \n' + + '\n' + + '

      Navigaciju između interaktivnih komponenata dijaloškog okvira u kartici vršite pritiskom na tabulator ili\n' + + ' Shift + tabulator.

      \n' + + '\n' + + '

      Na karticu s drugim dijaloškim okvirom možete se prebaciti tako da stavite fokus na izbornik kartice pa pritisnete tipku s odgovarajućom strelicom\n' + + ' za kružno pomicanje između dostupnih kartica.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/hu_HU.js b/static/js/tinymce/plugins/help/js/i18n/keynav/hu_HU.js new file mode 100644 index 0000000..2791e15 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/hu_HU.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.hu_HU', +'

      Billentyűzetes navigáció indítása

      \n' + + '\n' + + '
      \n' + + '
      Fókusz a menüsávra
      \n' + + '
      Windows és Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fókusz az eszköztárra
      \n' + + '
      Windows és Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fókusz a láblécre
      \n' + + '
      Windows és Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fókusz egy környezetfüggő eszköztárra
      \n' + + '
      Windows, Linux és macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      A navigáció az első felhasználói felületi elemnél kezdődik, amelyet a rendszer kiemel, illetve aláhúz, amennyiben az az első elem\n' + + ' a lábléc elemútvonalán.

      \n' + + '\n' + + '

      Navigálás a felhasználói felület szakaszai között

      \n' + + '\n' + + '

      A felhasználói felület következő szakaszára váltáshoz nyomja meg a Tab billentyűt.

      \n' + + '\n' + + '

      A felhasználói felület előző szakaszára váltáshoz nyomja meg a Shift+Tab billentyűt.

      \n' + + '\n' + + '

      A Tab billentyűvel a felhasználói felület szakaszai között a következő sorrendben vált:\n' + + '\n' + + '

        \n' + + '
      1. Menüsáv
      2. \n' + + '
      3. Az egyes eszköztárcsoportok
      4. \n' + + '
      5. Oldalsáv
      6. \n' + + '
      7. Elemútvonal a láblécen
      8. \n' + + '
      9. Szószámátkapcsoló gomb a láblécen
      10. \n' + + '
      11. Márkalink a láblécen
      12. \n' + + '
      13. Szerkesztő átméretezési fogópontja a láblécen
      14. \n' + + '
      \n' + + '\n' + + '

      Ha a felhasználói felület valamelyik eleme nincs jelen, a rendszer kihagyja.

      \n' + + '\n' + + '

      Ha a billentyűzetes navigáció fókusza a láblécen van, és nincs látható oldalsáv, a Shift+Tab\n' + + ' billentyűkombináció lenyomásakor az első eszköztárcsoportra ugrik a fókusz, nem az utolsóra.\n' + + '\n' + + '

      Navigálás a felhasználói felület szakaszain belül

      \n' + + '\n' + + '

      A felhasználói felület következő elemére váltáshoz nyomja meg a megfelelő nyílbillentyűt.

      \n' + + '\n' + + '

      A bal és a jobb nyílgomb

      \n' + + '\n' + + '
        \n' + + '
      • a menüsávban a menük között vált.
      • \n' + + '
      • a menükben megnyit egy almenüt.
      • \n' + + '
      • az eszköztárcsoportban a gombok között vált.
      • \n' + + '
      • a lábléc elemútvonalán az elemek között vált.
      • \n' + + '
      \n' + + '\n' + + '

      A le és a fel nyílgomb\n' + + '\n' + + '

        \n' + + '
      • a menükben a menüpontok között vált.
      • \n' + + '
      • az eszköztár elÅ‘ugró menüjében az elemek között vált.
      • \n' + + '
      \n' + + '\n' + + '

      A nyílbillentyűk lenyomásával körkörösen lépkedhet a fókuszban lévő felhasználói felületi szakasz elemei között.

      \n' + + '\n' + + '

      A megnyitott menüket, almenüket és előugró menüket az Esc billentyűvel zárhatja be.\n' + + '\n' + + '

      Ha a fókusz az aktuális felületi elem „felső†részén van, az Esc billentyűvel az egész\n' + + ' billentyűzetes navigációból kilép.

      \n' + + '\n' + + '

      Menüpont vagy eszköztárgomb aktiválása

      \n' + + '\n' + + '

      Amikor a kívánt menüelem vagy eszköztárgomb van kijelölve, nyomja meg a Return, az Enter\n' + + ' vagy a Szóköz billentyűt az adott elem vagy gomb aktiválásához.\n' + + '\n' + + '

      Navigálás a lapokkal nem rendelkező párbeszédablakokban

      \n' + + '\n' + + '

      A lapokkal nem rendelkező párbeszédablakokban az első interaktív összetevő kapja a fókuszt, amikor a párbeszédpanel megnyílik.

      \n' + + '\n' + + '

      A párbeszédpanelek interaktív összetevői között a Tab vagy a Shift+Tab billentyűvel navigálhat.

      \n' + + '\n' + + '

      Navigálás a lapokkal rendelkező párbeszédablakokban

      \n' + + '\n' + + '

      A lapokkal rendelkező párbeszédablakokban a lapmenü első gombja kapja a fókuszt, amikor a párbeszédpanel megnyílik.

      \n' + + '\n' + + '

      A párbeszédpanel e lapjának interaktív összetevői között a Tab vagy\n' + + ' Shift+Tab billentyűvel navigálhat.

      \n' + + '\n' + + '

      A párbeszédablak másik lapjára úgy léphet, hogy a fókuszt a lapmenüre állítja, majd lenyomja a megfelelő nyílbillentyűt\n' + + ' a rendelkezésre álló lapok közötti lépkedéshez.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/id.js b/static/js/tinymce/plugins/help/js/i18n/keynav/id.js new file mode 100644 index 0000000..03cf417 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/id.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.id', +'

      Memulai navigasi keyboard

      \n' + + '\n' + + '
      \n' + + '
      Fokus pada bilah Menu
      \n' + + '
      Windows atau Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokus pada Bilah Alat
      \n' + + '
      Windows atau Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokus pada footer
      \n' + + '
      Windows atau Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokus pada bilah alat kontekstual
      \n' + + '
      Windows, Linux, atau macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigasi akan dimulai dari item pertama UI, yang akan disorot atau digarisbawahi di\n' + + ' alur elemen Footer.

      \n' + + '\n' + + '

      Berpindah antar-bagian UI

      \n' + + '\n' + + '

      Untuk berpindah dari satu bagian UI ke bagian berikutnya, tekan Tab.

      \n' + + '\n' + + '

      Untuk berpindah dari satu bagian UI ke bagian sebelumnya, tekan Shift+Tab.

      \n' + + '\n' + + '

      Urutan Tab bagian-bagian UI ini adalah:\n' + + '\n' + + '

        \n' + + '
      1. Bilah menu
      2. \n' + + '
      3. Tiap grup bilah alat
      4. \n' + + '
      5. Bilah sisi
      6. \n' + + '
      7. Alur elemen di footer
      8. \n' + + '
      9. Tombol aktifkan/nonaktifkan jumlah kata di footer
      10. \n' + + '
      11. Tautan merek di footer
      12. \n' + + '
      13. Pengatur pengubahan ukuran editor di footer
      14. \n' + + '
      \n' + + '\n' + + '

      Jika suatu bagian UI tidak ada, bagian tersebut dilewati.

      \n' + + '\n' + + '

      Jika fokus navigasi keyboard ada pada footer, tetapi tidak ada bilah sisi yang terlihat, menekan Shift+Tab\n' + + ' akan memindahkan fokus ke grup bilah alat pertama, bukan yang terakhir.\n' + + '\n' + + '

      Berpindah di dalam bagian-bagian UI

      \n' + + '\n' + + '

      Untuk berpindah dari satu elemen UI ke elemen berikutnya, tekan tombol Panah yang sesuai.

      \n' + + '\n' + + '

      Tombol panah Kiri dan Kanan untuk

      \n' + + '\n' + + '
        \n' + + '
      • berpindah-pindah antar-menu di dalam bilah menu.
      • \n' + + '
      • membuka sub-menu di dalam menu.
      • \n' + + '
      • berpindah-pindah antar-tombol di dalam grup bilah alat.
      • \n' + + '
      • berpindah-pindah antar-item di dalam alur elemen footer.
      • \n' + + '
      \n' + + '\n' + + '

      Tombol panah Bawah dan Atas untuk\n' + + '\n' + + '

        \n' + + '
      • berpindah-pindah antar-item menu di dalam menu.
      • \n' + + '
      • berpindah-pindah antar-item di dalam menu pop-up bilah alat.
      • \n' + + '
      \n' + + '\n' + + '

      Tombol Panah hanya bergerak di dalam bagian UI yang difokuskan.

      \n' + + '\n' + + '

      Untuk menutup menu, sub-menu, atau menu pop-up yang terbuka, tekan tombol Esc.\n' + + '\n' + + '

      Jika fokus sedang berada di ‘atas’ bagian UI tertentu, menekan tombol Esc juga dapat mengeluarkan fokus\n' + + ' dari seluruh navigasi keyboard.

      \n' + + '\n' + + '

      Menjalankan item menu atau tombol bilah alat

      \n' + + '\n' + + '

      Jika item menu atau tombol bilah alat yang diinginkan tersorot, tekan Return, Enter,\n' + + ' atau Spasi untuk menjalankan item.\n' + + '\n' + + '

      Berpindah dalam dialog tanpa tab

      \n' + + '\n' + + '

      Dalam dialog tanpa tab, fokus diarahkan pada komponen interaktif pertama saat dialog terbuka.

      \n' + + '\n' + + '

      Berpindah di antara komponen dalam dialog interaktif dengan menekan Tab atau Shift+Tab.

      \n' + + '\n' + + '

      Berpindah dalam dialog dengan tab

      \n' + + '\n' + + '

      Dalam dialog yang memiliki tab, fokus diarahkan pada tombol pertama di dalam menu saat dialog terbuka.

      \n' + + '\n' + + '

      Berpindah di antara komponen-komponen interaktif pada tab dialog ini dengan menekan Tab atau\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Beralih ke tab dialog lain dengan mengarahkan fokus pada menu tab lalu tekan tombol Panah\n' + + ' yang sesuai untuk berpindah ke berbagai tab yang tersedia.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/it.js b/static/js/tinymce/plugins/help/js/i18n/keynav/it.js new file mode 100644 index 0000000..450a34a --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/it.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.it', +'

      Iniziare la navigazione tramite tastiera

      \n' + + '\n' + + '
      \n' + + '
      Impostare lo stato attivo per la barra dei menu
      \n' + + '
      Windows o Linux: ALT+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Impostare lo stato attivo per la barra degli strumenti
      \n' + + '
      Windows o Linux: ALT+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Impostare lo stato attivo per il piè di pagina
      \n' + + '
      Windows o Linux: ALT+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Impostare lo stato attivo per la barra degli strumenti contestuale
      \n' + + '
      Windows, Linux o macOS: CTRL+F9\n' + + '
      \n' + + '\n' + + "

      La navigazione inizierà dalla prima voce dell'interfaccia utente, che sarà evidenziata o sottolineata nel caso della prima voce\n" + + " nel percorso dell'elemento del piè di pagina.

      \n" + + '\n' + + "

      Navigare tra le sezioni dell'interfaccia utente

      \n" + + '\n' + + "

      Per passare da una sezione dell'interfaccia utente alla successiva, premere TAB.

      \n" + + '\n' + + "

      Per passare da una sezione dell'interfaccia utente alla precedente, premere MAIUSC+TAB.

      \n" + + '\n' + + "

      L'ordine di tabulazione di queste sezioni dell'interfaccia utente è:\n" + + '\n' + + '

        \n' + + '
      1. Barra dei menu
      2. \n' + + '
      3. Ogni gruppo di barre degli strumenti
      4. \n' + + '
      5. Barra laterale
      6. \n' + + "
      7. Percorso dell'elemento nel piè di pagina
      8. \n" + + '
      9. Pulsante di attivazione/disattivazione del conteggio delle parole nel piè di pagina
      10. \n' + + '
      11. Collegamento al marchio nel piè di pagina
      12. \n' + + "
      13. Quadratino di ridimensionamento dell'editor nel piè di pagina
      14. \n" + + '
      \n' + + '\n' + + "

      Se una sezione dell'interfaccia utente non è presente, viene saltata.

      \n" + + '\n' + + '

      Se il piè di pagina ha lo stato attivo per la navigazione tramite tastiera e non è presente alcuna barra laterale visibile, premendo MAIUSC+TAB\n' + + " si sposta lo stato attivo sul primo gruppo di barre degli strumenti, non sull'ultimo.\n" + + '\n' + + "

      Navigare all'interno delle sezioni dell'interfaccia utente

      \n" + + '\n' + + "

      Per passare da un elemento dell'interfaccia utente al successivo, premere il tasto freccia appropriato.

      \n" + + '\n' + + '

      I tasti freccia Sinistra e Destra

      \n' + + '\n' + + '
        \n' + + '
      • consentono di spostarsi tra i menu della barra dei menu.
      • \n' + + '
      • aprono un sottomenu in un menu.
      • \n' + + '
      • consentono di spostarsi tra i pulsanti di un gruppo di barre degli strumenti.
      • \n' + + "
      • consentono di spostarsi tra le voci nel percorso dell'elemento del piè di pagina.
      • \n" + + '
      \n' + + '\n' + + '

      I tasti freccia Giù e Su\n' + + '\n' + + '

        \n' + + '
      • consentono di spostarsi tra le voci di un menu.
      • \n' + + '
      • consentono di spostarsi tra le voci di un menu a comparsa della barra degli strumenti.
      • \n' + + '
      \n' + + '\n' + + "

      I tasti freccia consentono di spostarsi all'interno della sezione dell'interfaccia utente con stato attivo.

      \n" + + '\n' + + '

      Per chiudere un menu aperto, un sottomenu aperto o un menu a comparsa aperto, premere il tasto ESC.\n' + + '\n' + + "

      Se lo stato attivo corrente si trova nella parte superiore di una particolare sezione dell'interfaccia utente, premendo il tasto ESC si esce\n" + + ' completamente dalla navigazione tramite tastiera.

      \n' + + '\n' + + '

      Eseguire una voce di menu o un pulsante della barra degli strumenti

      \n' + + '\n' + + '

      Quando la voce di menu o il pulsante della barra degli strumenti desiderati sono evidenziati, premere il tasto diritorno a capo, il tasto Invio\n' + + ' o la barra spaziatrice per eseguirli.\n' + + '\n' + + '

      Navigare nelle finestre di dialogo non a schede

      \n' + + '\n' + + "

      Nelle finestre di dialogo non a schede, all'apertura della finestra di dialogo diventa attivo il primo componente interattivo.

      \n" + + '\n' + + '

      Per spostarsi tra i componenti interattivi della finestra di dialogo, premere TAB o MAIUSC+TAB.

      \n' + + '\n' + + '

      Navigare nelle finestre di dialogo a schede

      \n' + + '\n' + + "

      Nelle finestre di dialogo a schede, all'apertura della finestra di dialogo diventa attivo il primo pulsante del menu della scheda.

      \n" + + '\n' + + '

      Per spostarsi tra i componenti interattivi di questa scheda della finestra di dialogo, premere TAB o\n' + + ' MAIUSC+TAB.

      \n' + + '\n' + + "

      Per passare a un'altra scheda della finestra di dialogo, attivare il menu della scheda e premere il tasto freccia\n" + + ' appropriato per scorrere le schede disponibili.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ja.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ja.js new file mode 100644 index 0000000..19febc7 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ja.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ja', +'

      キーボード ナビゲーションã®é–‹å§‹

      \n' + + '\n' + + '
      \n' + + '
      メニュー ãƒãƒ¼ã‚’フォーカス
      \n' + + '
      Windows ã¾ãŸã¯ Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      ツール ãƒãƒ¼ã‚’フォーカス
      \n' + + '
      Windows ã¾ãŸã¯ Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      フッターをフォーカス
      \n' + + '
      Windows ã¾ãŸã¯ Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      コンテキスト ツール ãƒãƒ¼ã‚’フォーカス
      \n' + + '
      Windowsã€Linux ã¾ãŸã¯ macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      ナビゲーションã¯æœ€åˆã® UI é …ç›®ã‹ã‚‰é–‹å§‹ã•ã‚Œã€å¼·èª¿è¡¨ç¤ºã•ã‚Œã‚‹ã‹ã€ãƒ•ãƒƒã‚¿ãƒ¼ã®è¦ç´ ãƒ‘スã«ã‚る最åˆã®é …ç›®ã®å ´åˆã¯\n' + + ' 下線ãŒå¼•ã‹ã‚Œã¾ã™ã€‚

      \n' + + '\n' + + '

      UI セクション間ã®ç§»å‹•

      \n' + + '\n' + + '

      次㮠UI セクションã«ç§»å‹•ã™ã‚‹ã«ã¯ã€Tab を押ã—ã¾ã™ã€‚

      \n' + + '\n' + + '

      å‰ã® UI セクションã«ç§»å‹•ã™ã‚‹ã«ã¯ã€Shift+Tab を押ã—ã¾ã™ã€‚

      \n' + + '\n' + + '

      ã“れら㮠UI セクション㮠Tab ã®é †åº:\n' + + '\n' + + '

        \n' + + '
      1. メニュー ãƒãƒ¼
      2. \n' + + '
      3. å„ツール ãƒãƒ¼ グループ
      4. \n' + + '
      5. サイド ãƒãƒ¼
      6. \n' + + '
      7. フッターã®è¦ç´ ãƒ‘ス
      8. \n' + + '
      9. フッターã®å˜èªžæ•°åˆ‡ã‚Šæ›¿ãˆãƒœã‚¿ãƒ³
      10. \n' + + '
      11. フッターã®ãƒ–ランド リンク
      12. \n' + + '
      13. フッターã®ã‚¨ãƒ‡ã‚£ã‚¿ãƒ¼ サイズ変更ãƒãƒ³ãƒ‰ãƒ«
      14. \n' + + '
      \n' + + '\n' + + '

      UI セクションãŒå­˜åœ¨ã—ãªã„å ´åˆã¯ã€ã‚¹ã‚­ãƒƒãƒ—ã•ã‚Œã¾ã™ã€‚

      \n' + + '\n' + + '

      フッターã«ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ ナビゲーション フォーカスãŒã‚ã‚Šã€è¡¨ç¤ºå¯èƒ½ãªã‚µã‚¤ãƒ‰ ãƒãƒ¼ãŒãªã„å ´åˆã€Shift+Tab を押ã™ã¨ã€\n' + + ' フォーカスãŒæœ€å¾Œã§ã¯ãªã最åˆã®ãƒ„ール ãƒãƒ¼ グループã«ç§»å‹•ã—ã¾ã™ã€‚\n' + + '\n' + + '

      UI セクション内ã®ç§»å‹•

      \n' + + '\n' + + '

      次㮠UI è¦ç´ ã«ç§»å‹•ã™ã‚‹ã«ã¯ã€é©åˆ‡ãªçŸ¢å°ã‚­ãƒ¼ã‚’押ã—ã¾ã™ã€‚

      \n' + + '\n' + + '

      左矢å°ã¨å³çŸ¢å°ã®ã‚­ãƒ¼

      \n' + + '\n' + + '
        \n' + + '
      • メニュー ãƒãƒ¼ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼é–“ã§ç§»å‹•ã—ã¾ã™ã€‚
      • \n' + + '
      • メニュー内ã®ã‚µãƒ–メニューを開ãã¾ã™ã€‚
      • \n' + + '
      • ツール ãƒãƒ¼ グループã®ãƒœã‚¿ãƒ³é–“ã§ç§»å‹•ã—ã¾ã™ã€‚
      • \n' + + '
      • フッターã®è¦ç´ ãƒ‘スã®é …目間ã§ç§»å‹•ã—ã¾ã™ã€‚
      • \n' + + '
      \n' + + '\n' + + '

      下矢å°ã¨ä¸ŠçŸ¢å°ã®ã‚­ãƒ¼\n' + + '\n' + + '

        \n' + + '
      • メニュー内ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼é …目間ã§ç§»å‹•ã—ã¾ã™ã€‚
      • \n' + + '
      • ツール ãƒãƒ¼ ãƒãƒƒãƒ—アップ メニュー内ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼é …目間ã§ç§»å‹•ã—ã¾ã™ã€‚
      • \n' + + '
      \n' + + '\n' + + '

      矢å°ã‚­ãƒ¼ã§ã€ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã•ã‚ŒãŸ UI セクション内ã§å¾ªç’°ã—ã¾ã™ã€‚

      \n' + + '\n' + + '

      é–‹ã„ãŸãƒ¡ãƒ‹ãƒ¥ãƒ¼ã€é–‹ã„ãŸã‚µãƒ–メニューã€é–‹ã„ãŸãƒãƒƒãƒ—アップ メニューを閉ã˜ã‚‹ã«ã¯ã€Esc キーを押ã—ã¾ã™ã€‚\n' + + '\n' + + '

      ç¾åœ¨ã®ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ãŒç‰¹å®šã® UI セクションã®ã€Œä¸€ç•ªä¸Šã€ã«ã‚ã‚‹å ´åˆã€Esc キーを押ã™ã¨\n' + + ' キーボード ナビゲーションも完全ã«é–‰ã˜ã‚‰ã‚Œã¾ã™ã€‚

      \n' + + '\n' + + '

      メニュー項目ã¾ãŸã¯ãƒ„ール ãƒãƒ¼ ボタンã®å®Ÿè¡Œ

      \n' + + '\n' + + '

      目的ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼é …目やツール ãƒãƒ¼ ボタンãŒå¼·èª¿è¡¨ç¤ºã•ã‚Œã¦ã„ã‚‹å ´åˆã€ãƒªã‚¿ãƒ¼ãƒ³ã€Enterã€\n' + + ' ã¾ãŸã¯ã‚¹ãƒšãƒ¼ã‚¹ キーを押ã—ã¦é …目を実行ã—ã¾ã™ã€‚\n' + + '\n' + + '

      タブã®ãªã„ダイアログã®ç§»å‹•

      \n' + + '\n' + + '

      タブã®ãªã„ダイアログã§ã¯ã€ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ãŒé–‹ãã¨æœ€åˆã®å¯¾è©±åž‹ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã•ã‚Œã¾ã™ã€‚

      \n' + + '\n' + + '

      Tab ã¾ãŸã¯ Shift+Tab を押ã—ã¦ã€å¯¾è©±åž‹ãƒ€ã‚¤ã‚¢ãƒ­ã‚° コンãƒãƒ¼ãƒãƒ³ãƒˆé–“ã§ç§»å‹•ã—ã¾ã™ã€‚

      \n' + + '\n' + + '

      タブ付ãダイアログã®ç§»å‹•

      \n' + + '\n' + + '

      タブ付ãダイアログã§ã¯ã€ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ãŒé–‹ãã¨ã‚¿ãƒ– メニューã®æœ€åˆã®ãƒœã‚¿ãƒ³ãŒãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã•ã‚Œã¾ã™ã€‚

      \n' + + '\n' + + '

      Tab ã¾ãŸã¯\n' + + ' Shift+Tab を押ã—ã¦ã€ã“ã®ãƒ€ã‚¤ã‚¢ãƒ­ã‚° タブã®å¯¾è©±åž‹ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆé–“ã§ç§»å‹•ã—ã¾ã™ã€‚

      \n' + + '\n' + + '

      タブ メニューをフォーカスã—ã¦ã‹ã‚‰é©åˆ‡ãªçŸ¢å°ã‚­ãƒ¼ã‚’押ã—ã¦è¡¨ç¤ºå¯èƒ½ãªã‚¿ãƒ–を循環ã—ã¦ã€\n' + + ' 別ã®ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã«åˆ‡ã‚Šæ›¿ãˆã¾ã™ã€‚

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/kk.js b/static/js/tinymce/plugins/help/js/i18n/keynav/kk.js new file mode 100644 index 0000000..dd6370f --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/kk.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.kk', +'

      Пернетақта навигациÑÑын баÑтау

      \n' + + '\n' + + '
      \n' + + '
      Мәзір жолағын фокуÑтау
      \n' + + '
      Windows немеÑе Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Құралдар тақтаÑын фокуÑтау
      \n' + + '
      Windows немеÑе Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Төменгі деректемені фокуÑтау
      \n' + + '
      Windows немеÑе Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Мәтінмәндік құралдар тақтаÑын фокуÑтау
      \n' + + '
      Windows, Linux немеÑе macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      ÐÐ°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ Ð±Ó©Ð»ÐµÐºÑ‚ÐµÐ»ÐµÑ‚Ñ–Ð½ немеÑе Төменгі деректеме Ñлементінің жолындағы бірінші Ñлемент жағдайында аÑÑ‚Ñ‹ Ñызылатын\n' + + ' бірінші ПИ Ñлементінен баÑталады.

      \n' + + '\n' + + '

      ПИ бөлімдері араÑында навигациÑлау

      \n' + + '\n' + + '

      Бір ПИ бөлімінен келеÑÑ–Ñіне өту үшін Tab пернеÑін баÑыңыз.

      \n' + + '\n' + + '

      Бір ПИ бөлімінен алдыңғыÑына өту үшін Shift+Tab пернеÑін баÑыңыз.

      \n' + + '\n' + + '

      ОÑÑ‹ ПИ бөлімдерінің Tab реті:\n' + + '\n' + + '

        \n' + + '
      1. Мәзір жолағы
      2. \n' + + '
      3. Әрбір құралдар тақтаÑÑ‹ тобы
      4. \n' + + '
      5. Бүйірлік жолақ
      6. \n' + + '
      7. Төменгі деректемедегі Ñлемент жолы
      8. \n' + + '
      9. Төменгі деректемедегі Ñөздер Ñанын ауыÑтыру түймеÑÑ–
      10. \n' + + '
      11. Төменгі деректемедегі брендингтік Ñілтеме
      12. \n' + + '
      13. Төменгі деректемедегі редактор өлшемін өзгерту тұтқаÑÑ‹
      14. \n' + + '
      \n' + + '\n' + + '

      ПИ бөлімі көрÑетілмеÑе, ол өткізіп жіберіледі.

      \n' + + '\n' + + '

      Төменгі деректемеде пернетақта навигациÑÑының фокуÑÑ‹ болÑа және бүйірлік жолақ көрінбеÑе, Shift+Tab тіркеÑімін баÑу әрекеті\n' + + ' фокуÑÑ‚Ñ‹ ÑоңғыÑÑ‹ емеÑ, бірінші құралдар тақтаÑÑ‹ тобына жылжытады.\n' + + '\n' + + '

      ПИ бөлімдерінде навигациÑлау

      \n' + + '\n' + + '

      Бір ПИ Ñлементінен келеÑÑ–Ñіне өту үшін Arrow (КөрÑеткі) пернеÑін баÑыңыз.

      \n' + + '\n' + + '

      Left (Сол жақ) және Right (Оң жақ) көрÑеткі пернелері

      \n' + + '\n' + + '
        \n' + + '
      • мәзір жолағындағы мәзірлер араÑында жылжыту.
      • \n' + + '
      • мәзірде ішкі мәзірді ашу.
      • \n' + + '
      • құралдар тақтаÑÑ‹ тобындағы түймелер араÑында жылжыту.
      • \n' + + '
      • төменгі деректеме Ñлементінің жолындағы Ñлементтер араÑында жылжыту.
      • \n' + + '
      \n' + + '\n' + + '

      Down (Төмен) және Up (Жоғары) көрÑеткі пернелері\n' + + '\n' + + '

        \n' + + '
      • мәзірдегі мәзір Ñлементтері араÑында жылжыту.
      • \n' + + '
      • құралдар тақтаÑының ашылмалы мәзіріндегі мәзір Ñлементтері араÑында жылжыту.
      • \n' + + '
      \n' + + '\n' + + '

      ФокуÑталған ПИ бөліміндегі Arrow (КөрÑеткі) пернелерінің циклі.

      \n' + + '\n' + + '

      Ðшық мәзірді жабу үшін ішкі мәзірді ашып немеÑе ашылмалы мәзірді ашып, Esc пернеÑін баÑыңыз.\n' + + '\n' + + '

      Ðғымдағы Ñ„Ð¾ÐºÑƒÑ Ð±ÐµÐ»Ð³Ñ–Ð»Ñ– бір ПИ бөлімінің «үÑтінде» болÑа, Esc пернеÑін баÑу әрекеті пернетақта\n' + + ' навигациÑÑын толығымен жабады.

      \n' + + '\n' + + '

      Мәзір Ñлементін немеÑе құралдар тақтаÑÑ‹ түймеÑін орындау

      \n' + + '\n' + + '

      Қажетті мәзір Ñлементі немеÑе құралдар тақтаÑÑ‹ түймеÑÑ– бөлектелген кезде, Ñлементті орындау үшін Return (Қайтару), Enter (Енгізу)\n' + + ' немеÑе Space bar (Ð‘Ð¾Ñ Ð¾Ñ€Ñ‹Ð½) пернеÑін баÑыңыз.\n' + + '\n' + + '

      Белгіленбеген диалог терезелерін навигациÑлау

      \n' + + '\n' + + '

      Белгіленбеген диалог терезелерінде диалог терезеÑÑ– ашылған кезде бірінші интерактивті Ò›Ò±Ñ€Ð°Ð¼Ð´Ð°Ñ Ñ„Ð¾ÐºÑƒÑталады.

      \n' + + '\n' + + '

      Tab немеÑе Shift+Tab пернеÑін баÑу арқылы интерактивті диалог терезеÑінің құрамдаÑтары араÑында навигациÑлаңыз.

      \n' + + '\n' + + '

      Белгіленген диалог терезелерін навигациÑлау

      \n' + + '\n' + + '

      Белгіленген диалог терезелерінде диалог терезеÑÑ– ашылған кезде қойынды мәзіріндегі бірінші түйме фокуÑталады.

      \n' + + '\n' + + '

      Tab немеÑе\n' + + ' Shift+Tab пернеÑін баÑу арқылы оÑÑ‹ диалог терезеÑÑ– қойындыÑының интерактивті құрамдаÑтары араÑында навигациÑлаңыз.

      \n' + + '\n' + + '

      Қойынды мәзірінің фокуÑын беру арқылы баÑқа диалог терезеÑінің қойындыÑына ауыÑып, тиіÑÑ‚Ñ– Arrow (КөрÑеткі)\n' + + ' пернеÑін баÑу арқылы қолжетімді қойындылар араÑында айналдыруға болады.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ko_KR.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ko_KR.js new file mode 100644 index 0000000..073592c --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ko_KR.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ko_KR', +'

      키보드 íƒìƒ‰ 시작

      \n' + + '\n' + + '
      \n' + + '
      메뉴 ëª¨ìŒ í¬ì»¤ìŠ¤ 표시
      \n' + + '
      Windows ë˜ëŠ” Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      ë„구 ëª¨ìŒ í¬ì»¤ìŠ¤ 표시
      \n' + + '
      Windows ë˜ëŠ” Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      푸터 í¬ì»¤ìŠ¤ 표시
      \n' + + '
      Windows ë˜ëŠ” Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      컨í…스트 ë„구 모ìŒì— í¬ì»¤ìŠ¤ 표시
      \n' + + '
      Windows, Linux ë˜ëŠ” macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      첫 번째 UI 항목ì—ì„œ íƒìƒ‰ì´ 시작ë˜ë©°, ì´ë•Œ 첫 번째 í•­ëª©ì´ ê°•ì¡° 표시ë˜ê±°ë‚˜ 푸터 요소 ê²½ë¡œì— ìžˆëŠ”\n' + + ' 경우 밑줄 표시ë©ë‹ˆë‹¤.

      \n' + + '\n' + + '

      UI 섹션 ê°„ íƒìƒ‰

      \n' + + '\n' + + '

      í•œ UI 섹션ì—ì„œ ë‹¤ìŒ UI 섹션으로 ì´ë™í•˜ë ¤ë©´ Tab(탭)ì„ ëˆ„ë¦…ë‹ˆë‹¤.

      \n' + + '\n' + + '

      í•œ UI 섹션ì—ì„œ ì´ì „ UI 섹션으로 ëŒì•„가려면 Shift+Tab(시프트+탭)ì„ ëˆ„ë¦…ë‹ˆë‹¤.

      \n' + + '\n' + + '

      ì´ UI ì„¹ì…˜ì˜ Tab(탭) 순서는 다ìŒê³¼ 같습니다.\n' + + '\n' + + '

        \n' + + '
      1. 메뉴 바
      2. \n' + + '
      3. ê° ë„구 ëª¨ìŒ ê·¸ë£¹
      4. \n' + + '
      5. 사ì´ë“œë°”
      6. \n' + + '
      7. í‘¸í„°ì˜ ìš”ì†Œ 경로
      8. \n' + + '
      9. í‘¸í„°ì˜ ë‹¨ì–´ 수 토글 버튼
      10. \n' + + '
      11. í‘¸í„°ì˜ ë¸Œëžœë”© ë§í¬
      12. \n' + + '
      13. í‘¸í„°ì˜ ì—디터 í¬ê¸° 변경 핸들
      14. \n' + + '
      \n' + + '\n' + + '

      UI ì„¹ì…˜ì´ ì—†ëŠ” 경우 건너뛰기합니다.

      \n' + + '\n' + + '

      í‘¸í„°ì— í‚¤ë³´ë“œ íƒìƒ‰ í¬ì»¤ìŠ¤ê°€ 있고 사ì´ë“œë°”는 ë³´ì´ì§€ 않는 경우 Shift+Tab(시프트+탭)ì„ ëˆ„ë¥´ë©´\n' + + ' í¬ì»¤ìŠ¤ 표시가 ë§ˆì§€ë§‰ì´ ì•„ë‹Œ 첫 번째 ë„구 ëª¨ìŒ ê·¸ë£¹ìœ¼ë¡œ ì´ë™í•©ë‹ˆë‹¤.\n' + + '\n' + + '

      UI 섹션 ë‚´ íƒìƒ‰

      \n' + + '\n' + + '

      í•œ UI 요소ì—ì„œ ë‹¤ìŒ UI 요소로 ì´ë™í•˜ë ¤ë©´ ì ì ˆí•œ 화살표 키를 누릅니다.

      \n' + + '\n' + + '

      왼쪽과 오른쪽 화살표 í‚¤ì˜ ìš©ë„:

      \n' + + '\n' + + '
        \n' + + '
      • 메뉴 모ìŒì—ì„œ 메뉴 항목 사ì´ë¥¼ ì´ë™í•©ë‹ˆë‹¤.
      • \n' + + '
      • 메뉴ì—ì„œ 하위 메뉴를 엽니다.
      • \n' + + '
      • ë„구 ëª¨ìŒ ê·¸ë£¹ì—ì„œ 버튼 사ì´ë¥¼ ì´ë™í•©ë‹ˆë‹¤.
      • \n' + + '
      • í‘¸í„°ì˜ ìš”ì†Œ 경로ì—ì„œ 항목 ê°„ì— ì´ë™í•©ë‹ˆë‹¤.
      • \n' + + '
      \n' + + '\n' + + '

      아래와 위 화살표 í‚¤ì˜ ìš©ë„:\n' + + '\n' + + '

        \n' + + '
      • 메뉴ì—ì„œ 메뉴 항목 사ì´ë¥¼ ì´ë™í•©ë‹ˆë‹¤.
      • \n' + + '
      • ë„구 ëª¨ìŒ íŒì—… 메뉴ì—ì„œ 메뉴 항목 사ì´ë¥¼ ì´ë™í•©ë‹ˆë‹¤.
      • \n' + + '
      \n' + + '\n' + + '

      화살표 키는 í¬ì»¤ìŠ¤ 표시 UI 섹션 ë‚´ì—ì„œ 순환ë©ë‹ˆë‹¤.

      \n' + + '\n' + + '

      ì—´ë ¤ 있는 메뉴, ì—´ë ¤ 있는 하위 메뉴 ë˜ëŠ” ì—´ë ¤ 있는 íŒì—… 메뉴를 닫으려면 Esc 키를 누릅니다.\n' + + '\n' + + "

      현재 í¬ì»¤ìŠ¤ 표시가 특정 UI 섹션 'ìƒë‹¨'ì— ìžˆëŠ” 경우 ì´ë•Œë„ Esc 키를 누르면\n" + + ' 키보드 íƒìƒ‰ì´ 완전히 종료ë©ë‹ˆë‹¤.

      \n' + + '\n' + + '

      메뉴 항목 ë˜ëŠ” ë„구 ëª¨ìŒ ë²„íŠ¼ 실행

      \n' + + '\n' + + '

      ì›í•˜ëŠ” 메뉴 항목 ë˜ëŠ” ë„구 ëª¨ìŒ ë²„íŠ¼ì´ ê°•ì¡° 표시ë˜ì–´ ìžˆì„ ë•Œ Return(리턴), Enter(엔터),\n' + + ' ë˜ëŠ” Space bar(스페ì´ìŠ¤ë°”)를 눌러 해당 í•­ëª©ì„ ì‹¤í–‰í•©ë‹ˆë‹¤.\n' + + '\n' + + '

      íƒ­ì´ ì—†ëŠ” 대화 íƒìƒ‰

      \n' + + '\n' + + '

      íƒ­ì´ ì—†ëŠ” ëŒ€í™”ì˜ ê²½ìš°, 첫 번째 대화형 요소가 í¬ì»¤ìŠ¤ í‘œì‹œëœ ìƒíƒœë¡œ 대화가 열립니다.

      \n' + + '\n' + + '

      대화형 요소들 사ì´ë¥¼ ì´ë™í•  때는 Tab(탭) ë˜ëŠ” Shift+Tab(시프트+탭)ì„ ëˆ„ë¦…ë‹ˆë‹¤.

      \n' + + '\n' + + '

      íƒ­ì´ ìžˆëŠ” 대화 íƒìƒ‰

      \n' + + '\n' + + '

      íƒ­ì´ ìžˆëŠ” ëŒ€í™”ì˜ ê²½ìš°, 탭 메뉴ì—ì„œ 첫 번째 ë²„íŠ¼ì´ í¬ì»¤ìŠ¤ í‘œì‹œëœ ìƒíƒœë¡œ 대화가 열립니다.

      \n' + + '\n' + + '

      ì´ ëŒ€í™” íƒ­ì˜ ëŒ€í™”í˜• 요소들 사ì´ë¥¼ ì´ë™í•  때는 Tab(탭) ë˜ëŠ”\n' + + ' Shift+Tab(시프트+탭)ì„ ëˆ„ë¦…ë‹ˆë‹¤.

      \n' + + '\n' + + '

      다른 대화 탭으로 ì´ë™í•˜ë ¤ë©´ 탭 메뉴를 í¬ì»¤ìŠ¤ 표시한 ë‹¤ìŒ ì ì ˆí•œ 화살표\n' + + ' 키를 눌러 사용 가능한 íƒ­ë“¤ì„ ì§€ë‚˜ ì›í•˜ëŠ” 탭으로 ì´ë™í•©ë‹ˆë‹¤.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ms.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ms.js new file mode 100644 index 0000000..579ab49 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ms.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ms', +'

      Mulakan navigasi papan kekunci

      \n' + + '\n' + + '
      \n' + + '
      Fokus bar Menu
      \n' + + '
      Windows atau Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokus Bar Alat
      \n' + + '
      Windows atau Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokus pengaki
      \n' + + '
      Windows atau Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokus bar alat kontekstual
      \n' + + '
      Windows, Linux atau macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigasi akan bermula pada item UI pertama, yang akan diserlahkan atau digaris bawah dalam saiz item pertama dalam\n' + + ' laluan elemen Pengaki.

      \n' + + '\n' + + '

      Navigasi antara bahagian UI

      \n' + + '\n' + + '

      Untuk bergerak dari satu bahagian UI ke yang seterusnya, tekan Tab.

      \n' + + '\n' + + '

      Untuk bergerak dari satu bahagian UI ke yang sebelumnya, tekan Shift+Tab.

      \n' + + '\n' + + '

      Tertib Tab bahagian UI ini ialah:\n' + + '\n' + + '

        \n' + + '
      1. Bar menu
      2. \n' + + '
      3. Setiap kumpulan bar alat
      4. \n' + + '
      5. Bar sisi
      6. \n' + + '
      7. Laluan elemen dalam pengaki
      8. \n' + + '
      9. Butang togol kiraan perkataan dalam pengaki
      10. \n' + + '
      11. Pautan penjenamaan dalam pengaki
      12. \n' + + '
      13. Pemegang saiz semula editor dalam pengaki
      14. \n' + + '
      \n' + + '\n' + + '

      Jika bahagian UI tidak wujud, ia dilangkau.

      \n' + + '\n' + + '

      Jika pengaki mempunyai fokus navigasi papan kekunci dan tiada bar sisi kelihatan, menekan Shift+Tab\n' + + ' akan mengalihkan fokus ke kumpulan bar alat pertama, bukannya yang terakhir.\n' + + '\n' + + '

      Navigasi dalam bahagian UI

      \n' + + '\n' + + '

      Untuk bergerak dari satu elemen UI ke yang seterusnya, tekan kekunci Anak Panah yang bersesuaian.

      \n' + + '\n' + + '

      Kekunci anak panah Kiri dan Kanan

      \n' + + '\n' + + '
        \n' + + '
      • bergerak antara menu dalam bar menu.
      • \n' + + '
      • membukan submenu dalam menu.
      • \n' + + '
      • bergerak antara butang dalam kumpulan bar alat.
      • \n' + + '
      • Laluan elemen dalam pengaki.
      • \n' + + '
      \n' + + '\n' + + '

      Kekunci anak panah Bawah dan Atas\n' + + '\n' + + '

        \n' + + '
      • bergerak antara item menu dalam menu.
      • \n' + + '
      • bergerak antara item dalam menu timbul bar alat.
      • \n' + + '
      \n' + + '\n' + + '

      Kekunci Anak Panah berkitar dalam bahagian UI difokuskan.

      \n' + + '\n' + + '

      Untuk menutup menu buka, submenu terbuka atau menu timbul terbuka, tekan kekunci Esc.\n' + + '\n' + + "

      Jika fokus semasa berada di bahagian 'atas' bahagian UI tertentu, menekan kekunci Esc juga akan keluar daripada\n" + + ' navigasi papan kekunci sepenuhnya.

      \n' + + '\n' + + '

      Laksanakan item menu atau butang bar alat

      \n' + + '\n' + + '

      Apabila item menu atau butang bar alat yang diinginkan diserlahkan, tekan Return, Enter,\n' + + ' atau bar Space untuk melaksanakan item.\n' + + '\n' + + '

      Navigasi ke dialog tidak bertab

      \n' + + '\n' + + '

      Dalam dialog tidak bertab, komponen interaksi pertama difokuskan apabila dialog dibuka.

      \n' + + '\n' + + '

      Navigasi antara komponen dialog interaktif dengan menekan Tab atau Shift+Tab.

      \n' + + '\n' + + '

      Navigasi ke dialog bertab

      \n' + + '\n' + + '

      Dalam dialog bertab, butang pertama dalam menu tab difokuskan apabila dialog dibuka.

      \n' + + '\n' + + '

      Navigasi antara komponen interaktif tab dialog ini dengan menekan Tab atau\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Tukar kepada tab dialog lain dengan memfokuskan menu tab, kemudian menekan kekunci Anak Panah yang bersesuaian\n' + + ' untuk berkitar menerusi tab yang tersedia.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/nb_NO.js b/static/js/tinymce/plugins/help/js/i18n/keynav/nb_NO.js new file mode 100644 index 0000000..f28db8f --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/nb_NO.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.nb_NO', +'

      Starte tastaturnavigering

      \n' + + '\n' + + '
      \n' + + '
      Utheve menylinjen
      \n' + + '
      Windows eller Linux: Alt + F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Utheve verktøylinjen
      \n' + + '
      Windows eller Linux: Alt + F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Utheve bunnteksten
      \n' + + '
      Windows eller Linux: Alt + F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Utheve en kontekstuell verktøylinje
      \n' + + '
      Windows, Linux eller macOS: Ctrl + F9\n' + + '
      \n' + + '\n' + + '

      Navigeringen starter ved det første grensesnittelementet, som utheves, eller understrekes når det gjelder det første elementet i\n' + + ' elementstien i bunnteksten.

      \n' + + '\n' + + '

      Navigere mellom grensesnittdeler

      \n' + + '\n' + + '

      Du kan bevege deg fra én grensesnittdel til den neste ved å trykke på tabulatortasten.

      \n' + + '\n' + + '

      Du kan bevege deg fra én grensesnittdel til den forrige ved å trykke på Shift + tabulatortasten.

      \n' + + '\n' + + '

      Rekkefølgen til tabulatortasten gjennom grensesnittdelene er:\n' + + '\n' + + '

        \n' + + '
      1. Menylinjen
      2. \n' + + '
      3. Hver gruppe på verktøylinjen
      4. \n' + + '
      5. Sidestolpen
      6. \n' + + '
      7. Elementstien i bunnteksten
      8. \n' + + '
      9. Veksleknappen for ordantall i bunnteksten
      10. \n' + + '
      11. Merkelenken i bunnteksten
      12. \n' + + '
      13. Skaleringshåndtaket for redigeringsprogrammet i bunnteksten
      14. \n' + + '
      \n' + + '\n' + + '

      Hvis en grensesnittdel ikke er til stede, blir den hoppet over.

      \n' + + '\n' + + '

      Hvis tastaturnavigeringen har uthevet bunnteksten og det ikke finnes en synlig sidestolpe, kan du trykke på Shift + tabulatortasten\n' + + ' for å flytte fokuset til den første gruppen på verktøylinjen i stedet for den siste.\n' + + '\n' + + '

      Navigere innenfor grensesnittdeler

      \n' + + '\n' + + '

      Du kan bevege deg fra ett grensesnittelement til det neste ved å trykke på den aktuelle piltasten.

      \n' + + '\n' + + '

      De venstre og høyre piltastene

      \n' + + '\n' + + '
        \n' + + '
      • beveger deg mellom menyer pÃ¥ menylinjen.
      • \n' + + '
      • Ã¥pner en undermeny i en meny.
      • \n' + + '
      • beveger deg mellom knapper i en gruppe pÃ¥ verktøylinjen.
      • \n' + + '
      • beveger deg mellom elementer i elementstien i bunnteksten.
      • \n' + + '
      \n' + + '\n' + + '

      Ned- og opp-piltastene\n' + + '\n' + + '

        \n' + + '
      • beveger deg mellom menyelementer i en meny.
      • \n' + + '
      • beveger deg mellom elementer i en hurtigmeny pÃ¥ verktøylinjen.
      • \n' + + '
      \n' + + '\n' + + '

      Med piltastene kan du bevege deg innenfor den uthevede grensesnittdelen.

      \n' + + '\n' + + '

      Du kan lukke en åpen meny, en åpen undermeny eller en åpen hurtigmeny ved å klikke på Esc-tasten.\n' + + '\n' + + '

      Hvis det øverste nivået i en grensesnittdel er uthevet, kan du ved å trykke på Esc også avslutte\n' + + ' tastaturnavigeringen helt.

      \n' + + '\n' + + '

      Utføre et menyelement eller en knapp på en verktøylinje

      \n' + + '\n' + + '

      Når det ønskede menyelementet eller verktøylinjeknappen er uthevet, trykker du på Retur, Enter,\n' + + ' eller mellomromstasten for å utføre elementet.\n' + + '\n' + + '

      Navigere i dialogbokser uten faner

      \n' + + '\n' + + '

      I dialogbokser uten faner blir den første interaktive komponenten uthevet når dialogboksen åpnes.

      \n' + + '\n' + + '

      Naviger mellom interaktive komponenter i dialogboksen ved å trykke på tabulatortasten eller Shift + tabulatortasten.

      \n' + + '\n' + + '

      Navigere i fanebaserte dialogbokser

      \n' + + '\n' + + '

      I fanebaserte dialogbokser blir den første knappen i fanemenyen uthevet når dialogboksen åpnes.

      \n' + + '\n' + + '

      Naviger mellom interaktive komponenter i fanen ved å trykke på tabulatortasten eller\n' + + ' Shift + tabulatortasten.

      \n' + + '\n' + + '

      Veksle til en annen fane i dialogboksen ved å utheve fanemenyen, og trykk deretter på den aktuelle piltasten\n' + + ' for å bevege deg mellom de tilgjengelige fanene.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/nl.js b/static/js/tinymce/plugins/help/js/i18n/keynav/nl.js new file mode 100644 index 0000000..99836a6 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/nl.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.nl', +'

      Toetsenbordnavigatie starten

      \n' + + '\n' + + '
      \n' + + '
      Focus op de menubalk instellen
      \n' + + '
      Windows of Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Focus op de werkbalk instellen
      \n' + + '
      Windows of Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Focus op de voettekst instellen
      \n' + + '
      Windows of Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Focus op een contextuele werkbalk instellen
      \n' + + '
      Windows, Linux of macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      De navigatie start bij het eerste UI-item, dat wordt gemarkeerd of onderstreept als het eerste item zich in\n' + + ' in het elementenpad van de voettekst bevindt.

      \n' + + '\n' + + '

      Navigeren tussen UI-secties

      \n' + + '\n' + + '

      Druk op Tab om naar de volgende UI-sectie te gaan.

      \n' + + '\n' + + '

      Druk op Shift+Tab om naar de vorige UI-sectie te gaan.

      \n' + + '\n' + + '

      De Tab-volgorde van deze UI-secties is:\n' + + '\n' + + '

        \n' + + '
      1. Menubalk
      2. \n' + + '
      3. Elke werkbalkgroep
      4. \n' + + '
      5. Zijbalk
      6. \n' + + '
      7. Elementenpad in de voettekst
      8. \n' + + '
      9. Wisselknop voor aantal woorden in de voettekst
      10. \n' + + '
      11. Merkkoppeling in de voettekst
      12. \n' + + '
      13. Greep voor het wijzigen van het formaat van de editor in de voettekst
      14. \n' + + '
      \n' + + '\n' + + '

      Als een UI-sectie niet aanwezig is, wordt deze overgeslagen.

      \n' + + '\n' + + '

      Als de focus van de toetsenbordnavigatie is ingesteld op de voettekst en er geen zichtbare zijbalk is, kun je op Shift+Tab drukken\n' + + ' om de focus naar de eerste werkbalkgroep in plaats van de laatste te verplaatsen.\n' + + '\n' + + '

      Navigeren binnen UI-secties

      \n' + + '\n' + + '

      Druk op de pijltjestoets om naar het betreffende UI-element te gaan.

      \n' + + '\n' + + '

      Met de pijltjestoetsen Links en Rechts

      \n' + + '\n' + + '
        \n' + + "
      • wissel je tussen menu's in de menubalk.
      • \n" + + '
      • open je een submenu in een menu.
      • \n' + + '
      • wissel je tussen knoppen in een werkbalkgroep.
      • \n' + + '
      • wissel je tussen items in het elementenpad in de voettekst.
      • \n' + + '
      \n' + + '\n' + + '

      Met de pijltjestoetsen Omlaag en Omhoog\n' + + '\n' + + '

        \n' + + '
      • wissel je tussen menu-items in een menu.
      • \n' + + '
      • wissel je tussen items in een werkbalkpop-upmenu.
      • \n' + + '
      \n' + + '\n' + + '

      Met de pijltjestoetsen wissel je binnen de UI-sectie waarop de focus is ingesteld.

      \n' + + '\n' + + '

      Druk op de toets Esc om een geopend menu, submenu of pop-upmenu te sluiten.\n' + + '\n' + + "

      Als de huidige focus is ingesteld 'bovenaan' een bepaalde UI-sectie, kun je op de toets Esc drukken\n" + + ' om de toetsenbordnavigatie af te sluiten.

      \n' + + '\n' + + '

      Een menu-item of werkbalkknop uitvoeren

      \n' + + '\n' + + '

      Als het gewenste menu-item of de gewenste werkbalkknop is gemarkeerd, kun je op Return, Enter\n' + + ' of de spatiebalk drukken om het item uit te voeren.\n' + + '\n' + + '

      Navigeren in dialoogvensters zonder tabblad

      \n' + + '\n' + + '

      Als een dialoogvenster zonder tabblad wordt geopend, wordt de focus ingesteld op het eerste interactieve onderdeel.

      \n' + + '\n' + + '

      Je kunt navigeren tussen interactieve onderdelen van een dialoogvenster door op Tab of Shift+Tab te drukken.

      \n' + + '\n' + + '

      Navigeren in dialoogvensters met tabblad

      \n' + + '\n' + + '

      Als een dialoogvenster met tabblad wordt geopend, wordt de focus ingesteld op de eerste knop in het tabbladmenu.

      \n' + + '\n' + + '

      Je kunt navigeren tussen interactieve onderdelen van dit tabblad van het dialoogvenster door op Tab of\n' + + ' Shift+Tab te drukken.

      \n' + + '\n' + + '

      Je kunt overschakelen naar een ander tabblad van het dialoogvenster door de focus in te stellen op het tabbladmenu en vervolgens op de juiste pijltjestoets\n' + + ' te drukken om tussen de beschikbare tabbladen te wisselen.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/pl.js b/static/js/tinymce/plugins/help/js/i18n/keynav/pl.js new file mode 100644 index 0000000..16704a0 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/pl.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.pl', +'

      Początek nawigacji przy użyciu klawiatury

      \n' + + '\n' + + '
      \n' + + '
      Ustaw fokus na pasek menu
      \n' + + '
      Windows lub Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Ustaw fokus na pasek narzędzi
      \n' + + '
      Windows lub Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Ustaw fokus na sekcjÄ™ Footer
      \n' + + '
      Windows lub Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Ustaw fokus na kontekstowy pasek narzędzi
      \n' + + '
      Windows, Linux lub macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Nawigacja zostanie rozpoczęta od pierwszego elementu interfejsu użytkownika, który jest podświetlony lub — w przypadku pierwszego elementu\n' + + ' w ścieżce elementów w sekcji Footer — podkreślony.

      \n' + + '\n' + + '

      Nawigacja pomiędzy sekcjami interfejsu użytkownika

      \n' + + '\n' + + '

      Aby przenieść się z danej sekcji interfejsu użytkownika do następnej, naciśnij Tab.

      \n' + + '\n' + + '

      Aby przenieść się z danej sekcji interfejsu użytkownika do poprzedniej, naciśnij Shift+Tab.

      \n' + + '\n' + + '

      Kolejność klawisza Tab w takich sekcjach interfejsu użytkownika jest następująca:\n' + + '\n' + + '

        \n' + + '
      1. Pasek menu
      2. \n' + + '
      3. Każda grupa na pasku narzędzi
      4. \n' + + '
      5. Pasek boczny
      6. \n' + + '
      7. Ścieżka elementów w sekcji Footer
      8. \n' + + '
      9. Przycisk przełączania liczby słów w sekcji Footer
      10. \n' + + '
      11. ÅÄ…cze brandujÄ…ce w sekcji Footer
      12. \n' + + '
      13. Uchwyt zmiany rozmiaru edytora w sekcji Footer
      14. \n' + + '
      \n' + + '\n' + + '

      Jeżeli nie ma sekcji interfejsu użytkownika, jest to pomijane.

      \n' + + '\n' + + '

      Jeżeli na sekcji Footer jest ustawiony fokus nawigacji przy użyciu klawiatury i nie ma widocznego paska bocznego, naciśnięcie Shift+Tab\n' + + ' przenosi fokus na pierwszą grupę paska narzędzi, a nie na ostatnią.\n' + + '\n' + + '

      Nawigacja wewnątrz sekcji interfejsu użytkownika

      \n' + + '\n' + + '

      Aby przenieść się z danego elementu interfejsu użytkownika do następnego, naciśnij odpowiedni klawisz strzałki.

      \n' + + '\n' + + '

      Klawisze strzałek w prawo i w lewo służą do

      \n' + + '\n' + + '
        \n' + + '
      • przenoszenia siÄ™ pomiÄ™dzy menu na pasku menu,
      • \n' + + '
      • otwarcia podmenu w menu,
      • \n' + + '
      • przenoszenia siÄ™ pomiÄ™dzy przyciskami w grupie paska narzÄ™dzi,
      • \n' + + '
      • przenoszenia siÄ™ pomiÄ™dzy elementami w Å›cieżce elementów w sekcji Footer.
      • \n' + + '
      \n' + + '\n' + + '

      Klawisze strzałek w dół i w górę służą do\n' + + '\n' + + '

        \n' + + '
      • przenoszenia siÄ™ pomiÄ™dzy elementami menu w menu,
      • \n' + + '
      • przenoszenia siÄ™ pomiÄ™dzy elementami w wyskakujÄ…cym menu paska narzÄ™dzi.
      • \n' + + '
      \n' + + '\n' + + '

      Klawisze strzałek służą do przemieszczania się w sekcji interfejsu użytkownika z ustawionym fokusem.

      \n' + + '\n' + + '

      Aby zamknąć otwarte menu, otwarte podmenu lub otwarte menu wyskakujące, naciśnij klawisz Esc.\n' + + '\n' + + '

      Jeżeli fokus jest ustawiony na górze konkretnej sekcji interfejsu użytkownika, naciśnięcie klawisza Esc powoduje wyjście\n' + + ' z nawigacji przy użyciu klawiatury.

      \n' + + '\n' + + '

      Wykonanie elementu menu lub przycisku paska narzędzi

      \n' + + '\n' + + '

      Gdy podświetlony jest żądany element menu lub przycisk paska narzędzi, naciśnij klawisz Return, Enter\n' + + ' lub Spacja, aby go wykonać.\n' + + '\n' + + '

      Nawigacja po oknie dialogowym bez kart

      \n' + + '\n' + + '

      Gdy otwiera się okno dialogowe bez kart, fokus ustawiany jest na pierwszą interaktywną część okna.

      \n' + + '\n' + + '

      Pomiędzy interaktywnymi częściami okna dialogowego nawiguj, naciskając klawisze Tab lub Shift+Tab.

      \n' + + '\n' + + '

      Nawigacja po oknie dialogowym z kartami

      \n' + + '\n' + + '

      W przypadku okna dialogowego z kartami po otwarciu okna dialogowego fokus ustawiany jest na pierwszy przycisk w menu karty.

      \n' + + '\n' + + '

      Nawigację pomiędzy interaktywnymi częściami karty okna dialogowego prowadzi się poprzez naciskanie klawiszy Tab lub\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Przełączenie się na inną kartę okna dialogowego wykonuje się poprzez ustawienie fokusu na menu karty i naciśnięcie odpowiedniego klawisza strzałki\n' + + ' w celu przemieszczenia się pomiędzy dostępnymi kartami.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/pt_BR.js b/static/js/tinymce/plugins/help/js/i18n/keynav/pt_BR.js new file mode 100644 index 0000000..dea9abf --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/pt_BR.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.pt_BR', +'

      Iniciar navegação pelo teclado

      \n' + + '\n' + + '
      \n' + + '
      Foco na barra de menus
      \n' + + '
      Windows ou Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Foco na barra de ferramentas
      \n' + + '
      Windows ou Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Foco no rodapé
      \n' + + '
      Windows ou Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Foco na barra de ferramentas contextual
      \n' + + '
      Windows, Linux ou macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      A navegação inicia no primeiro item da IU, que será destacado ou sublinhado no caso do primeiro item no\n' + + ' caminho do elemento Rodapé.

      \n' + + '\n' + + '

      Navegar entre seções da IU

      \n' + + '\n' + + '

      Para ir de uma seção da IU para a seguinte, pressione Tab.

      \n' + + '\n' + + '

      Para ir de uma seção da IU para a anterior, pressione Shift+Tab.

      \n' + + '\n' + + '

      A ordem de Tab destas seções da IU é:\n' + + '\n' + + '

        \n' + + '
      1. Barra de menus
      2. \n' + + '
      3. Cada grupo da barra de ferramentas
      4. \n' + + '
      5. Barra lateral
      6. \n' + + '
      7. Caminho do elemento no rodapé
      8. \n' + + '
      9. Botão de alternar contagem de palavras no rodapé
      10. \n' + + '
      11. Link da marca no rodapé
      12. \n' + + '
      13. Alça de redimensionamento do editor no rodapé
      14. \n' + + '
      \n' + + '\n' + + '

      Se não houver uma seção da IU, ela será pulada.

      \n' + + '\n' + + '

      Se o rodapé tiver o foco da navegação pelo teclado e não houver uma barra lateral visível, pressionar Shift+Tab\n' + + ' move o foco para o primeiro grupo da barra de ferramentas, não para o último.\n' + + '\n' + + '

      Navegar dentro das seções da IU

      \n' + + '\n' + + '

      Para ir de um elemento da IU para o seguinte, pressione a Seta correspondente.

      \n' + + '\n' + + '

      As teclas de seta Esquerda e Direita

      \n' + + '\n' + + '
        \n' + + '
      • movem entre menus na barra de menus.
      • \n' + + '
      • abrem um submenu em um menu.
      • \n' + + '
      • movem entre botões em um grupo da barra de ferramentas.
      • \n' + + '
      • movem entre itens no caminho do elemento do rodapé.
      • \n' + + '
      \n' + + '\n' + + '

      As teclas de seta Abaixo e Acima\n' + + '\n' + + '

        \n' + + '
      • movem entre itens de menu em um menu.
      • \n' + + '
      • movem entre itens em um menu suspenso da barra de ferramentas.
      • \n' + + '
      \n' + + '\n' + + '

      As teclas de Seta alternam dentre a seção da IU em foco.

      \n' + + '\n' + + '

      Para fechar um menu aberto, um submenu aberto ou um menu suspenso aberto, pressione Esc.\n' + + '\n' + + '

      Se o foco atual estiver no ‘alto’ de determinada seção da IU, pressionar Esc também sai\n' + + ' totalmente da navegação pelo teclado.

      \n' + + '\n' + + '

      Executar um item de menu ou botão da barra de ferramentas

      \n' + + '\n' + + '

      Com o item de menu ou botão da barra de ferramentas desejado destacado, pressione Return, Enter,\n' + + ' ou a Barra de espaço para executar o item.\n' + + '\n' + + '

      Navegar por caixas de diálogo sem guias

      \n' + + '\n' + + '

      Em caixas de diálogo sem guias, o primeiro componente interativo recebe o foco quando a caixa de diálogo abre.

      \n' + + '\n' + + '

      Navegue entre componentes interativos de caixa de diálogo pressionando Tab ou Shift+Tab.

      \n' + + '\n' + + '

      Navegar por caixas de diálogo com guias

      \n' + + '\n' + + '

      Em caixas de diálogo com guias, o primeiro botão no menu da guia recebe o foco quando a caixa de diálogo abre.

      \n' + + '\n' + + '

      Navegue entre componentes interativos dessa guia da caixa de diálogo pressionando Tab ou\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Alterne para outra guia da caixa de diálogo colocando o foco no menu da guia e pressionando a Seta\n' + + ' adequada para percorrer as guias disponíveis.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/pt_PT.js b/static/js/tinymce/plugins/help/js/i18n/keynav/pt_PT.js new file mode 100644 index 0000000..b41bacd --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/pt_PT.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.pt_PT', +'

      Iniciar navegação com teclado

      \n' + + '\n' + + '
      \n' + + '
      Foco na barra de menu
      \n' + + '
      Windows ou Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Foco na barra de ferramentas
      \n' + + '
      Windows ou Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Foco no rodapé
      \n' + + '
      Windows ou Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Foco numa barra de ferramentas contextual
      \n' + + '
      Windows, Linux ou macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      A navegação começará no primeiro item de IU, que estará realçado ou sublinhado, no caso do primeiro item no\n' + + ' caminho do elemento do rodapé.

      \n' + + '\n' + + '

      Navegar entre secções de IU

      \n' + + '\n' + + '

      Para se mover de uma secção de IU para a seguinte, prima Tab.

      \n' + + '\n' + + '

      Para se mover de uma secção de IU para a anterior, prima Shift+Tab.

      \n' + + '\n' + + '

      A ordem de tabulação destas secções de IU é:\n' + + '\n' + + '

        \n' + + '
      1. Barra de menu
      2. \n' + + '
      3. Cada grupo da barra de ferramentas
      4. \n' + + '
      5. Barra lateral
      6. \n' + + '
      7. Caminho do elemento no rodapé
      8. \n' + + '
      9. Botão de alternar da contagem de palavras no rodapé
      10. \n' + + '
      11. Ligação da marca no rodapé
      12. \n' + + '
      13. Alça de redimensionamento do editor no rodapé
      14. \n' + + '
      \n' + + '\n' + + '

      Se uma secção de IU não estiver presente, é ignorada.

      \n' + + '\n' + + '

      Se o rodapé tiver foco de navegação com teclado e não existir uma barra lateral visível, premir Shift+Tab\n' + + ' move o foco para o primeiro grupo da barra de ferramentas e não para o último.\n' + + '\n' + + '

      Navegar nas secções de IU

      \n' + + '\n' + + '

      Para se mover de um elemento de IU para o seguinte, prima a tecla de seta adequada.

      \n' + + '\n' + + '

      As teclas de seta Para a esquerda e Para a direita

      \n' + + '\n' + + '
        \n' + + '
      • movem-se entre menus na barra de menu.
      • \n' + + '
      • abrem um submenu num menu.
      • \n' + + '
      • movem-se entre botões num grupo da barra de ferramentas.
      • \n' + + '
      • movem-se entre itens no caminho do elemento do rodapé.
      • \n' + + '
      \n' + + '\n' + + '

      As teclas de seta Para cima e Para baixo\n' + + '\n' + + '

        \n' + + '
      • movem-se entre itens de menu num menu.
      • \n' + + '
      • movem-se entre itens num menu de pop-up da barra de ferramentas.
      • \n' + + '
      \n' + + '\n' + + '

      As teclas de seta deslocam-se ciclicamente na secção de IU em foco.

      \n' + + '\n' + + '

      Para fechar um menu aberto, um submenu aberto ou um menu de pop-up aberto, prima a tecla Esc.\n' + + '\n' + + '

      Se o foco atual estiver no "topo" de determinada secção de IU, premir a tecla Esc também fecha\n' + + ' completamente a navegação com teclado.

      \n' + + '\n' + + '

      Executar um item de menu ou botão da barra de ferramentas

      \n' + + '\n' + + '

      Quando o item de menu ou o botão da barra de ferramentas pretendido estiver realçado, prima Retrocesso, Enter\n' + + ' ou a Barra de espaço para executar o item.\n' + + '\n' + + '

      Navegar em diálogos sem separadores

      \n' + + '\n' + + '

      Nos diálogos sem separadores, o primeiro componente interativo fica em foco quando o diálogo abre.

      \n' + + '\n' + + '

      Navegue entre componentes interativos do diálogo, premindo Tab ou Shift+Tab.

      \n' + + '\n' + + '

      Navegar em diálogos com separadores

      \n' + + '\n' + + '

      Nos diálogos com separadores, o primeiro botão no menu do separador fica em foco quando o diálogo abre.

      \n' + + '\n' + + '

      Navegue entre os componentes interativos deste separador do diálogo, premindo Tab ou\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Mude para outro separador do diálogo colocando o menu do separador em foco e, em seguida, premindo a tecla de seta\n' + + ' adequada para se deslocar ciclicamente pelos separadores disponíveis.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ro.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ro.js new file mode 100644 index 0000000..738be9d --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ro.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ro', +'

      Începeți navigarea de la tastatură

      \n' + + '\n' + + '
      \n' + + '
      Focalizare pe bara de meniu
      \n' + + '
      Windows sau Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Focalizare pe bara de instrumente
      \n' + + '
      Windows sau Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Focalizare pe subsol
      \n' + + '
      Windows sau Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Focalizare pe o bară de instrumente contextuală
      \n' + + '
      Windows, Linux sau macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigarea va începe de la primul element al interfeței cu utilizatorul, care va fi evidențiat sau subliniat în cazul primului element din\n' + + ' calea elementului Subsol.

      \n' + + '\n' + + '

      Navigați între secțiunile interfeței cu utilizatorul

      \n' + + '\n' + + '

      Pentru a trece de la o secțiune a interfeței cu utilizatorul la alta, apăsați Tab.

      \n' + + '\n' + + '

      Pentru a trece de la o secțiune a interfeței cu utilizatorul la cea anterioară, apăsați Shift+Tab.

      \n' + + '\n' + + '

      Ordinea cu Tab a acestor secțiuni ale interfeței cu utilizatorul este următoarea:\n' + + '\n' + + '

        \n' + + '
      1. Bara de meniu
      2. \n' + + '
      3. Fiecare grup de bare de instrumente
      4. \n' + + '
      5. Bara laterală
      6. \n' + + '
      7. Calea elementului în subsol
      8. \n' + + '
      9. Buton de comutare a numărului de cuvinte în subsol
      10. \n' + + '
      11. Link de branding în subsol
      12. \n' + + '
      13. Mâner de redimensionare a editorului în subsol
      14. \n' + + '
      \n' + + '\n' + + '

      În cazul în care o secțiune a interfeței cu utilizatorul nu este prezentă, aceasta este omisă.

      \n' + + '\n' + + '

      În cazul în care subsolul are focalizarea navigației asupra tastaturii și nu există o bară laterală vizibilă, apăsarea butonului Shift+Tab\n' + + ' mută focalizarea pe primul grup de bare de instrumente, nu pe ultimul.\n' + + '\n' + + '

      Navigați în secțiunile interfeței cu utilizatorul

      \n' + + '\n' + + '

      Pentru a trece de la un element de interfață cu utilizatorul la următorul, apăsați tasta cu săgeata corespunzătoare.

      \n' + + '\n' + + '

      Tastele cu săgeți către stânga și dreapta

      \n' + + '\n' + + '
        \n' + + '
      • navighează între meniurile din bara de meniuri.
      • \n' + + '
      • deschid un sub-meniu dintr-un meniu.
      • \n' + + '
      • navighează între butoanele dintr-un grup de bare de instrumente.
      • \n' + + '
      • navighează între elementele din calea elementelor subsolului.
      • \n' + + '
      \n' + + '\n' + + '

      Tastele cu săgeți în sus și în jos\n' + + '\n' + + '

        \n' + + '
      • navighează între elementele de meniu dintr-un meniu.
      • \n' + + '
      • navighează între elementele unui meniu pop-up din bara de instrumente.
      • \n' + + '
      \n' + + '\n' + + '

      Tastele cu săgeți navighează în cadrul secțiunii interfeței cu utilizatorul asupra căreia se focalizează.

      \n' + + '\n' + + '

      Pentru a închide un meniu deschis, un sub-meniu deschis sau un meniu pop-up deschis, apăsați tasta Esc.\n' + + '\n' + + '

      Dacă focalizarea curentă este asupra „părții superioare†a unei anumite secțiuni a interfeței cu utilizatorul, prin apăsarea tastei Esc se iese, de asemenea,\n' + + ' în întregime din navigarea de la tastatură.

      \n' + + '\n' + + '

      Executarea unui element de meniu sau a unui buton din bara de instrumente

      \n' + + '\n' + + '

      Atunci când elementul de meniu dorit sau butonul dorit din bara de instrumente este evidențiat, apăsați Return, Enter,\n' + + ' sau bara de spațiu pentru a executa elementul.\n' + + '\n' + + '

      Navigarea de dialoguri fără file

      \n' + + '\n' + + '

      În dialogurile fără file, prima componentă interactivă beneficiază de focalizare la deschiderea dialogului.

      \n' + + '\n' + + '

      Navigați între componentele dialogului interactiv apăsând Tab sau Shift+Tab.

      \n' + + '\n' + + '

      Navigarea de dialoguri cu file

      \n' + + '\n' + + '

      În dialogurile cu file, primul buton din meniul cu file beneficiază de focalizare la deschiderea dialogului.

      \n' + + '\n' + + '

      Navigați între componentele interactive ale acestei file de dialog apăsând Tab sau\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Treceți la o altă filă de dialog focalizând asupra meniului cu file și apoi apăsând săgeata corespunzătoare\n' + + ' pentru a parcurge filele disponibile.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/ru.js b/static/js/tinymce/plugins/help/js/i18n/keynav/ru.js new file mode 100644 index 0000000..83ec76e --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/ru.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ru', +'

      Ðачните управление Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ клавиатуры

      \n' + + '\n' + + '
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° панели меню
      \n' + + '
      Windows или Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° панели инÑтрументов
      \n' + + '
      Windows или Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° нижнем колонтитуле
      \n' + + '
      Windows или Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° контекÑтной панели инÑтрументов
      \n' + + '
      Windows, Linux или macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Первый доÑтупный Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñлемент интерфейÑа будет выделен цветом или подчеркнут (еÑли он находитÑÑ\n' + + ' в пути Ñлементов нижнего колонтитула).

      \n' + + '\n' + + '

      Переход между разделами пользовательÑкого интерфейÑа

      \n' + + '\n' + + '

      Чтобы перейти из текущего раздела интерфейÑа в Ñледующий, нажмите Tab.

      \n' + + '\n' + + '

      Чтобы перейти из текущего раздела интерфейÑа в предыдущий, нажмите Shift+Tab.

      \n' + + '\n' + + '

      Вкладки разделов интерфейÑа раÑположены в Ñледующем порÑдке:\n' + + '\n' + + '

        \n' + + '
      1. Панель меню
      2. \n' + + '
      3. Группы панели инÑтрументов
      4. \n' + + '
      5. Ð‘Ð¾ÐºÐ¾Ð²Ð°Ñ Ð¿Ð°Ð½ÐµÐ»ÑŒ
      6. \n' + + '
      7. Путь Ñлементов нижнего колонтитула
      8. \n' + + '
      9. ПодÑчет Ñлов/Ñимволов в нижнем колонтитуле
      10. \n' + + '
      11. Ð‘Ñ€ÐµÐ½Ð´Ð¾Ð²Ð°Ñ ÑÑылка в нижнем колонтитуле
      12. \n' + + '
      13. Угол Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° окна редактора
      14. \n' + + '
      \n' + + '\n' + + '

      ЕÑли раздел интерфейÑа отÑутÑтвует, он пропуÑкаетÑÑ.

      \n' + + '\n' + + '

      ЕÑли при управлении Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹ Ñ„Ð¾ÐºÑƒÑ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð½Ð° нижнем колонтитуле, а Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð±Ð¾ÐºÐ¾Ð²Ð°Ñ Ð¿Ð°Ð½ÐµÐ»ÑŒ отÑутÑтвует, то при нажатии ÑÐ¾Ñ‡ÐµÑ‚Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ Shift+Tab\n' + + ' Ñ„Ð¾ÐºÑƒÑ Ð¿ÐµÑ€ÐµÐ½Ð¾ÑитÑÑ Ð½Ð° первую группу панели инÑтрументов, а не на поÑледнюю.\n' + + '\n' + + '

      Переход между Ñлементами внутри разделов пользовательÑкого интерфейÑа

      \n' + + '\n' + + '

      Чтобы перейти от текущего Ñлемента интерфейÑа к Ñледующему, нажмите ÑоответÑтвующую клавишу Ñо Ñтрелкой.

      \n' + + '\n' + + '

      Клавиши Ñо Ñтрелками влево и вправо позволÑÑŽÑ‚

      \n' + + '\n' + + '
        \n' + + '
      • перемещатьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ разными меню в панели меню.
      • \n' + + '
      • открывать разделы меню.
      • \n' + + '
      • перемещатьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ кнопками в группе панели инÑтрументов.
      • \n' + + '
      • перемещатьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ Ñлементами в пути Ñлементов нижнего колонтитула.
      • \n' + + '
      \n' + + '\n' + + '

      Клавиши Ñо Ñтрелками вниз и вверх позволÑÑŽÑ‚\n' + + '\n' + + '

        \n' + + '
      • перемещатьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ Ñлементами одного меню.
      • \n' + + '
      • перемещатьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ Ñлементами вÑплывающего меню в панели инÑтрументов.
      • \n' + + '
      \n' + + '\n' + + '

      При иÑпользовании клавиш Ñо Ñтрелками вы будете цикличеÑки перемещатьÑÑ Ð¿Ð¾ Ñлементам в пределах выбранного раздела интерфейÑа.

      \n' + + '\n' + + '

      Чтобы закрыть открытое меню, его раздел или вÑплывающее меню, нажмите клавишу Esc.\n' + + '\n' + + '

      ЕÑли Ñ„Ð¾ÐºÑƒÑ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð½Ð°Ð²ÐµÑ€Ñ…Ñƒ какого-либо раздела интерфейÑа, нажатие клавиши Esc также приведет\n' + + ' к выходу из режима ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ клавиатуры.

      \n' + + '\n' + + '

      ИÑпользование Ñлемента меню или кнопки на панели инÑтрументов

      \n' + + '\n' + + '

      Когда Ñлемент меню или кнопка панели инÑтрументов будут выделены, нажмите Return, Enter\n' + + ' или Space, чтобы их активировать.\n' + + '\n' + + '

      Управление в диалоговом окне без вкладок

      \n' + + '\n' + + '

      При открытии диалогового окна без вкладок Ñ„Ð¾ÐºÑƒÑ Ð¿ÐµÑ€ÐµÐ½Ð¾ÑитÑÑ Ð½Ð° первый интерактивный компонент.

      \n' + + '\n' + + '

      Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° между интерактивными компонентами диалогового окна нажимайте Tab или Shift+Tab.

      \n' + + '\n' + + '

      Управление в диалоговом окне Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ°Ð¼Ð¸

      \n' + + '\n' + + '

      При открытии диалогового окна Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ°Ð¼Ð¸ Ñ„Ð¾ÐºÑƒÑ Ð¿ÐµÑ€ÐµÐ½Ð¾ÑитÑÑ Ð½Ð° первую кнопку в меню вкладок.

      \n' + + '\n' + + '

      Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° между интерактивными компонентами Ñтой вкладки диалогового окна нажимайте Tab или\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° на другую вкладку диалогового окна перемеÑтите Ñ„Ð¾ÐºÑƒÑ Ð½Ð° меню вкладок, а затем иÑпользуйте клавиши Ñо Ñтрелками\n' + + ' Ð´Ð»Ñ Ñ†Ð¸ÐºÐ»Ð¸Ñ‡ÐµÑкого Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ доÑтупными вкладками.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/sk.js b/static/js/tinymce/plugins/help/js/i18n/keynav/sk.js new file mode 100644 index 0000000..6ced780 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/sk.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.sk', +'

      ZaÄíname s navigáciou pomocou klávesnice

      \n' + + '\n' + + '
      \n' + + '
      Prejsť na panel s ponukami
      \n' + + '
      Windows alebo Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Prejsť na panel nástrojov
      \n' + + '
      Windows alebo Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      PrejsÅ¥ na pätiÄku
      \n' + + '
      Windows alebo Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Prejsť na kontextový panel nástrojov
      \n' + + '
      Windows, Linux alebo macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigácia zaÄne pri prvej položke používateľského rozhrania, ktorá bude zvýraznená alebo v prípade prvej položky\n' + + ' cesty k pätiÄke podÄiarknutá.

      \n' + + '\n' + + '

      Navigácia medzi ÄasÅ¥ami používateľského rozhrania

      \n' + + '\n' + + '

      Ak sa chcete posunúť z jednej Äasti používateľského rozhrania do druhej, stlaÄte tlaÄidlo Tab.

      \n' + + '\n' + + '

      Ak sa chcete posunúť z jednej Äasti používateľského rozhrania do predchádzajúcej, stlaÄte tlaÄidlá Shift + Tab.

      \n' + + '\n' + + '

      Poradie prepínania medzi týmito ÄasÅ¥ami používateľského rozhrania pri stláÄaní tlaÄidla Tab:\n' + + '\n' + + '

        \n' + + '
      1. Panel s ponukou
      2. \n' + + '
      3. Každá skupina panela nástrojov
      4. \n' + + '
      5. BoÄný panel
      6. \n' + + '
      7. Cesta k prvku v pätiÄke
      8. \n' + + '
      9. PrepínaÄ poÄtu slov v pätiÄke
      10. \n' + + '
      11. Odkaz na informácie o znaÄke v pätiÄke
      12. \n' + + '
      13. Úchyt na zmenu veľkosti editora v pätiÄke
      14. \n' + + '
      \n' + + '\n' + + '

      Ak nejaká ÄasÅ¥ používateľského rozhrania nie je prítomná, preskoÄí sa.

      \n' + + '\n' + + '

      Ak je pätiÄka vybratá na navigáciu pomocou klávesnice a nie je viditeľný boÄný panel, stlaÄením klávesov Shift+Tab\n' + + ' prejdete na prvú skupinu panela nástrojov, nie na poslednú.\n' + + '\n' + + '

      Navigácia v rámci Äastí používateľského rozhrania

      \n' + + '\n' + + '

      Ak sa chcete posunúť z jedného prvku používateľského rozhrania na Äalší, stlaÄte prísluÅ¡ný kláves so šípkou.

      \n' + + '\n' + + '

      Klávesy so šípkami doľava a doprava

      \n' + + '\n' + + '
        \n' + + '
      • umožňujú presun medzi ponukami na paneli ponúk,
      • \n' + + '
      • otvárajú podponuku v rámci ponuky,
      • \n' + + '
      • umožňujú presun medzi tlaÄidlami v skupine panelov nástrojov,
      • \n' + + '
      • umožňujú presun medzi položkami cesty prvku v pätiÄke.
      • \n' + + '
      \n' + + '\n' + + '

      Klávesy so šípkami dole a hore\n' + + '\n' + + '

        \n' + + '
      • umožňujú presun medzi položkami ponuky,
      • \n' + + '
      • umožňujú presun medzi položkami v kontextovej ponuke panela nástrojov.
      • \n' + + '
      \n' + + '\n' + + '

      Klávesy so šípkami vykonávajú prepínanie v rámci vybranej Äasti používateľského rozhrania.

      \n' + + '\n' + + '

      Ak chcete zatvoriÅ¥ otvorenú ponuku, otvorenú podponuku alebo otvorenú kontextovú ponuku, stlaÄte kláves Esc.\n' + + '\n' + + '

      Ak je aktuálne vybratá horná ÄasÅ¥ konkrétneho používateľského rozhrania, stlaÄením klávesu Esc úplne ukonÄíte tiež\n' + + ' navigáciu pomocou klávesnice.

      \n' + + '\n' + + '

      Vykonanie príkazu položky ponuky alebo tlaÄidla panela nástrojov

      \n' + + '\n' + + '

      KeÄ je zvýraznená požadovaná položka ponuky alebo tlaÄidlo panela nástrojov, stlaÄením klávesov Return, Enter\n' + + ' alebo medzerníka vykonáte prísluÅ¡ný príkaz položky.\n' + + '\n' + + '

      Navigácia v dialógových oknách bez záložiek

      \n' + + '\n' + + '

      Pri otvorení dialógových okien bez záložiek prejdete na prvý interaktívny komponent.

      \n' + + '\n' + + '

      Medzi interaktívnymi dialógovými komponentmi môžete prechádzaÅ¥ stlaÄením klávesov Tab alebo Shift+Tab.

      \n' + + '\n' + + '

      Navigácia v dialógových oknách so záložkami

      \n' + + '\n' + + '

      Pri otvorení dialógových okien so záložkami prejdete na prvé tlaÄidlo v ponuke záložiek.

      \n' + + '\n' + + '

      Medzi interaktívnymi komponentmi tejto dialógovej záložky môžete prechádzaÅ¥ stlaÄením klávesov Tab alebo\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Ak chcete prepnúť na ÄalÅ¡iu záložku dialógového okna, prejdite do ponuky záložiek a potom môžete stlaÄením prísluÅ¡ného klávesu so šípkou\n' + + ' prepínaÅ¥ medzi dostupnými záložkami.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/sl_SI.js b/static/js/tinymce/plugins/help/js/i18n/keynav/sl_SI.js new file mode 100644 index 0000000..11fddec --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/sl_SI.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.sl_SI', +'

      ZaÄetek krmarjenja s tipkovnico

      \n' + + '\n' + + '
      \n' + + '
      Fokus na menijsko vrstico
      \n' + + '
      Windows ali Linux: Alt + F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokus na orodno vrstico
      \n' + + '
      Windows ali Linux: Alt + F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokus na nogo
      \n' + + '
      Windows ali Linux: Alt + F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokus na kontekstualno orodno vrstico
      \n' + + '
      Windows, Linux ali macOS: Ctrl + F9\n' + + '
      \n' + + '\n' + + '

      Krmarjenje se bo zaÄelo s prvim elementom uporabniÅ¡kega vmesnika, ki bo izpostavljena ali podÄrtan, Äe gre za prvi element na\n' + + ' poti do elementa noge.

      \n' + + '\n' + + '

      Krmarjenje med razdelki uporabniškega vmesnika

      \n' + + '\n' + + '

      Če se želite pomakniti z enega dela uporabniškega vmesnika na naslednjega, pritisnite tabulatorko.

      \n' + + '\n' + + '

      Če se želite pomakniti z enega dela uporabniškega vmesnika na prejšnjega, pritisnite shift + tabulatorko.

      \n' + + '\n' + + '

      Zaporedje teh razdelkov uporabniškega vmesnika, ko pritiskate tabulatorko, je:\n' + + '\n' + + '

        \n' + + '
      1. Menijska vrstica
      2. \n' + + '
      3. Posamezne skupine orodne vrstice
      4. \n' + + '
      5. Stranska vrstica
      6. \n' + + '
      7. Pod do elementa v nogi
      8. \n' + + '
      9. Gumb za preklop Å¡tetja besed v nogi
      10. \n' + + '
      11. Povezava do blagovne znamke v nogi
      12. \n' + + '
      13. RoÄaj za spreminjanje velikosti urejevalnika v nogi
      14. \n' + + '
      \n' + + '\n' + + '

      ÄŒe razdelek uporabniÅ¡kega vmesnika ni prisoten, je preskoÄen.

      \n' + + '\n' + + '

      ÄŒe ima noga fokus za krmarjenje s tipkovnico in ni vidne stranske vrstice, s pritiskom na shift + tabulatorko\n' + + ' fokus premaknete na prvo skupino orodne vrstice, ne zadnjo.\n' + + '\n' + + '

      Krmarjenje v razdelkih uporabniškega vmesnika

      \n' + + '\n' + + '

      ÄŒe se želite premakniti z enega elementa uporabniÅ¡kega vmesnika na naslednjega, pritisnite ustrezno puÅ¡ÄiÄno tipko.

      \n' + + '\n' + + '

      Leva in desna puÅ¡ÄiÄna tipka

      \n' + + '\n' + + '
        \n' + + '
      • omogoÄata premikanje med meniji v menijski vrstici.
      • \n' + + '
      • odpreta podmeni v meniju.
      • \n' + + '
      • omogoÄata premikanje med gumbi v skupini orodne vrstice.
      • \n' + + '
      • omogoÄata premikanje med elementi na poti do elementov noge.
      • \n' + + '
      \n' + + '\n' + + '

      Spodnja in zgornja puÅ¡ÄiÄna tipka\n' + + '\n' + + '

        \n' + + '
      • omogoÄata premikanje med elementi menija.
      • \n' + + '
      • omogoÄata premikanje med elementi v pojavnem meniju orodne vrstice.
      • \n' + + '
      \n' + + '\n' + + '

      PuÅ¡ÄiÄne tipke omogoÄajo kroženje znotraj razdelka uporabniÅ¡kega vmesnika, na katerem je fokus.

      \n' + + '\n' + + '

      Če želite zapreti odprt meni, podmeni ali pojavni meni, pritisnite tipko Esc.\n' + + '\n' + + '

      ÄŒe je trenutni fokus na »vrhu« doloÄenega razdelka uporabniÅ¡kega vmesnika, s pritiskom tipke Esc zaprete\n' + + ' tudi celotno krmarjenje s tipkovnico.

      \n' + + '\n' + + '

      Izvajanje menijskega elementa ali gumba orodne vrstice

      \n' + + '\n' + + '

      Ko je oznaÄen želeni menijski element ali orodja vrstica, pritisnite vraÄalko, Enter\n' + + ' ali preslednico, da izvedete element.\n' + + '\n' + + '

      Krmarjenje po pogovornih oknih brez zavihkov

      \n' + + '\n' + + '

      Ko odprete pogovorno okno brez zavihkov, ima fokus prva interaktivna komponenta.

      \n' + + '\n' + + '

      Med interaktivnimi komponentami pogovornega okna se premikate s pritiskom tabulatorke ali kombinacije tipke shift + tabulatorke.

      \n' + + '\n' + + '

      Krmarjenje po pogovornih oknih z zavihki

      \n' + + '\n' + + '

      Ko odprete pogovorno okno z zavihki, ima fokus prvi gumb v meniju zavihka.

      \n' + + '\n' + + '

      Med interaktivnimi komponentami tega zavihka pogovornega okna se premikate s pritiskom tabulatorke ali\n' + + ' kombinacije tipke shift + tabulatorke.

      \n' + + '\n' + + '

      Na drug zavihek pogovornega okna preklopite tako, da fokus prestavite na meni zavihka in nato pritisnete ustrezno puÅ¡ÄiÄno\n' + + ' tipko, da se pomaknete med razpoložljivimi zavihki.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/sv_SE.js b/static/js/tinymce/plugins/help/js/i18n/keynav/sv_SE.js new file mode 100644 index 0000000..d82bce6 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/sv_SE.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.sv_SE', +'

      Påbörja tangentbordsnavigering

      \n' + + '\n' + + '
      \n' + + '
      Fokusera på menyraden
      \n' + + '
      Windows eller Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Fokusera på verktygsraden
      \n' + + '
      Windows eller Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Fokusera på verktygsraden
      \n' + + '
      Windows eller Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Fokusera på en snabbverktygsrad
      \n' + + '
      Windows, Linux eller macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Navigeringen börjar vid det första gränssnittsobjektet, vilket är markerat eller understruket om det gäller det första objektet i\n' + + ' sidfotens elementsökväg.

      \n' + + '\n' + + '

      Navigera mellan UI-avsnitt

      \n' + + '\n' + + '

      Flytta från ett UI-avsnitt till nästa genom att trycka på Tabb.

      \n' + + '\n' + + '

      Flytta från ett UI-avsnitt till det föregående genom att trycka på Skift+Tabb.

      \n' + + '\n' + + '

      Tabb-ordningen för dessa UI-avsnitt är:\n' + + '\n' + + '

        \n' + + '
      1. Menyrad
      2. \n' + + '
      3. Varje verktygsradsgrupp
      4. \n' + + '
      5. Sidoruta
      6. \n' + + '
      7. Elementsökväg i sidfoten
      8. \n' + + '
      9. Växlingsknapp för ordantal i sidfoten
      10. \n' + + '
      11. Varumärkeslänk i sidfoten
      12. \n' + + '
      13. Storlekshandtag för redigeraren i sidfoten
      14. \n' + + '
      \n' + + '\n' + + '

      Om ett UI-avsnitt inte finns hoppas det över.

      \n' + + '\n' + + '

      Om sidfoten har fokus på tangentbordsnavigering, och det inte finns någon synlig sidoruta, flyttas fokus till den första verktygsradsgruppen\n' + + ' när du trycker på Skift+Tabb, inte till den sista.\n' + + '\n' + + '

      Navigera i UI-avsnitt

      \n' + + '\n' + + '

      Flytta från ett UI-element till nästa genom att trycka på motsvarande piltangent.

      \n' + + '\n' + + '

      Vänsterpil och högerpil

      \n' + + '\n' + + '
        \n' + + '
      • flytta mellan menyer pÃ¥ menyraden.
      • \n' + + '
      • öppna en undermeny pÃ¥ en meny.
      • \n' + + '
      • flytta mellan knappar i en verktygsradgrupp.
      • \n' + + '
      • flytta mellan objekt i sidfotens elementsökväg.
      • \n' + + '
      \n' + + '\n' + + '

      Nedpil och uppil\n' + + '\n' + + '

        \n' + + '
      • flytta mellan menyalternativ pÃ¥ en meny.
      • \n' + + '
      • flytta mellan alternativ pÃ¥ en popup-meny pÃ¥ verktygsraden.
      • \n' + + '
      \n' + + '\n' + + '

      Piltangenterna cirkulerar inom det fokuserade UI-avsnittet.

      \n' + + '\n' + + '

      Tryck på Esc-tangenten om du vill stänga en öppen meny, undermeny eller popup-meny.\n' + + '\n' + + '

      Om det aktuella fokuset är högst upp i ett UI-avsnitt avlutas även tangentbordsnavigeringen helt när\n' + + ' du trycker på Esc-tangenten.

      \n' + + '\n' + + '

      Köra ett menyalternativ eller en verktygfältsknapp

      \n' + + '\n' + + '

      När menyalternativet eller verktygsradsknappen är markerad trycker du på Retur, Enter\n' + + ' eller blanksteg för att köra alternativet.\n' + + '\n' + + '

      Navigera i dialogrutor utan flikar

      \n' + + '\n' + + '

      I dialogrutor utan flikar är den första interaktiva komponenten i fokus när dialogrutan öppnas.

      \n' + + '\n' + + '

      Navigera mellan interaktiva dialogkomponenter genom att trycka på Tabb eller Skift+Tabb.

      \n' + + '\n' + + '

      Navigera i dialogrutor med flikar

      \n' + + '\n' + + '

      I dialogrutor utan flikar är den första knappen på flikmenyn i fokus när dialogrutan öppnas.

      \n' + + '\n' + + '

      Navigera mellan interaktiva komponenter på dialogrutefliken genom att trycka på Tabb eller\n' + + ' Skift+Tabb.

      \n' + + '\n' + + '

      Växla till en annan dialogruta genom att fokusera på flikmenyn och sedan trycka på motsvarande piltangent\n' + + ' för att cirkulera mellan de tillgängliga flikarna.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/th_TH.js b/static/js/tinymce/plugins/help/js/i18n/keynav/th_TH.js new file mode 100644 index 0000000..240f7dd --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/th_TH.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.th_TH', +'

      เริ่มต้นà¸à¸²à¸£à¸™à¸³à¸—างด้วยà¹à¸›à¹‰à¸™à¸žà¸´à¸¡à¸žà¹Œ

      \n' + + '\n' + + '
      \n' + + '
      โฟà¸à¸±à¸ªà¸—ี่à¹à¸–บเมนู
      \n' + + '
      Windows หรือ Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      โฟà¸à¸±à¸ªà¸—ี่à¹à¸–บเครื่องมือ
      \n' + + '
      Windows หรือ Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      โฟà¸à¸±à¸ªà¸—ี่ส่วนท้าย
      \n' + + '
      Windows หรือ Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      โฟà¸à¸±à¸ªà¸—ี่à¹à¸–บเครื่องมือตามบริบท
      \n' + + '
      Windows, Linux หรือ macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      à¸à¸²à¸£à¸™à¸³à¸—างจะเริ่มที่รายà¸à¸²à¸£ UI à¹à¸£à¸ ซึ่งจะมีà¸à¸²à¸£à¹„ฮไลต์หรือขีดเส้นใต้ไว้ในà¸à¸£à¸“ีที่รายà¸à¸²à¸£à¹à¸£à¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™\n' + + ' พาธองค์ประà¸à¸­à¸šà¸ªà¹ˆà¸§à¸™à¸—้าย

      \n' + + '\n' + + '

      à¸à¸²à¸£à¸™à¸³à¸—างระหว่างส่วนต่างๆ ของ UI

      \n' + + '\n' + + '

      ในà¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¸ˆà¸²à¸à¸ªà¹ˆà¸§à¸™ UI หนึ่งไปยังส่วนถัดไป ให้à¸à¸” Tab

      \n' + + '\n' + + '

      ในà¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¸ˆà¸²à¸à¸ªà¹ˆà¸§à¸™ UI หนึ่งไปยังส่วนà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² ให้à¸à¸” Shift+Tab

      \n' + + '\n' + + '

      ลำดับà¹à¸—็บของส่วนต่างๆ ของ UI คือ:\n' + + '\n' + + '

        \n' + + '
      1. à¹à¸–บเมนู
      2. \n' + + '
      3. à¹à¸•à¹ˆà¸¥à¸°à¸à¸¥à¸¸à¹ˆà¸¡à¹à¸–บเครื่องมือ
      4. \n' + + '
      5. à¹à¸–บข้าง
      6. \n' + + '
      7. พาธองค์ประà¸à¸­à¸šà¹ƒà¸™à¸ªà¹ˆà¸§à¸™à¸—้าย
      8. \n' + + '
      9. ปุ่มสลับเปิด/ปิดจำนวนคำในส่วนท้าย
      10. \n' + + '
      11. ลิงà¸à¹Œà¸Šà¸·à¹ˆà¸­à¹à¸šà¸£à¸™à¸”์ในส่วนท้าย
      12. \n' + + '
      13. จุดจับปรับขนาดของตัวà¹à¸à¹‰à¹„ขในส่วนท้าย
      14. \n' + + '
      \n' + + '\n' + + '

      หาà¸à¸ªà¹ˆà¸§à¸™ UI ไม่ปราà¸à¸ à¹à¸ªà¸”งว่าถูà¸à¸‚้ามไป

      \n' + + '\n' + + '

      หาà¸à¸ªà¹ˆà¸§à¸™à¸—้ายมีà¸à¸²à¸£à¹‚ฟà¸à¸±à¸ªà¸à¸²à¸£à¸™à¸³à¸—างà¹à¸›à¹‰à¸™à¸žà¸´à¸¡à¸žà¹Œà¹à¸¥à¸°à¹„ม่มีà¹à¸–บข้างปราà¸à¸ à¸à¸²à¸£à¸à¸” Shift+Tab\n' + + ' จะย้ายà¸à¸²à¸£à¹‚ฟà¸à¸±à¸ªà¹„ปที่à¸à¸¥à¸¸à¹ˆà¸¡à¹à¸–บเครื่องมือà¹à¸£à¸ ไม่ใช่สุดท้าย\n' + + '\n' + + '

      à¸à¸²à¸£à¸™à¸³à¸—างภายในส่วนต่างๆ ของ UI

      \n' + + '\n' + + '

      ในà¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¸ˆà¸²à¸à¸­à¸‡à¸„์ประà¸à¸­à¸š UI หนึ่งไปยังองค์ประà¸à¸­à¸šà¸ªà¹ˆà¸§à¸™à¸–ัดไป ให้à¸à¸”ปุ่มลูà¸à¸¨à¸£à¸—ี่เหมาะสม

      \n' + + '\n' + + '

      ปุ่มลูà¸à¸¨à¸£à¸‹à¹‰à¸²à¸¢à¹à¸¥à¸°à¸‚วา

      \n' + + '\n' + + '
        \n' + + '
      • ย้ายไปมาระหว่างเมนูต่างๆ ในà¹à¸–บเมนู
      • \n' + + '
      • เปิดเมนูย่อยในเมนู
      • \n' + + '
      • ย้ายไปมาระหว่างปุ่มต่างๆ ในà¸à¸¥à¸¸à¹ˆà¸¡à¹à¸–บเครื่องมือ
      • \n' + + '
      • ย้ายไปมาระหว่างรายà¸à¸²à¸£à¸•à¹ˆà¸²à¸‡à¹† ในพาธองค์ประà¸à¸­à¸šà¸‚องส่วนท้าย
      • \n' + + '
      \n' + + '\n' + + '

      ปุ่มลูà¸à¸¨à¸£à¸¥à¸‡à¹à¸¥à¸°à¸‚ึ้น\n' + + '\n' + + '

        \n' + + '
      • ย้ายไปมาระหว่างรายà¸à¸²à¸£à¹€à¸¡à¸™à¸¹à¸•à¹ˆà¸²à¸‡à¹† ในเมนู
      • \n' + + '
      • ย้ายไปมาระหว่างรายà¸à¸²à¸£à¸•à¹ˆà¸²à¸‡à¹† ในเมนูป๊อบอัพà¹à¸–บเครื่องมือ
      • \n' + + '
      \n' + + '\n' + + '

      ปุ่มลูà¸à¸¨à¸£à¸ˆà¸°à¹€à¸¥à¸·à¹ˆà¸­à¸™à¹„ปมาภายในส่วน UI ที่โฟà¸à¸±à¸ª

      \n' + + '\n' + + '

      ในà¸à¸²à¸£à¸›à¸´à¸”เมนูที่เปิดอยู่ เมนูย่อยที่เปิดอยู่ หรือเมนูป๊อบอัพที่เปิดอยู่ ให้à¸à¸”ปุ่ม Esc\n' + + '\n' + + '

      หาà¸à¹‚ฟà¸à¸±à¸ªà¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸­à¸¢à¸¹à¹ˆà¸—ี่ ‘ด้านบนสุด’ ของส่วน UI เฉพาะ à¸à¸²à¸£à¸à¸”ปุ่ม Esc จะทำให้ออà¸à¸ˆà¸²à¸\n' + + ' à¸à¸²à¸£à¸™à¸³à¸—างด้วยà¹à¸›à¹‰à¸™à¸žà¸´à¸¡à¸žà¹Œà¸—ั้งหมดเช่นà¸à¸±à¸™

      \n' + + '\n' + + '

      à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸£à¸²à¸¢à¸à¸²à¸£à¹€à¸¡à¸™à¸¹à¸«à¸£à¸·à¸­à¸›à¸¸à¹ˆà¸¡à¹ƒà¸™à¹à¸–บเครื่องมือ

      \n' + + '\n' + + '

      เมื่อไฮไลต์รายà¸à¸²à¸£à¹€à¸¡à¸™à¸¹à¸«à¸£à¸·à¸­à¸›à¸¸à¹ˆà¸¡à¹ƒà¸™à¹à¸–บเครื่องมือที่ต้องà¸à¸²à¸£ ให้à¸à¸” Return, Enter\n' + + ' หรือ Space bar เพื่อดำเนินà¸à¸²à¸£à¸£à¸²à¸¢à¸à¸²à¸£à¸”ังà¸à¸¥à¹ˆà¸²à¸§\n' + + '\n' + + '

      à¸à¸²à¸£à¸™à¸³à¸—างสำหรับà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบที่ไม่อยู่ในà¹à¸—็บ

      \n' + + '\n' + + '

      ในà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบที่ไม่อยู่ในà¹à¸—็บ จะโฟà¸à¸±à¸ªà¸—ี่ส่วนประà¸à¸­à¸šà¹€à¸Šà¸´à¸‡à¹‚ต้ตอบà¹à¸£à¸à¹€à¸¡à¸·à¹ˆà¸­à¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบเปิด

      \n' + + '\n' + + '

      นำทางระหว่างส่วนประà¸à¸­à¸šà¹€à¸Šà¸´à¸‡à¹‚ต้ตอบต่างๆ ของà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบ โดยà¸à¸²à¸£à¸à¸” Tab หรือ Shift+Tab

      \n' + + '\n' + + '

      à¸à¸²à¸£à¸™à¸³à¸—างสำหรับà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบที่อยู่ในà¹à¸—็บ

      \n' + + '\n' + + '

      ในà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบที่อยู่ในà¹à¸—็บ จะโฟà¸à¸±à¸ªà¸—ี่ปุ่มà¹à¸£à¸à¹ƒà¸™à¹€à¸¡à¸™à¸¹à¹à¸—็บเมื่อà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบเปิด

      \n' + + '\n' + + '

      นำทางระหว่างส่วนประà¸à¸­à¸šà¹€à¸Šà¸´à¸‡à¹‚ต้ตอบต่างๆ ของà¹à¸—็บà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบนี้โดยà¸à¸²à¸£à¸à¸” Tab หรือ\n' + + ' Shift+Tab

      \n' + + '\n' + + '

      สลับไปยังà¹à¸—็บà¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบอื่นโดยà¸à¸²à¸£à¹€à¸¥à¸·à¸­à¸à¹‚ฟà¸à¸±à¸ªà¸—ี่เมนูà¹à¸—็บ à¹à¸¥à¹‰à¸§à¸à¸”ปุ่มลูà¸à¸¨à¸£à¸—ี่เหมาะสม\n' + + ' เพื่อเลือà¸à¹à¸—็บที่ใช้ได้

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/tr.js b/static/js/tinymce/plugins/help/js/i18n/keynav/tr.js new file mode 100644 index 0000000..87e8885 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/tr.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.tr', +'

      Klavyeyle gezintiyi baÅŸlatma

      \n' + + '\n' + + '
      \n' + + '
      Menü çubuğuna odaklan
      \n' + + '
      Windows veya Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Araç çubuğuna odaklan
      \n' + + '
      Windows veya Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Alt bilgiye odaklan
      \n' + + '
      Windows veya Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Bağlamsal araç çubuğuna odaklan
      \n' + + '
      Windows, Linux veya macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Gezinti ilk kullanıcı arabirimi öğesinden başlar, bu öğe vurgulanır ya da ilk öğe, Alt bilgi elemanı\n' + + ' yolundaysa altı çizilir.

      \n' + + '\n' + + '

      Kullanıcı arabirimi bölümleri arasında gezinme

      \n' + + '\n' + + '

      Sonraki kullanıcı arabirimi bölümüne gitmek için Sekme tuşuna basın.

      \n' + + '\n' + + '

      Önceki kullanıcı arabirimi bölümüne gitmek için Shift+Sekme tuşlarına basın.

      \n' + + '\n' + + '

      Bu kullanıcı arabirimi bölümlerinin Sekme sırası:\n' + + '\n' + + '

        \n' + + '
      1. Menü çubuğu
      2. \n' + + '
      3. Her araç çubuğu grubu
      4. \n' + + '
      5. Kenar çubuğu
      6. \n' + + '
      7. Alt bilgide öğe yolu
      8. \n' + + '
      9. Alt bilgide sözcük sayısı geçiş düğmesi
      10. \n' + + '
      11. Alt bilgide marka bağlantısı
      12. \n' + + '
      13. Alt bilgide düzenleyiciyi yeniden boyutlandırma tutamacı
      14. \n' + + '
      \n' + + '\n' + + '

      Kullanıcı arabirimi bölümü yoksa atlanır.

      \n' + + '\n' + + '

      Alt bilgide klavyeyle gezinti odağı yoksa ve görünür bir kenar çubuğu mevcut değilse Shift+Sekme tuşlarına basıldığında\n' + + ' odak son araç çubuğu yerine ilk araç çubuğu grubuna taşınır.\n' + + '\n' + + '

      Kullanıcı arabirimi bölümleri içinde gezinme

      \n' + + '\n' + + '

      Sonraki kullanıcı arabirimi elemanına gitmek için uygun Ok tuşuna basın.

      \n' + + '\n' + + '

      Sol ve Sağ ok tuşları

      \n' + + '\n' + + '
        \n' + + '
      • menü çubuÄŸundaki menüler arasında hareket eder.
      • \n' + + '
      • menüde bir alt menü açar.
      • \n' + + '
      • araç çubuÄŸu grubundaki düğmeler arasında hareket eder.
      • \n' + + '
      • alt bilginin öğe yolundaki öğeler arasında hareket eder.
      • \n' + + '
      \n' + + '\n' + + '

      Aşağı ve Yukarı ok tuşları\n' + + '\n' + + '

        \n' + + '
      • menüdeki menü öğeleri arasında hareket eder.
      • \n' + + '
      • araç çubuÄŸu açılır menüsündeki öğeler arasında hareket eder.
      • \n' + + '
      \n' + + '\n' + + '

      Ok tuşları, odaklanılan kullanıcı arabirimi bölümü içinde döngüsel olarak hareket eder.

      \n' + + '\n' + + '

      Açık bir menüyü, açık bir alt menüyü veya açık bir açılır menüyü kapatmak için Esc tuşuna basın.\n' + + '\n' + + '

      Geçerli odak belirli bir kullanıcı arabirimi bölümünün "üst" kısmındaysa Esc tuşuna basıldığında\n' + + ' klavyeyle gezintiden de tamamen çıkılır.

      \n' + + '\n' + + '

      Menü öğesini veya araç çubuğu düğmesini yürütme

      \n' + + '\n' + + '

      İstediğiniz menü öğesi veya araç çubuğu düğmesi vurgulandığında Return, Enter\n' + + ' veya Ara çubuğu tuşuna basın.\n' + + '\n' + + '

      Sekme bulunmayan iletişim kutularında gezinme

      \n' + + '\n' + + '

      Sekme bulunmayan iletişim kutularında, iletişim kutusu açıldığında ilk etkileşimli bileşene odaklanılır.

      \n' + + '\n' + + '

      Etkileşimli iletişim kutusu bileşenleri arasında gezinmek için Sekme veya Shift+ Sekme tuşlarına basın.

      \n' + + '\n' + + '

      Sekmeli iletişim kutularında gezinme

      \n' + + '\n' + + '

      Sekmeli iletişim kutularında, iletişim kutusu açıldığında sekme menüsündeki ilk düğmeye odaklanılır.

      \n' + + '\n' + + '

      Bu iletişim kutusu sekmesinin etkileşimli bileşenleri arasında gezinmek için Sekme veya\n' + + ' Shift+Sekme tuşlarına basın.

      \n' + + '\n' + + '

      Mevcut sekmeler arasında geçiş yapmak için sekme menüsüne odaklanıp uygun Ok tuşuna basarak\n' + + ' başka bir iletişim kutusu sekmesine geçiş yapın.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/uk.js b/static/js/tinymce/plugins/help/js/i18n/keynav/uk.js new file mode 100644 index 0000000..5cd0744 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/uk.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.uk', +'

      Початок роботи з навігацією за допомогою клавіатури

      \n' + + '\n' + + '
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° Ñ€Ñдок меню
      \n' + + '
      Windows або Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° панелі інÑтрументів
      \n' + + '
      Windows або Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° розділі "Ðижній колонтитул"
      \n' + + '
      Windows або Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Ð¤Ð¾ÐºÑƒÑ Ð½Ð° контекÑтній панелі інÑтрументів
      \n' + + '
      Windows, Linux або macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      ÐÐ°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ Ð¿Ð¾Ñ‡Ð½ÐµÑ‚ÑŒÑÑ Ð· першого елемента інтерфейÑу кориÑтувача, Ñкий буде виділено або підкреÑлено в разі, Ñкщо перший елемент знаходитьÑÑ Ð²\n' + + ' шлÑху до елемента "Ðижній колонтитул".

      \n' + + '\n' + + '

      ÐÐ°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ Ð¼Ñ–Ð¶ розділами інтерфейÑу кориÑтувача

      \n' + + '\n' + + '

      Щоб перейти з одного розділу інтерфейÑу кориÑтувача до наÑтупного розділу, натиÑніть клавішу Tab.

      \n' + + '\n' + + '

      Щоб перейти з одного розділу інтерфейÑу кориÑтувача до попереднього розділу, натиÑніть ÑÐ¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ ÐºÐ»Ð°Ð²Ñ–Ñˆ Shift+Tab.

      \n' + + '\n' + + '

      ПорÑдок Вкладок цих розділів інтерфейÑу кориÑтувача такий:\n' + + '\n' + + '

        \n' + + '
      1. РÑдок меню
      2. \n' + + '
      3. Кожна група панелей інÑтрументів
      4. \n' + + '
      5. Бічна панель
      6. \n' + + '
      7. ШлÑÑ… до елементів у розділі "Ðижній колонтитул"
      8. \n' + + '
      9. Кнопка перемикача "КількіÑÑ‚ÑŒ Ñлів" у розділі "Ðижній колонтитул"
      10. \n' + + '
      11. ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° брендинг у розділі "Ðижній колонтитул"
      12. \n' + + '
      13. Маркер Ð·Ð¼Ñ–Ð½ÐµÐ½Ð½Ñ Ñ€Ð¾Ð·Ð¼Ñ–Ñ€Ñƒ в розділі "Ðижній колонтитул"
      14. \n' + + '
      \n' + + '\n' + + '

      Якщо розділ інтерфейÑу кориÑтувача відÑутній, він пропуÑкаєтьÑÑ.

      \n' + + '\n' + + '

      Якщо Ñ„Ð¾ÐºÑƒÑ Ð½Ð°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ— клавіатури знаходитьÑÑ Ð½Ð° розділі "Ðижній колонтитул", але кориÑтувач не бачить видиму бічну панель, натиÑніть Shift+Tab,\n' + + ' щоб переміÑтити Ñ„Ð¾ÐºÑƒÑ Ð½Ð° першу групу панелі інÑтрументів, а не на оÑтанню.\n' + + '\n' + + '

      ÐÐ°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ Ð² межах розділів інтерфейÑу кориÑтувача

      \n' + + '\n' + + '

      Щоб перейти з одного елементу інтерфейÑу кориÑтувача до наÑтупного, натиÑніть відповідну клавішу зі Ñтрілкою.

      \n' + + '\n' + + '

      Клавіші зі Ñтрілками Ліворуч Ñ– Праворуч

      \n' + + '\n' + + '
        \n' + + '
      • переміщують між меню в Ñ€Ñдку меню.
      • \n' + + '
      • відкривають вкладене меню в меню.
      • \n' + + '
      • переміщують кориÑтувача між кнопками в групі панелі інÑтрументів.
      • \n' + + '
      • переміщують між елементами в шлÑху до елементів у розділі "Ðижній колонтитул".
      • \n' + + '
      \n' + + '\n' + + '

      Клавіші зі Ñтрілками Вниз Ñ– Вгору\n' + + '\n' + + '

        \n' + + '
      • переміщують між елементами меню в меню.
      • \n' + + '
      • переміщують між елементами в Ñпливаючому меню панелі інÑтрументів.
      • \n' + + '
      \n' + + '\n' + + '

      Клавіші зі Ñтрілками переміщують Ñ„Ð¾ÐºÑƒÑ Ñ†Ð¸ÐºÐ»Ñ–Ñ‡Ð½Ð¾ в межах розділу інтерфейÑу кориÑтувача, на Ñкому знаходитьÑÑ Ñ„Ð¾ÐºÑƒÑ.

      \n' + + '\n' + + '

      Щоб закрити відкрите меню, відкрите вкладене меню або відкрите Ñпливаюче меню, натиÑніть клавішу Esc.\n' + + '\n' + + '

      Якщо поточний Ñ„Ð¾ÐºÑƒÑ Ð·Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑŒÑÑ Ð½Ð° верхньому рівні певного розділу інтерфейÑу кориÑтувача, натиÑÐºÐ°Ð½Ð½Ñ ÐºÐ»Ð°Ð²Ñ–ÑˆÑ– Esc також виконує вихід\n' + + ' з навігації за допомогою клавіатури повніÑÑ‚ÑŽ.

      \n' + + '\n' + + '

      Ð’Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ñƒ меню або кнопки панелі інÑтрументів

      \n' + + '\n' + + '

      Коли потрібний елемент меню або кнопку панелі інÑтрументів виділено, натиÑніть клавіші Return, Enter,\n' + + ' або Пробіл, щоб виконати цей елемент.\n' + + '\n' + + '

      ÐÐ°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ Ð¿Ð¾ діалоговим вікнам без вкладок

      \n' + + '\n' + + '

      У діалогових вікнах без вкладок перший інтерактивний компонент приймає фокуÑ, коли відкриваєтьÑÑ Ð´Ñ–Ð°Ð»Ð¾Ð³Ð¾Ð²Ðµ вікно.

      \n' + + '\n' + + '

      Переходьте між інтерактивними компонентами діалогового вікна, натиÑкаючи клавіші Tab або Shift+Tab.

      \n' + + '\n' + + '

      ÐÐ°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ Ð¿Ð¾ діалоговим вікнам з вкладками

      \n' + + '\n' + + '

      У діалогових вікнах із вкладками перша кнопка в меню вкладки приймає фокуÑ, коли відкриваєтьÑÑ Ð´Ñ–Ð°Ð»Ð¾Ð³Ð¾Ð²Ðµ вікно.

      \n' + + '\n' + + '

      Переходьте між інтерактивними компонентами цієї вкладки діалогового вікна, натиÑкаючи клавіші Tab або\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Щоб перейти на іншу вкладку діалогового вікна, переміÑÑ‚Ñ–Ñ‚ÑŒ Ñ„Ð¾ÐºÑƒÑ Ð½Ð° меню вкладки, а потім натиÑніть відповідну клавішу зі Ñтрілкою,\n' + + ' щоб циклічно переходити по доÑтупним вкладкам.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/vi.js b/static/js/tinymce/plugins/help/js/i18n/keynav/vi.js new file mode 100644 index 0000000..83d570d --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/vi.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.vi', +'

      Bắt đầu Ä‘iá»u hÆ°á»›ng bàn phím

      \n' + + '\n' + + '
      \n' + + '
      Tập trung vào thanh menu
      \n' + + '
      Windows hoặc Linux: Alt+F9
      \n' + + '
      macOS: ⌥F9
      \n' + + '
      Tập trung vào thanh công cụ
      \n' + + '
      Windows hoặc Linux: Alt+F10
      \n' + + '
      macOS: ⌥F10
      \n' + + '
      Tập trung vào chân trang
      \n' + + '
      Windows hoặc Linux: Alt+F11
      \n' + + '
      macOS: ⌥F11
      \n' + + '
      Tập trung vào thanh công cụ ngữ cảnh
      \n' + + '
      Windows, Linux hoặc macOS: Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      Äiá»u hÆ°á»›ng sẽ bắt đầu từ mục UI đầu tiên. Mục này sẽ được tô sáng hoặc có gạch dÆ°á»›i (nếu là mục đầu tiên trong\n' + + ' Ä‘Æ°á»ng dẫn phần tá»­ Chân trang).

      \n' + + '\n' + + '

      Di chuyển qua lại giữa các phần UI

      \n' + + '\n' + + '

      Äể di chuyển từ má»™t phần UI sang phần tiếp theo, ấn Tab.

      \n' + + '\n' + + '

      Äể di chuyển từ má»™t phần UI vá» phần trÆ°á»›c đó, ấn Shift+Tab.

      \n' + + '\n' + + '

      Thứ tự Tab của các phần UI này như sau:\n' + + '\n' + + '

        \n' + + '
      1. Thanh menu
      2. \n' + + '
      3. Từng nhóm thanh công cụ
      4. \n' + + '
      5. Thanh bên
      6. \n' + + '
      7. ÄÆ°á»ng dẫn phần tá»­ trong chân trang
      8. \n' + + '
      9. Nút chuyển đổi đếm chữ ở chân trang
      10. \n' + + '
      11. Liên kết thương hiệu ở chân trang
      12. \n' + + '
      13. Núm Ä‘iá»u tác chỉnh kích cỡ trình soạn thảo ở chân trang
      14. \n' + + '
      \n' + + '\n' + + '

      Nếu ngÆ°á»i dùng không thấy má»™t phần UI, thì có nghÄ©a phần đó bị bá» qua.

      \n' + + '\n' + + '

      Nếu ở chân trang có tính năng tập trung Ä‘iá»u hÆ°á»›ng bàn phím, mà không có thanh bên nào hiện hữu, thao tác ấn Shift+Tab\n' + + ' sẽ chuyển hÆ°á»›ng tập trung vào nhóm thanh công cụ đầu tiên, không phải cuối cùng.\n' + + '\n' + + '

      Di chuyển qua lại trong các phần UI

      \n' + + '\n' + + '

      Äể di chuyển từ má»™t phần tá»­ UI sang phần tiếp theo, ấn phím MÅ©i tên tÆ°Æ¡ng ứng cho phù hợp.

      \n' + + '\n' + + '

      Các phím mũi tên Trái và Phải

      \n' + + '\n' + + '
        \n' + + '
      • di chuyển giữa các menu trong thanh menu.
      • \n' + + '
      • mở menu phụ trong má»™t menu.
      • \n' + + '
      • di chuyển giữa các nút trong nhóm thanh công cụ.
      • \n' + + '
      • di chuyển giữa các mục trong Ä‘Æ°á»ng dẫn phần tá»­ của chân trang.
      • \n' + + '
      \n' + + '\n' + + '

      Các phím mũi tên Hướng xuống và Hướng lên\n' + + '\n' + + '

        \n' + + '
      • di chuyển giữa các mục menu trong menu.
      • \n' + + '
      • di chuyển giữa các mục trong menu thanh công cụ dạng bật lên.
      • \n' + + '
      \n' + + '\n' + + '

      Các phím mũi tên xoay vòng trong một phần UI tập trung.

      \n' + + '\n' + + '

      Äể đóng má»™t menu mở, má»™t menu phụ Ä‘ang mở, hoặc má»™t menu dạng bật lên Ä‘ang mở, hãy ấn phím Esc.\n' + + '\n' + + '

      Nếu trá»ng tâm hiện tại là ở phần “đầu†của má»™t phần UI cụ thể, thao tác ấn phím Esc cÅ©ng sẽ thoát\n' + + ' toàn bá»™ phần Ä‘iá»u hÆ°á»›ng bàn phím.

      \n' + + '\n' + + '

      Thực hiện chức năng của một mục menu hoặc nút thanh công cụ

      \n' + + '\n' + + '

      Khi mục menu hoặc nút thanh công cụ muốn dùng được tô sáng, hãy ấn Return, Enter,\n' + + ' hoặc Phím cách để thực hiện chức năng mục đó.\n' + + '\n' + + '

      Äiá»u hÆ°á»›ng giữa các há»™p thoại không có nhiá»u tab

      \n' + + '\n' + + '

      Trong các há»™p thoại không có nhiá»u tab, khi há»™p thoại mở ra, trá»ng tâm sẽ hÆ°á»›ng vào thành phần tÆ°Æ¡ng tác đầu tiên.

      \n' + + '\n' + + '

      Di chuyển giữa các thành phần hộp thoại tương tác bằng cách ấn Tab hoặc Shift+Tab.

      \n' + + '\n' + + '

      Äiá»u hÆ°á»›ng giữa các há»™p thoại có nhiá»u tab

      \n' + + '\n' + + '

      Trong các há»™p thoại có nhiá»u tab, khi há»™p thoại mở ra, trá»ng tâm sẽ hÆ°á»›ng vào nút đầu tiên trong menu tab.

      \n' + + '\n' + + '

      Di chuyển giữa các thành phần tương tác của tab hộp thoại này bằng cách ấn Tab hoặc\n' + + ' Shift+Tab.

      \n' + + '\n' + + '

      Chuyển sang má»™t tab há»™p thoại khác bằng cách chuyển trá»ng tâm vào menu tab, rồi ấn phím MÅ©i tên phù hợp\n' + + ' để xoay vòng các tab hiện có.

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/zh_CN.js b/static/js/tinymce/plugins/help/js/i18n/keynav/zh_CN.js new file mode 100644 index 0000000..41fe62c --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/zh_CN.js @@ -0,0 +1,84 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_CN', +'

      开始键盘导航

      \n' + + '\n' + + '
      \n' + + '
      èšç„¦äºŽèœå•æ 
      \n' + + '
      Windows 或 Linux:Alt+F9
      \n' + + '
      macOS:⌥F9
      \n' + + '
      èšç„¦äºŽå·¥å…·æ 
      \n' + + '
      Windows 或 Linux:Alt+F10
      \n' + + '
      macOS:⌥F10
      \n' + + '
      èšç„¦äºŽé¡µè„š
      \n' + + '
      Windows 或 Linux:Alt+F11
      \n' + + '
      macOS:⌥F11
      \n' + + '
      èšç„¦äºŽä¸Šä¸‹æ–‡å·¥å…·æ 
      \n' + + '
      Windowsã€Linux 或 macOS:Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      导航将在第一个 UI 项上开始,其中çªå‡ºæ˜¾ç¤ºè¯¥é¡¹ï¼Œæˆ–者对于页脚元素路径中的第一项,将为其添加下划线。

      \n' + + '\n' + + '

      在 UI 部分之间导航

      \n' + + '\n' + + '

      è¦ä»Žä¸€ä¸ª UI 部分移至下一个,请按 Tab。

      \n' + + '\n' + + '

      è¦ä»Žä¸€ä¸ª UI 部分移至上一个,请按 Shift+Tab。

      \n' + + '\n' + + '

      这些 UI 部分的 Tab 顺åºä¸ºï¼š\n' + + '\n' + + '

        \n' + + '
      1. èœå•æ 
      2. \n' + + '
      3. æ¯ä¸ªå·¥å…·æ ç»„
      4. \n' + + '
      5. è¾¹æ 
      6. \n' + + '
      7. 页脚中的元素路径
      8. \n' + + '
      9. 页脚中的字数切æ¢æŒ‰é’®
      10. \n' + + '
      11. 页脚中的å“牌链接
      12. \n' + + '
      13. 页脚中的编辑器调整大å°å›¾æŸ„
      14. \n' + + '
      \n' + + '\n' + + '

      如果ä¸å­˜åœ¨æŸä¸ª UI 部分,则跳过它。

      \n' + + '\n' + + '

      如果键盘导航焦点在页脚,并且没有å¯è§çš„è¾¹æ ï¼Œåˆ™æŒ‰ Shift+Tab 将焦点移至第一个工具æ ç»„而éžæœ€åŽä¸€ä¸ªã€‚\n' + + '\n' + + '

      在 UI 部分内导航

      \n' + + '\n' + + '

      è¦ä»Žä¸€ä¸ª UI 元素移至下一个,请按相应的箭头键。

      \n' + + '\n' + + '

      左和å³ç®­å¤´é”®

      \n' + + '\n' + + '
        \n' + + '
      • 在èœå•æ ä¸­çš„èœå•ä¹‹é—´ç§»åŠ¨ã€‚
      • \n' + + '
      • 打开èœå•ä¸­çš„å­èœå•ã€‚
      • \n' + + '
      • 在工具æ ç»„中的按钮之间移动。
      • \n' + + '
      • 在页脚的元素路径中的å„项之间移动。
      • \n' + + '
      \n' + + '\n' + + '

      下和上箭头键\n' + + '\n' + + '

        \n' + + '
      • 在èœå•ä¸­çš„èœå•é¡¹ä¹‹é—´ç§»åŠ¨ã€‚
      • \n' + + '
      • 在工具æ å¼¹å‡ºèœå•ä¸­çš„å„项之间移动。
      • \n' + + '
      \n' + + '\n' + + '

      箭头键在具有焦点的 UI 部分内循环。

      \n' + + '\n' + + '

      è¦å…³é—­æ‰“开的èœå•ã€æ‰“开的å­èœå•æˆ–打开的弹出èœå•ï¼Œè¯·æŒ‰ Esc 键。\n' + + '\n' + + '

      如果当å‰çš„焦点在特定 UI 部分的“顶部â€ï¼Œåˆ™æŒ‰ Esc 键还将完全退出键盘导航。

      \n' + + '\n' + + '

      执行èœå•é¡¹æˆ–工具æ æŒ‰é’®

      \n' + + '\n' + + '

      当çªå‡ºæ˜¾ç¤ºæ‰€éœ€çš„èœå•é¡¹æˆ–工具æ æŒ‰é’®æ—¶ï¼ŒæŒ‰ Returnã€Enter 或空格以执行该项。\n' + + '\n' + + '

      在éžæ ‡ç­¾é¡µå¼å¯¹è¯æ¡†ä¸­å¯¼èˆª

      \n' + + '\n' + + '

      在éžæ ‡ç­¾é¡µå¼å¯¹è¯æ¡†ä¸­ï¼Œå½“对è¯æ¡†æ‰“开时,第一个交互组件获得焦点。

      \n' + + '\n' + + '

      通过按 Tab 或 Shift+Tab,在交互对è¯æ¡†ç»„件之间导航。

      \n' + + '\n' + + '

      在标签页å¼å¯¹è¯æ¡†ä¸­å¯¼èˆª

      \n' + + '\n' + + '

      在标签页å¼å¯¹è¯æ¡†ä¸­ï¼Œå½“对è¯æ¡†æ‰“开时,标签页èœå•ä¸­çš„第一个按钮获得焦点。

      \n' + + '\n' + + '

      通过按 Tab 或 Shift+Tab,在此对è¯æ¡†çš„交互组件之间导航。

      \n' + + '\n' + + '

      通过将焦点移至å¦ä¸€å¯¹è¯æ¡†æ ‡ç­¾é¡µçš„èœå•ï¼Œç„¶åŽæŒ‰ç›¸åº”的箭头键以在å¯ç”¨çš„标签页间循环,从而切æ¢åˆ°è¯¥å¯¹è¯æ¡†æ ‡ç­¾é¡µã€‚

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/js/i18n/keynav/zh_TW.js b/static/js/tinymce/plugins/help/js/i18n/keynav/zh_TW.js new file mode 100644 index 0000000..26b3dd2 --- /dev/null +++ b/static/js/tinymce/plugins/help/js/i18n/keynav/zh_TW.js @@ -0,0 +1,90 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_TW', +'

      開始éµç›¤ç€è¦½

      \n' + + '\n' + + '
      \n' + + '
      跳至功能表列
      \n' + + '
      Windows 或 Linux:Alt+F9
      \n' + + '
      macOS:⌥F9
      \n' + + '
      跳至工具列
      \n' + + '
      Windows 或 Linux:Alt+F10
      \n' + + '
      macOS:⌥F10
      \n' + + '
      跳至é å°¾
      \n' + + '
      Windows 或 Linux:Alt+F11
      \n' + + '
      macOS:⌥F11
      \n' + + '
      跳至關è¯å¼å·¥å…·åˆ—
      \n' + + '
      Windowsã€Linux 或 macOS:Ctrl+F9\n' + + '
      \n' + + '\n' + + '

      ç€è¦½æœƒå¾žç¬¬ä¸€å€‹ UI 項目開始,該項目會å白顯示,但如果是「é å°¾ã€å…ƒç´ è·¯å¾‘的第一項,\n' + + ' 則加底線。

      \n' + + '\n' + + '

      在 UI å€æ®µä¹‹é–“ç€è¦½

      \n' + + '\n' + + '

      從 UI å€æ®µç§»è‡³ä¸‹ä¸€å€‹ï¼Œè«‹æŒ‰ Tab。

      \n' + + '\n' + + '

      從 UI å€æ®µç§»å›žä¸Šä¸€å€‹ï¼Œè«‹æŒ‰ Shift+Tab。

      \n' + + '\n' + + '

      這些 UI å€æ®µçš„ Tab é †åºå¦‚下:\n' + + '\n' + + '

        \n' + + '
      1. 功能表列
      2. \n' + + '
      3. å„個工具列群組
      4. \n' + + '
      5. å´é‚Šæ¬„
      6. \n' + + '
      7. é å°¾ä¸­çš„元素路徑
      8. \n' + + '
      9. é å°¾ä¸­å­—數切æ›æŒ‰éˆ•
      10. \n' + + '
      11. é å°¾ä¸­çš„å“牌連çµ
      12. \n' + + '
      13. é å°¾ä¸­ç·¨è¼¯å™¨èª¿æ•´å¤§å°æŽ§é»ž
      14. \n' + + '
      \n' + + '\n' + + '

      如果 UI å€æ®µæœªé¡¯ç¤ºï¼Œè¡¨ç¤ºå·²ç•¥éŽè©²å€æ®µã€‚

      \n' + + '\n' + + '

      如果éµç›¤ç€è¦½è·³è‡³é å°¾ï¼Œä½†æ²’有顯示å´é‚Šæ¬„,則按下 Shift+Tab\n' + + ' 會跳至第一個工具列群組,而ä¸æ˜¯æœ€å¾Œä¸€å€‹ã€‚\n' + + '\n' + + '

      在 UI å€æ®µä¹‹å…§ç€è¦½

      \n' + + '\n' + + '

      在兩個 UI 元素之間移動,請按é©ç•¶çš„æ–¹å‘éµã€‚

      \n' + + '\n' + + '

      å‘左和å‘å³æ–¹å‘éµ

      \n' + + '\n' + + '
        \n' + + '
      • 在功能表列中的功能表之間移動。
      • \n' + + '
      • 開啟功能表中的å­åŠŸèƒ½è¡¨ã€‚
      • \n' + + '
      • 在工具列群組中的按鈕之間移動。
      • \n' + + '
      • 在é å°¾çš„元素路徑中項目之間移動。
      • \n' + + '
      \n' + + '\n' + + '

      å‘下和å‘上方å‘éµ\n' + + '\n' + + '

        \n' + + '
      • 在功能表中的功能表項目之間移動。
      • \n' + + '
      • 在工具列快顯功能表中的項目之間移動。
      • \n' + + '
      \n' + + '\n' + + '

      æ–¹å‘éµæœƒåœ¨æ‰€è·³è‡³ UI å€æ®µä¹‹å…§å¾ªç’°ã€‚

      \n' + + '\n' + + '

      è‹¥è¦é—œé–‰å·²é–‹å•Ÿçš„功能表ã€å·²é–‹å•Ÿçš„å­åŠŸèƒ½è¡¨ï¼Œæˆ–已開啟的快顯功能表,請按 Esc éµã€‚\n' + + '\n' + + '

      如果目å‰å·²è·³è‡³ç‰¹å®š UI å€æ®µçš„「頂端ã€ï¼Œå‰‡æŒ‰ Esc éµä¹ŸæœƒçµæŸ\n' + + ' 整個éµç›¤ç€è¦½ã€‚

      \n' + + '\n' + + '

      執行功能表列項目或工具列按鈕

      \n' + + '\n' + + '

      當想è¦çš„功能表項目或工具列按鈕已å白顯示時,按 Returnã€Enterã€\n' + + ' 或空白éµå³å¯åŸ·è¡Œè©²é …目。\n' + + '\n' + + '

      ç€è¦½éžç´¢å¼•æ¨™ç±¤å¼å°è©±æ–¹å¡Š

      \n' + + '\n' + + '

      在éžç´¢å¼•æ¨™ç±¤å¼å°è©±æ–¹å¡Šä¸­ï¼Œé–‹å•Ÿå°è©±æ–¹å¡Šæ™‚會跳至第一個互動元件。

      \n' + + '\n' + + '

      按 Tab 或 Shift+Tab å³å¯åœ¨äº’å‹•å¼å°è©±æ–¹å¡Šå…ƒä»¶ä¹‹é–“ç€è¦½ã€‚

      \n' + + '\n' + + '

      ç€è¦½ç´¢å¼•æ¨™ç±¤å¼å°è©±æ–¹å¡Š

      \n' + + '\n' + + '

      在索引標籤å¼å°è©±æ–¹å¡Šä¸­ï¼Œé–‹å•Ÿå°è©±æ–¹å¡Šæ™‚會跳至索引標籤å¼åŠŸèƒ½è¡¨ä¸­çš„第一個按鈕。

      \n' + + '\n' + + '

      è‹¥è¦åœ¨æ­¤å°è©±æ–¹å¡Šçš„互動å¼å…ƒä»¶ä¹‹é–“ç€è¦½ï¼Œè«‹æŒ‰ Tab 或\n' + + ' Shift+Tab。

      \n' + + '\n' + + '

      先跳至索引標籤å¼åŠŸèƒ½è¡¨ï¼Œç„¶å¾ŒæŒ‰é©ç•¶çš„æ–¹å‘éµï¼Œå³å¯åˆ‡æ›è‡³å¦ä¸€å€‹å°è©±æ–¹å¡Šç´¢å¼•æ¨™ç±¤ï¼Œ\n' + + ' 以循環ç€è¦½å¯ç”¨çš„索引標籤。

      \n'); \ No newline at end of file diff --git a/static/js/tinymce/plugins/help/plugin.min.js b/static/js/tinymce/plugins/help/plugin.min.js new file mode 100644 index 0000000..58153fe --- /dev/null +++ b/static/js/tinymce/plugins/help/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");let t=0;const n=e=>{const n=(new Date).getTime(),a=Math.floor(1e9*Math.random());return t++,e+"_"+a+t+String(n)},a=e=>t=>t.options.get(e),r=a("help_tabs"),o=a("forced_plugins"),i=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=a=e,(r=String).prototype.isPrototypeOf(n)||(null===(o=a.constructor)||void 0===o?void 0:o.name)===r.name)?"string":t;var n,a,r,o})(e));const s=(void 0,e=>undefined===e);const l=e=>"function"==typeof e,c=(!1,()=>false);class m{constructor(e,t){this.tag=e,this.value=t}static some(e){return new m(!0,e)}static none(){return m.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?m.some(e(this.value)):m.none()}bind(e){return this.tag?e(this.value):m.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:m.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?m.none():m.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}m.singletonNone=new m(!1);const u=Array.prototype.slice,p=Array.prototype.indexOf,y=(e,t)=>{const n=e.length,a=new Array(n);for(let r=0;r{const n=[];for(let a=0,r=e.length;a{const n=u.call(e,0);return n.sort(t),n},g=Object.keys,k=Object.hasOwnProperty,v=(e,t)=>k.call(e,t);var b=tinymce.util.Tools.resolve("tinymce.Resource"),f=tinymce.util.Tools.resolve("tinymce.util.I18n");const A=(e,t)=>b.load(`tinymce.html-i18n.help-keynav.${t}`,`${e}/js/i18n/keynav/${t}.js`),C=e=>A(e,f.getCode()).catch((()=>A(e,"en")));var w=tinymce.util.Tools.resolve("tinymce.Env");const S=e=>{const t=w.os.isMacOS()||w.os.isiOS(),n=t?{alt:"⌥",ctrl:"⌃",shift:"⇧",meta:"⌘",access:"⌃⌥"}:{meta:"Ctrl ",access:"Shift + Alt "},a=e.split("+"),r=y(a,(e=>{const t=e.toLowerCase().trim();return v(n,t)?n[t]:e}));return t?r.join("").replace(/\s/,""):r.join("+")},M=[{shortcuts:["Meta + B"],action:"Bold"},{shortcuts:["Meta + I"],action:"Italic"},{shortcuts:["Meta + U"],action:"Underline"},{shortcuts:["Meta + A"],action:"Select all"},{shortcuts:["Meta + Y","Meta + Shift + Z"],action:"Redo"},{shortcuts:["Meta + Z"],action:"Undo"},{shortcuts:["Access + 1"],action:"Heading 1"},{shortcuts:["Access + 2"],action:"Heading 2"},{shortcuts:["Access + 3"],action:"Heading 3"},{shortcuts:["Access + 4"],action:"Heading 4"},{shortcuts:["Access + 5"],action:"Heading 5"},{shortcuts:["Access + 6"],action:"Heading 6"},{shortcuts:["Access + 7"],action:"Paragraph"},{shortcuts:["Access + 8"],action:"Div"},{shortcuts:["Access + 9"],action:"Address"},{shortcuts:["Alt + 0"],action:"Open help dialog"},{shortcuts:["Alt + F9"],action:"Focus to menubar"},{shortcuts:["Alt + F10"],action:"Focus to toolbar"},{shortcuts:["Alt + F11"],action:"Focus to element path"},{shortcuts:["Ctrl + F9"],action:"Focus to contextual toolbar"},{shortcuts:["Shift + Enter"],action:"Open popup menu for split buttons"},{shortcuts:["Meta + K"],action:"Insert link (if link plugin activated)"},{shortcuts:["Meta + S"],action:"Save (if save plugin activated)"},{shortcuts:["Meta + F"],action:"Find (if searchreplace plugin activated)"},{shortcuts:["Meta + Shift + F"],action:"Switch to or from fullscreen mode"}],T=()=>({name:"shortcuts",title:"Handy Shortcuts",items:[{type:"table",header:["Action","Shortcut"],cells:y(M,(e=>{const t=y(e.shortcuts,S).join(" or ");return[e.action,t]}))}]}),x=y([{key:"accordion",name:"Accordion"},{key:"advlist",name:"Advanced List"},{key:"anchor",name:"Anchor"},{key:"autolink",name:"Autolink"},{key:"autoresize",name:"Autoresize"},{key:"autosave",name:"Autosave"},{key:"charmap",name:"Character Map"},{key:"code",name:"Code"},{key:"codesample",name:"Code Sample"},{key:"colorpicker",name:"Color Picker"},{key:"directionality",name:"Directionality"},{key:"emoticons",name:"Emoticons"},{key:"fullscreen",name:"Full Screen"},{key:"help",name:"Help"},{key:"image",name:"Image"},{key:"importcss",name:"Import CSS"},{key:"insertdatetime",name:"Insert Date/Time"},{key:"link",name:"Link"},{key:"lists",name:"Lists"},{key:"media",name:"Media"},{key:"nonbreaking",name:"Nonbreaking"},{key:"pagebreak",name:"Page Break"},{key:"preview",name:"Preview"},{key:"quickbars",name:"Quick Toolbars"},{key:"save",name:"Save"},{key:"searchreplace",name:"Search and Replace"},{key:"table",name:"Table"},{key:"template",name:"Template"},{key:"textcolor",name:"Text Color"},{key:"visualblocks",name:"Visual Blocks"},{key:"visualchars",name:"Visual Characters"},{key:"wordcount",name:"Word Count"},{key:"a11ychecker",name:"Accessibility Checker",type:"premium"},{key:"advcode",name:"Advanced Code Editor",type:"premium"},{key:"advtable",name:"Advanced Tables",type:"premium"},{key:"advtemplate",name:"Advanced Templates",type:"premium",slug:"advanced-templates"},{key:"ai",name:"AI Assistant",type:"premium"},{key:"casechange",name:"Case Change",type:"premium"},{key:"checklist",name:"Checklist",type:"premium"},{key:"editimage",name:"Enhanced Image Editing",type:"premium"},{key:"footnotes",name:"Footnotes",type:"premium"},{key:"typography",name:"Advanced Typography",type:"premium",slug:"advanced-typography"},{key:"mediaembed",name:"Enhanced Media Embed",type:"premium",slug:"introduction-to-mediaembed"},{key:"export",name:"Export",type:"premium"},{key:"formatpainter",name:"Format Painter",type:"premium"},{key:"inlinecss",name:"Inline CSS",type:"premium",slug:"inline-css"},{key:"linkchecker",name:"Link Checker",type:"premium"},{key:"mentions",name:"Mentions",type:"premium"},{key:"mergetags",name:"Merge Tags",type:"premium"},{key:"pageembed",name:"Page Embed",type:"premium"},{key:"permanentpen",name:"Permanent Pen",type:"premium"},{key:"powerpaste",name:"PowerPaste",type:"premium",slug:"introduction-to-powerpaste"},{key:"rtc",name:"Real-Time Collaboration",type:"premium",slug:"rtc-introduction"},{key:"tinymcespellchecker",name:"Spell Checker Pro",type:"premium",slug:"introduction-to-tiny-spellchecker"},{key:"autocorrect",name:"Spelling Autocorrect",type:"premium"},{key:"tableofcontents",name:"Table of Contents",type:"premium"},{key:"tinycomments",name:"Tiny Comments",type:"premium",slug:"introduction-to-tiny-comments"},{key:"tinydrive",name:"Tiny Drive",type:"premium",slug:"tinydrive-introduction"}],(e=>({...e,type:e.type||"opensource",slug:e.slug||e.key}))),_=e=>{const t=e=>`${e.name}`,n=(e,n)=>{return(a=x,r=e=>e.key===n,((e,t,n)=>{for(let a=0,r=e.length;a((e,n)=>{const a=e.plugins[n].getMetadata;if(l(a)){const e=a();return{name:e.name,html:t(e)}}return{name:n,html:n}})(e,n)),(e=>{const n="premium"===e.type?`${e.name}*`:e.name;return{name:n,html:t({name:n,url:`https://www.tiny.cloud/docs/tinymce/6/${e.slug}/`})}}));var a,r},a=e=>{const t=(e=>{const t=g(e.plugins),n=o(e);return s(n)?t:h(t,(e=>!(((e,t)=>p.call(e,t))(n,e)>-1)))})(e),a=d(y(t,(t=>n(e,t))),((e,t)=>e.name.localeCompare(t.name))),r=y(a,(e=>"
    • "+e.html+"
    • ")),i=r.length,l=r.join("");return"

      "+f.translate(["Plugins installed ({0}):",i])+"

        "+l+"
      "},r={type:"htmlpanel",presets:"document",html:[(e=>null==e?"":"
      "+a(e)+"
      ")(e),(()=>{const e=h(x,(({type:e})=>"premium"===e)),t=d(y(e,(e=>e.name)),((e,t)=>e.localeCompare(t))),n=y(t,(e=>`
    • ${e}
    • `)).join("");return"

      "+f.translate("Premium plugins:")+"

      "})()].join("")};return{name:"plugins",title:"Plugins",items:[r]}};var O=tinymce.util.Tools.resolve("tinymce.EditorManager");const P=(e,t,a)=>()=>{(async(e,t,a)=>{const o=T(),s=await(async e=>({name:"keyboardnav",title:"Keyboard Navigation",items:[{type:"htmlpanel",presets:"document",html:await C(e)}]}))(a),l=_(e),c=(()=>{var e,t;const n='TinyMCE '+(e=O.majorVersion,t=O.minorVersion,(0===e.indexOf("@")?"X.X.X":e+"."+t)+"");return{name:"versions",title:"Version",items:[{type:"htmlpanel",html:"

      "+f.translate(["You are using {0}",n])+"

      ",presets:"document"}]}})(),u={[o.name]:o,[s.name]:s,[l.name]:l,[c.name]:c,...t.get()};return m.from(r(e)).fold((()=>(e=>{const t=g(e),n=t.indexOf("versions");return-1!==n&&(t.splice(n,1),t.push("versions")),{tabs:e,names:t}})(u)),(e=>((e,t)=>{const a={},r=y(e,(e=>{var r;if(i(e))return v(t,e)&&(a[e]=t[e]),e;{const t=null!==(r=e.name)&&void 0!==r?r:n("tab-name");return a[t]=e,t}}));return{tabs:a,names:r}})(e,u)))})(e,t,a).then((({tabs:t,names:n})=>{const a={type:"tabpanel",tabs:(e=>{const t=[],n=e=>{t.push(e)};for(let t=0;t{return v(n=t,a=e)?m.from(n[a]):m.none();var n,a})))};e.windowManager.open({title:"Help",size:"medium",body:a,buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:{}})}))};e.add("help",((e,t)=>{const a=(e=>{let t={};return{get:()=>t,set:e=>{t=e}}})(),r=(e=>({addTab:t=>{var a;const r=null!==(a=t.name)&&void 0!==a?a:n("tab-name"),o=e.get();o[r]=t,e.set(o)}}))(a);(e=>{(0,e.options.register)("help_tabs",{processor:"array"})})(e);const o=P(e,a,t);return((e,t)=>{e.ui.registry.addButton("help",{icon:"help",tooltip:"Help",onAction:t}),e.ui.registry.addMenuItem("help",{text:"Help",icon:"help",shortcut:"Alt+0",onAction:t})})(e,o),((e,t)=>{e.addCommand("mceHelp",t)})(e,o),e.shortcuts.add("Alt+0","Open help dialog","mceHelp"),((e,t)=>{e.on("init",(()=>{C(t)}))})(e,t),r}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/image/plugin.min.js b/static/js/tinymce/plugins/image/plugin.min.js new file mode 100644 index 0000000..905b475 --- /dev/null +++ b/static/js/tinymce/plugins/image/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=Object.getPrototypeOf,a=(e,t,a)=>{var i;return!!a(e,t.prototype)||(null===(i=e.constructor)||void 0===i?void 0:i.name)===t.name},i=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&a(e,String,((e,t)=>t.isPrototypeOf(e)))?"string":t})(t)===e,s=e=>t=>typeof t===e,r=i("string"),o=i("object"),n=e=>((e,i)=>o(e)&&a(e,i,((e,a)=>t(e)===a)))(e,Object),l=i("array"),c=(null,e=>null===e);const m=s("boolean"),d=e=>!(e=>null==e)(e),g=s("function"),u=s("number"),p=()=>{};class h{constructor(e,t){this.tag=e,this.value=t}static some(e){return new h(!0,e)}static none(){return h.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?h.some(e(this.value)):h.none()}bind(e){return this.tag?e(this.value):h.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:h.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return d(e)?h.some(e):h.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}h.singletonNone=new h(!1);const b=Object.keys,v=Object.hasOwnProperty,y=(e,t)=>v.call(e,t),f=Array.prototype.push,w=e=>{const t=[];for(let a=0,i=e.length;a{((e,t,a)=>{if(!(r(a)||m(a)||u(a)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",a,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,a+"")})(e.dom,t,a)},D=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},_=D;var C=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),I=tinymce.util.Tools.resolve("tinymce.util.URI");const U=e=>e.length>0,S=e=>t=>t.options.get(e),x=S("image_dimensions"),N=S("image_advtab"),T=S("image_uploadtab"),O=S("image_prepend_url"),E=S("image_class_list"),L=S("image_description"),j=S("image_title"),M=S("image_caption"),R=S("image_list"),k=S("a11y_advanced_options"),z=S("automatic_uploads"),P=(e,t)=>Math.max(parseInt(e,10),parseInt(t,10)),B=e=>(e&&(e=e.replace(/px$/,"")),e),F=e=>(e.length>0&&/^[0-9]+$/.test(e)&&(e+="px"),e),H=e=>"IMG"===e.nodeName&&(e.hasAttribute("data-mce-object")||e.hasAttribute("data-mce-placeholder")),G=(e,t)=>{const a=e.options.get;return I.isDomSafe(t,"img",{allow_html_data_urls:a("allow_html_data_urls"),allow_script_urls:a("allow_script_urls"),allow_svg_data_urls:a("allow_svg_data_urls")})},W=C.DOM,$=e=>e.style.marginLeft&&e.style.marginRight&&e.style.marginLeft===e.style.marginRight?B(e.style.marginLeft):"",V=e=>e.style.marginTop&&e.style.marginBottom&&e.style.marginTop===e.style.marginBottom?B(e.style.marginTop):"",K=e=>e.style.borderWidth?B(e.style.borderWidth):"",Z=(e,t)=>{var a;return e.hasAttribute(t)&&null!==(a=e.getAttribute(t))&&void 0!==a?a:""},q=e=>null!==e.parentNode&&"FIGURE"===e.parentNode.nodeName,J=(e,t,a)=>{""===a||null===a?e.removeAttribute(t):e.setAttribute(t,a)},Q=(e,t)=>{const a=e.getAttribute("style"),i=t(null!==a?a:"");i.length>0?(e.setAttribute("style",i),e.setAttribute("data-mce-style",i)):e.removeAttribute("style")},X=(e,t)=>(e,a,i)=>{const s=e.style;s[a]?(s[a]=F(i),Q(e,t)):J(e,a,i)},Y=(e,t)=>e.style[t]?B(e.style[t]):Z(e,t),ee=(e,t)=>{const a=F(t);e.style.marginLeft=a,e.style.marginRight=a},te=(e,t)=>{const a=F(t);e.style.marginTop=a,e.style.marginBottom=a},ae=(e,t)=>{const a=F(t);e.style.borderWidth=a},ie=(e,t)=>{e.style.borderStyle=t},se=e=>{var t;return null!==(t=e.style.borderStyle)&&void 0!==t?t:""},re=e=>d(e)&&"FIGURE"===e.nodeName,oe=e=>0===W.getAttrib(e,"alt").length&&"presentation"===W.getAttrib(e,"role"),ne=e=>oe(e)?"":Z(e,"alt"),le=(e,t)=>{var a;const i=document.createElement("img");return J(i,"style",t.style),($(i)||""!==t.hspace)&&ee(i,t.hspace),(V(i)||""!==t.vspace)&&te(i,t.vspace),(K(i)||""!==t.border)&&ae(i,t.border),(se(i)||""!==t.borderStyle)&&ie(i,t.borderStyle),e(null!==(a=i.getAttribute("style"))&&void 0!==a?a:"")},ce=(e,t)=>({src:Z(t,"src"),alt:ne(t),title:Z(t,"title"),width:Y(t,"width"),height:Y(t,"height"),class:Z(t,"class"),style:e(Z(t,"style")),caption:q(t),hspace:$(t),vspace:V(t),border:K(t),borderStyle:se(t),isDecorative:oe(t)}),me=(e,t,a,i,s)=>{a[i]!==t[i]&&s(e,i,String(a[i]))},de=(e,t,a)=>{if(a){W.setAttrib(e,"role","presentation");const t=_(e);A(t,"alt","")}else{if(c(t)){"alt",_(e).dom.removeAttribute("alt")}else{const a=_(e);A(a,"alt",t)}"presentation"===W.getAttrib(e,"role")&&W.setAttrib(e,"role","")}},ge=(e,t)=>(a,i,s)=>{e(a,s),Q(a,t)},ue=(e,t,a)=>{const i=ce(e,a);me(a,i,t,"caption",((e,t,a)=>(e=>{q(e)?(e=>{const t=e.parentNode;d(t)&&(W.insertAfter(e,t),W.remove(t))})(e):(e=>{const t=W.create("figure",{class:"image"});W.insertAfter(t,e),t.appendChild(e),t.appendChild(W.create("figcaption",{contentEditable:"true"},"Caption")),t.contentEditable="false"})(e)})(e))),me(a,i,t,"src",J),me(a,i,t,"title",J),me(a,i,t,"width",X(0,e)),me(a,i,t,"height",X(0,e)),me(a,i,t,"class",J),me(a,i,t,"style",ge(((e,t)=>J(e,"style",t)),e)),me(a,i,t,"hspace",ge(ee,e)),me(a,i,t,"vspace",ge(te,e)),me(a,i,t,"border",ge(ae,e)),me(a,i,t,"borderStyle",ge(ie,e)),((e,t,a)=>{a.alt===t.alt&&a.isDecorative===t.isDecorative||de(e,a.alt,a.isDecorative)})(a,i,t)},pe=(e,t)=>{const a=(e=>{if(e.margin){const t=String(e.margin).split(" ");switch(t.length){case 1:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[0],e["margin-bottom"]=e["margin-bottom"]||t[0],e["margin-left"]=e["margin-left"]||t[0];break;case 2:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[1],e["margin-bottom"]=e["margin-bottom"]||t[0],e["margin-left"]=e["margin-left"]||t[1];break;case 3:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[1],e["margin-bottom"]=e["margin-bottom"]||t[2],e["margin-left"]=e["margin-left"]||t[1];break;case 4:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[1],e["margin-bottom"]=e["margin-bottom"]||t[2],e["margin-left"]=e["margin-left"]||t[3]}delete e.margin}return e})(e.dom.styles.parse(t)),i=e.dom.styles.parse(e.dom.styles.serialize(a));return e.dom.styles.serialize(i)},he=e=>{const t=e.selection.getNode(),a=e.dom.getParent(t,"figure.image");return a?e.dom.select("img",a)[0]:t&&("IMG"!==t.nodeName||H(t))?null:t},be=(e,t)=>{var a;const i=e.dom,s=((t,a)=>{const i={};var s;return((e,t,a,i)=>{((e,t)=>{const a=b(e);for(let i=0,s=a.length;i{(t(e,s)?a:i)(e,s)}))})(t,((t,a)=>!e.schema.isValidChild(a,"figure")),(s=i,(e,t)=>{s[t]=e}),p),i})(e.schema.getTextBlockElements()),r=i.getParent(t.parentNode,(e=>{return t=s,a=e.nodeName,y(t,a)&&void 0!==t[a]&&null!==t[a];var t,a}),e.getBody());return r&&null!==(a=i.split(r,t))&&void 0!==a?a:t},ve=(e,t)=>{const a=((t,a)=>{const i=document.createElement("img");if(ue((t=>pe(e,t)),{...a,caption:!1},i),de(i,a.alt,a.isDecorative),a.caption){const e=W.create("figure",{class:"image"});return e.appendChild(i),e.appendChild(W.create("figcaption",{contentEditable:"true"},"Caption")),e.contentEditable="false",e}return i})(0,t);e.dom.setAttrib(a,"data-mce-id","__mcenew"),e.focus(),e.selection.setContent(a.outerHTML);const i=e.dom.select('*[data-mce-id="__mcenew"]')[0];if(e.dom.setAttrib(i,"data-mce-id",null),re(i)){const t=be(e,i);e.selection.select(t)}else e.selection.select(i)},ye=(e,t)=>{const a=he(e);if(a){const i={...ce((t=>pe(e,t)),a),...t},s=((e,t)=>{const a=t.src;return{...t,src:G(e,a)?a:""}})(e,i);i.src?((e,t)=>{const a=he(e);if(a)if(ue((t=>pe(e,t)),t,a),((e,t)=>{e.dom.setAttrib(t,"src",t.getAttribute("src"))})(e,a),re(a.parentNode)){const t=a.parentNode;be(e,t),e.selection.select(a.parentNode)}else e.selection.select(a),((e,t,a)=>{const i=()=>{a.onload=a.onerror=null,e.selection&&(e.selection.select(a),e.nodeChanged())};a.onload=()=>{t.width||t.height||!x(e)||e.dom.setAttribs(a,{width:String(a.clientWidth),height:String(a.clientHeight)}),i()},a.onerror=i})(e,t,a)})(e,s):((e,t)=>{if(t){const a=e.dom.is(t.parentNode,"figure.image")?t.parentNode:t;e.dom.remove(a),e.focus(),e.nodeChanged(),e.dom.isEmpty(e.getBody())&&(e.setContent(""),e.selection.setCursorLocation())}})(e,a)}else t.src&&ve(e,{src:"",alt:"",title:"",width:"",height:"",class:"",style:"",caption:!1,hspace:"",vspace:"",border:"",borderStyle:"",isDecorative:!1,...t})},fe=(we=(e,t)=>n(e)&&n(t)?fe(e,t):t,(...e)=>{if(0===e.length)throw new Error("Can't merge zero objects");const t={};for(let a=0;ar(e.value)?e.value:"",Ce=(e,t)=>{const a=[];return De.each(e,(e=>{const i=(e=>r(e.text)?e.text:r(e.title)?e.title:"")(e);if(void 0!==e.menu){const s=Ce(e.menu,t);a.push({text:i,items:s})}else{const s=t(e);a.push({text:i,value:s})}})),a},Ie=(e=_e)=>t=>t?h.from(t).map((t=>Ce(t,e))):h.none(),Ue=(e,t)=>((e,a)=>{for(let a=0;ay(e,"items"))(i=e[a])?Ue(i.items,t):i.value===t?h.some(i):h.none();if(s.isSome())return s}var i;return h.none()})(e),Se=Ie,xe=(e,t)=>e.bind((e=>Ue(e,t))),Ne=e=>{const t=Se((t=>e.convertURL(t.value||t.url||"","src"))),a=new Promise((a=>{((e,t)=>{const a=R(e);r(a)?fetch(a).then((e=>{e.ok&&e.json().then(t)})):g(a)?a(t):t(a)})(e,(e=>{a(t(e).map((e=>w([[{text:"None",value:""}],e]))))}))})),i=(A=E(e),Ie(_e)(A)),s=N(e),o=T(e),n=(e=>U(e.options.get("images_upload_url")))(e),l=(e=>d(e.options.get("images_upload_handler")))(e),c=(e=>{const t=he(e);return t?ce((t=>pe(e,t)),t):{src:"",alt:"",title:"",width:"",height:"",class:"",style:"",caption:!1,hspace:"",vspace:"",border:"",borderStyle:"",isDecorative:!1}})(e),m=L(e),u=j(e),p=x(e),b=M(e),v=k(e),y=z(e),f=h.some(O(e)).filter((e=>r(e)&&e.length>0));var A;return a.then((e=>({image:c,imageList:e,classList:i,hasAdvTab:s,hasUploadTab:o,hasUploadUrl:n,hasUploadHandler:l,hasDescription:m,hasImageTitle:u,hasDimensions:p,hasImageCaption:b,prependURL:f,hasAccessibilityOptions:v,automaticUploads:y})))},Te=e=>{const t=e.imageList.map((e=>({name:"images",type:"listbox",label:"Image list",items:e}))),a={name:"alt",type:"input",label:"Alternative description",enabled:!(e.hasAccessibilityOptions&&e.image.isDecorative)},i=e.classList.map((e=>({name:"classes",type:"listbox",label:"Class",items:e})));return w([[{name:"src",type:"urlinput",filetype:"image",label:"Source"}],t.toArray(),e.hasAccessibilityOptions&&e.hasDescription?[{type:"label",label:"Accessibility",items:[{name:"isDecorative",type:"checkbox",label:"Image is decorative"}]}]:[],e.hasDescription?[a]:[],e.hasImageTitle?[{name:"title",type:"input",label:"Image title"}]:[],e.hasDimensions?[{name:"dimensions",type:"sizeinput"}]:[],[{...(s=e.classList.isSome()&&e.hasImageCaption,s?{type:"grid",columns:2}:{type:"panel"}),items:w([i.toArray(),e.hasImageCaption?[{type:"label",label:"Caption",items:[{type:"checkbox",name:"caption",label:"Show caption"}]}]:[]])}]]);var s},Oe=e=>({title:"General",name:"general",items:Te(e)}),Ee=Te,Le=e=>({src:{value:e.src,meta:{}},images:e.src,alt:e.alt,title:e.title,dimensions:{width:e.width,height:e.height},classes:e.class,caption:e.caption,style:e.style,vspace:e.vspace,border:e.border,hspace:e.hspace,borderstyle:e.borderStyle,fileinput:[],isDecorative:e.isDecorative}),je=(e,t)=>({src:e.src.value,alt:null!==e.alt&&0!==e.alt.length||!t?e.alt:null,title:e.title,width:e.dimensions.width,height:e.dimensions.height,class:e.classes,style:e.style,caption:e.caption,hspace:e.hspace,vspace:e.vspace,border:e.border,borderStyle:e.borderstyle,isDecorative:e.isDecorative}),Me=(e,t,a,i)=>{((e,t)=>{const a=t.getData();((e,t)=>/^(?:[a-zA-Z]+:)?\/\//.test(t)?h.none():e.prependURL.bind((e=>t.substring(0,e.length)!==e?h.some(e+t):h.none())))(e,a.src.value).each((e=>{t.setData({src:{value:e,meta:a.src.meta}})}))})(t,i),((e,t)=>{const a=t.getData(),i=a.src.meta;if(void 0!==i){const s=fe({},a);((e,t,a)=>{e.hasDescription&&r(a.alt)&&(t.alt=a.alt),e.hasAccessibilityOptions&&(t.isDecorative=a.isDecorative||t.isDecorative||!1),e.hasImageTitle&&r(a.title)&&(t.title=a.title),e.hasDimensions&&(r(a.width)&&(t.dimensions.width=a.width),r(a.height)&&(t.dimensions.height=a.height)),r(a.class)&&xe(e.classList,a.class).each((e=>{t.classes=e.value})),e.hasImageCaption&&m(a.caption)&&(t.caption=a.caption),e.hasAdvTab&&(r(a.style)&&(t.style=a.style),r(a.vspace)&&(t.vspace=a.vspace),r(a.border)&&(t.border=a.border),r(a.hspace)&&(t.hspace=a.hspace),r(a.borderstyle)&&(t.borderstyle=a.borderstyle))})(e,s,i),t.setData(s)}})(t,i),((e,t,a,i)=>{const s=i.getData(),r=s.src.value,o=s.src.meta||{};o.width||o.height||!t.hasDimensions||(U(r)?e.imageSize(r).then((e=>{a.open&&i.setData({dimensions:e})})).catch((e=>console.error(e))):i.setData({dimensions:{width:"",height:""}}))})(e,t,a,i),((e,t,a)=>{const i=a.getData(),s=xe(e.imageList,i.src.value);t.prevImage=s,a.setData({images:s.map((e=>e.value)).getOr("")})})(t,a,i)},Re=(e,t,a,i)=>{const s=i.getData();var r;i.block("Uploading image"),(r=s.fileinput,((e,t)=>0{i.unblock()}),(s=>{const r=URL.createObjectURL(s),o=()=>{i.unblock(),URL.revokeObjectURL(r)},n=s=>{i.setData({src:{value:s,meta:{}}}),i.showTab("general"),Me(e,t,a,i)};var l;(l=s,new Promise(((e,t)=>{const a=new FileReader;a.onload=()=>{e(a.result)},a.onerror=()=>{var e;t(null===(e=a.error)||void 0===e?void 0:e.message)},a.readAsDataURL(l)}))).then((a=>{const l=e.createBlobCache(s,r,a);t.automaticUploads?e.uploadImage(l).then((e=>{n(e.url),o()})).catch((t=>{o(),e.alertErr(t)})):(e.addToBlobCache(l),n(l.blobUri()),i.unblock())}))}))},ke=(e,t,a)=>(i,s)=>{"src"===s.name?Me(e,t,a,i):"images"===s.name?((e,t,a,i)=>{const s=i.getData(),r=xe(t.imageList,s.images);r.each((e=>{const t=""===s.alt||a.prevImage.map((e=>e.text===s.alt)).getOr(!1);t?""===e.value?i.setData({src:e,alt:a.prevAlt}):i.setData({src:e,alt:e.text}):i.setData({src:e})})),a.prevImage=r,Me(e,t,a,i)})(e,t,a,i):"alt"===s.name?a.prevAlt=i.getData().alt:"fileinput"===s.name?Re(e,t,a,i):"isDecorative"===s.name&&i.setEnabled("alt",!i.getData().isDecorative)},ze=e=>()=>{e.open=!1},Pe=e=>e.hasAdvTab||e.hasUploadUrl||e.hasUploadHandler?{type:"tabpanel",tabs:w([[Oe(e)],e.hasAdvTab?[{title:"Advanced",name:"advanced",items:[{type:"grid",columns:2,items:[{type:"input",label:"Vertical space",name:"vspace",inputMode:"numeric"},{type:"input",label:"Horizontal space",name:"hspace",inputMode:"numeric"},{type:"input",label:"Border width",name:"border",inputMode:"numeric"},{type:"listbox",name:"borderstyle",label:"Border style",items:[{text:"Select...",value:""},{text:"Solid",value:"solid"},{text:"Dotted",value:"dotted"},{text:"Dashed",value:"dashed"},{text:"Double",value:"double"},{text:"Groove",value:"groove"},{text:"Ridge",value:"ridge"},{text:"Inset",value:"inset"},{text:"Outset",value:"outset"},{text:"None",value:"none"},{text:"Hidden",value:"hidden"}]}]}]}]:[],e.hasUploadTab&&(e.hasUploadUrl||e.hasUploadHandler)?[{title:"Upload",name:"upload",items:[{type:"dropzone",name:"fileinput"}]}]:[]])}:{type:"panel",items:Ee(e)},Be=(e,t,a)=>i=>{const s=fe(Le(t.image),i.getData()),r={...s,style:le(a.normalizeCss,je(s,!1))};e.execCommand("mceUpdateImage",!1,je(r,t.hasAccessibilityOptions)),e.editorUpload.uploadImagesAuto(),i.close()},Fe=e=>t=>G(e,t)?(e=>new Promise((t=>{const a=document.createElement("img"),i=e=>{a.onload=a.onerror=null,a.parentNode&&a.parentNode.removeChild(a),t(e)};a.onload=()=>{const e={width:P(a.width,a.clientWidth),height:P(a.height,a.clientHeight)};i(Promise.resolve(e))},a.onerror=()=>{i(Promise.reject(`Failed to get image dimensions for: ${e}`))};const s=a.style;s.visibility="hidden",s.position="fixed",s.bottom=s.left="0px",s.width=s.height="auto",document.body.appendChild(a),a.src=e})))(e.documentBaseURI.toAbsolute(t)).then((e=>({width:String(e.width),height:String(e.height)}))):Promise.resolve({width:"",height:""}),He=e=>(t,a,i)=>{var s;return e.editorUpload.blobCache.create({blob:t,blobUri:a,name:null===(s=t.name)||void 0===s?void 0:s.replace(/\.[^\.]+$/,""),filename:t.name,base64:i.split(",")[1]})},Ge=e=>t=>{e.editorUpload.blobCache.add(t)},We=e=>t=>{e.windowManager.alert(t)},$e=e=>t=>pe(e,t),Ve=e=>t=>e.dom.parseStyle(t),Ke=e=>(t,a)=>e.dom.serializeStyle(t,a),Ze=e=>t=>Ae(e).upload([t],!1).then((e=>{var t;return 0===e.length?Promise.reject("Failed to upload image"):!1===e[0].status?Promise.reject(null===(t=e[0].error)||void 0===t?void 0:t.message):e[0]})),qe=e=>{const t={imageSize:Fe(e),addToBlobCache:Ge(e),createBlobCache:He(e),alertErr:We(e),normalizeCss:$e(e),parseStyle:Ve(e),serializeStyle:Ke(e),uploadImage:Ze(e)};return{open:()=>{Ne(e).then((a=>{const i=(e=>({prevImage:xe(e.imageList,e.image.src),prevAlt:e.image.alt,open:!0}))(a);return{title:"Insert/Edit Image",size:"normal",body:Pe(a),buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:Le(a.image),onSubmit:Be(e,a,t),onChange:ke(t,a,i),onClose:ze(i)}})).then(e.windowManager.open)}}},Je=e=>{const t=e.attr("class");return d(t)&&/\bimage\b/.test(t)},Qe=e=>t=>{let a=t.length;const i=t=>{t.attr("contenteditable",e?"true":null)};for(;a--;){const s=t[a];Je(s)&&(s.attr("contenteditable",e?"false":null),De.each(s.getAll("figcaption"),i))}},Xe=e=>t=>{const a=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("image",(e=>{(e=>{const t=e.options.register;t("image_dimensions",{processor:"boolean",default:!0}),t("image_advtab",{processor:"boolean",default:!1}),t("image_uploadtab",{processor:"boolean",default:!0}),t("image_prepend_url",{processor:"string",default:""}),t("image_class_list",{processor:"object[]"}),t("image_description",{processor:"boolean",default:!0}),t("image_title",{processor:"boolean",default:!1}),t("image_caption",{processor:"boolean",default:!1}),t("image_list",{processor:e=>{const t=!1===e||r(e)||((e,t)=>{if(l(e)){for(let a=0,i=e.length;a{e.on("PreInit",(()=>{e.parser.addNodeFilter("figure",Qe(!0)),e.serializer.addNodeFilter("figure",Qe(!1))}))})(e),(e=>{e.ui.registry.addToggleButton("image",{icon:"image",tooltip:"Insert/edit image",onAction:qe(e).open,onSetup:t=>{t.setActive(d(he(e)));const a=e.selection.selectorChangedWithUnbind("img:not([data-mce-object]):not([data-mce-placeholder]),figure.image",t.setActive).unbind,i=Xe(e)(t);return()=>{a(),i()}}}),e.ui.registry.addMenuItem("image",{icon:"image",text:"Image...",onAction:qe(e).open,onSetup:Xe(e)}),e.ui.registry.addContextMenu("image",{update:t=>e.selection.isEditable()&&(re(t)||"IMG"===t.nodeName&&!H(t))?["image"]:[]})})(e),(e=>{e.addCommand("mceImage",qe(e).open),e.addCommand("mceUpdateImage",((t,a)=>{e.undoManager.transact((()=>ye(e,a)))}))})(e)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/importcss/plugin.min.js b/static/js/tinymce/plugins/importcss/plugin.min.js new file mode 100644 index 0000000..9afd216 --- /dev/null +++ b/static/js/tinymce/plugins/importcss/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(s=r=e,(o=String).prototype.isPrototypeOf(s)||(null===(n=r.constructor)||void 0===n?void 0:n.name)===o.name)?"string":t;var s,r,o,n})(t)===e,s=t("string"),r=t("object"),o=t("array"),n=("function",e=>"function"==typeof e);var c=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),i=tinymce.util.Tools.resolve("tinymce.EditorManager"),l=tinymce.util.Tools.resolve("tinymce.Env"),a=tinymce.util.Tools.resolve("tinymce.util.Tools");const p=e=>t=>t.options.get(e),u=p("importcss_merge_classes"),m=p("importcss_exclusive"),f=p("importcss_selector_converter"),y=p("importcss_selector_filter"),d=p("importcss_groups"),h=p("importcss_append"),_=p("importcss_file_filter"),g=p("skin"),v=p("skin_url"),b=Array.prototype.push,x=/^\.(?:ephox|tiny-pageembed|mce)(?:[.-]+\w+)+$/,T=e=>s(e)?t=>-1!==t.indexOf(e):e instanceof RegExp?t=>e.test(t):e,S=(e,t)=>{let s={};const r=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(t);if(!r)return;const o=r[1],n=r[2].substr(1).split(".").join(" "),c=a.makeMap("a,img");return r[1]?(s={title:t},e.schema.getTextBlockElements()[o]?s.block=o:e.schema.getBlockElements()[o]||c[o.toLowerCase()]?s.selector=o:s.inline=o):r[2]&&(s={inline:"span",title:t.substr(1),classes:n}),u(e)?s.classes=n:s.attributes={class:n},s},k=(e,t)=>null===t||m(e),w=e=>{e.on("init",(()=>{const t=(()=>{const e=[],t=[],s={};return{addItemToGroup:(e,r)=>{s[e]?s[e].push(r):(t.push(e),s[e]=[r])},addItem:t=>{e.push(t)},toFormats:()=>{return(r=t,n=e=>{const t=s[e];return 0===t.length?[]:[{title:e,items:t}]},(e=>{const t=[];for(let s=0,r=e.length;s{const s=e.length,r=new Array(s);for(let o=0;oa.map(e,(e=>a.extend({},e,{original:e,selectors:{},filter:T(e.filter)}))))(d(e)),u=(t,s)=>{if(((e,t,s,r)=>!(k(e,s)?t in r:t in s.selectors))(e,t,s,r)){((e,t,s,r)=>{k(e,s)?r[t]=!0:s.selectors[t]=!0})(e,t,s,r);const o=((e,t,s,r)=>{let o;const n=f(e);return o=r&&r.selector_converter?r.selector_converter:n||(()=>S(e,s)),o.call(t,s,r)})(e,e.plugins.importcss,t,s);if(o){const t=o.name||c.DOM.uniqueId();return e.formatter.register(t,o),{title:o.title,format:t}}}return null};a.each(((e,t,r)=>{const o=[],n={},c=(t,n)=>{let p,u=t.href;if(u=(e=>{const t=l.cacheSuffix;return s(e)&&(e=e.replace("?"+t,"").replace("&"+t,"")),e})(u),u&&(!r||r(u,n))&&!((e,t)=>{const s=g(e);if(s){const r=v(e),o=r?e.documentBaseURI.toAbsolute(r):i.baseURL+"/skins/ui/"+s,n=i.baseURL+"/skins/content/";return t===o+"/content"+(e.inline?".inline":"")+".min.css"||-1!==t.indexOf(n)}return!1})(e,u)){a.each(t.imports,(e=>{c(e,!0)}));try{p=t.cssRules||t.rules}catch(e){}a.each(p,(e=>{e.styleSheet?c(e.styleSheet,!0):e.selectorText&&a.each(e.selectorText.split(","),(e=>{o.push(a.trim(e))}))}))}};a.each(e.contentCSS,(e=>{n[e]=!0})),r||(r=(e,t)=>t||n[e]);try{a.each(t.styleSheets,(e=>{c(e)}))}catch(e){}return o})(e,e.getDoc(),T(_(e))),(e=>{if(!x.test(e)&&(!n||n(e))){const s=((e,t)=>a.grep(e,(e=>!e.filter||e.filter(t))))(p,e);if(s.length>0)a.each(s,(s=>{const r=u(e,s);r&&t.addItemToGroup(s.title,r)}));else{const s=u(e,null);s&&t.addItem(s)}}}));const m=t.toFormats();e.dispatch("addStyleModifications",{items:m,replace:!h(e)})}))};e.add("importcss",(e=>((e=>{const t=e.options.register,o=e=>s(e)||n(e)||r(e);t("importcss_merge_classes",{processor:"boolean",default:!0}),t("importcss_exclusive",{processor:"boolean",default:!0}),t("importcss_selector_converter",{processor:"function"}),t("importcss_selector_filter",{processor:o}),t("importcss_file_filter",{processor:o}),t("importcss_groups",{processor:"object[]"}),t("importcss_append",{processor:"boolean",default:!1})})(e),w(e),(e=>({convertSelectorToFormat:t=>S(e,t)}))(e))))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/insertdatetime/plugin.min.js b/static/js/tinymce/plugins/insertdatetime/plugin.min.js new file mode 100644 index 0000000..e0c4d38 --- /dev/null +++ b/static/js/tinymce/plugins/insertdatetime/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),a=t("insertdatetime_dateformat"),n=t("insertdatetime_timeformat"),r=t("insertdatetime_formats"),s=t("insertdatetime_element"),i="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),o="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),l="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),m="January February March April May June July August September October November December".split(" "),c=(e,t)=>{if((e=""+e).length(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",c(a.getMonth()+1,2))).replace("%d",c(a.getDate(),2))).replace("%H",""+c(a.getHours(),2))).replace("%M",""+c(a.getMinutes(),2))).replace("%S",""+c(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(m[a.getMonth()]))).replace("%b",""+e.translate(l[a.getMonth()]))).replace("%A",""+e.translate(o[a.getDay()]))).replace("%a",""+e.translate(i[a.getDay()]))).replace("%%","%"),u=(e,t)=>{if(s(e)){const a=d(e,t);let n;n=/%[HMSIp]/.test(t)?d(e,"%Y-%m-%dT%H:%M"):d(e,"%Y-%m-%d");const r=e.dom.getParent(e.selection.getStart(),"time");r?((e,t,a,n)=>{const r=e.dom.create("time",{datetime:a},n);e.dom.replace(r,t),e.selection.select(r,!0),e.selection.collapse(!1)})(e,r,n,a):e.insertContent('")}else e.insertContent(d(e,t))};var p=tinymce.util.Tools.resolve("tinymce.util.Tools");const g=e=>t=>{const a=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("insertdatetime",(e=>{(e=>{const t=e.options.register;t("insertdatetime_dateformat",{processor:"string",default:e.translate("%Y-%m-%d")}),t("insertdatetime_timeformat",{processor:"string",default:e.translate("%H:%M:%S")}),t("insertdatetime_formats",{processor:"string[]",default:["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"]}),t("insertdatetime_element",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mceInsertDate",((t,n)=>{u(e,null!=n?n:a(e))})),e.addCommand("mceInsertTime",((t,a)=>{u(e,null!=a?a:n(e))}))})(e),(e=>{const t=r(e),a=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})((e=>{const t=r(e);return t.length>0?t[0]:n(e)})(e)),s=t=>e.execCommand("mceInsertDate",!1,t);e.ui.registry.addSplitButton("insertdatetime",{icon:"insert-time",tooltip:"Insert date/time",select:e=>e===a.get(),fetch:a=>{a(p.map(t,(t=>({type:"choiceitem",text:d(e,t),value:t}))))},onAction:e=>{s(a.get())},onItemAction:(e,t)=>{a.set(t),s(t)},onSetup:g(e)});const i=e=>()=>{a.set(e),s(e)};e.ui.registry.addNestedMenuItem("insertdatetime",{icon:"insert-time",text:"Date/time",getSubmenuItems:()=>p.map(t,(t=>({type:"menuitem",text:d(e,t),onAction:i(t)}))),onSetup:g(e)})})(e)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/link/plugin.min.js b/static/js/tinymce/plugins/link/plugin.min.js new file mode 100644 index 0000000..fa1f873 --- /dev/null +++ b/static/js/tinymce/plugins/link/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(l=o.constructor)||void 0===l?void 0:l.name)===r.name)?"string":t;var n,o,r,l})(t)===e,n=e=>t=>typeof t===e,o=t("string"),r=t("object"),l=t("array"),a=(null,e=>null===e);const i=n("boolean"),s=e=>!(e=>null==e)(e),c=n("function"),u=(e,t)=>{if(l(e)){for(let n=0,o=e.length;n{},d=(e,t)=>e===t;class m{constructor(e,t){this.tag=e,this.value=t}static some(e){return new m(!0,e)}static none(){return m.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?m.some(e(this.value)):m.none()}bind(e){return this.tag?e(this.value):m.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:m.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return s(e)?m.some(e):m.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}m.singletonNone=new m(!1);const h=Array.prototype.indexOf,f=Array.prototype.push,p=e=>{const t=[];for(let n=0,o=e.length;n{for(let n=0;ne.exists((e=>n(e,t))),y=e=>{const t=[],n=e=>{t.push(e)};for(let t=0;te?m.some(t):m.none(),b=e=>t=>t.options.get(e),_=b("link_assume_external_targets"),w=b("link_context_toolbar"),C=b("link_list"),O=b("link_default_target"),N=b("link_default_protocol"),A=b("link_target_list"),S=b("link_rel_list"),E=b("link_class_list"),T=b("link_title"),R=b("allow_unsafe_link_target"),P=b("link_quicklink");var L=tinymce.util.Tools.resolve("tinymce.util.Tools");const M=e=>o(e.value)?e.value:"",D=(e,t)=>{const n=[];return L.each(e,(e=>{const r=(e=>o(e.text)?e.text:o(e.title)?e.title:"")(e);if(void 0!==e.menu){const o=D(e.menu,t);n.push({text:r,items:o})}else{const o=t(e);n.push({text:r,value:o})}})),n},B=(e=M)=>t=>m.from(t).map((t=>D(t,e))),I=e=>B(M)(e),j=B,K=(e,t)=>n=>({name:e,type:"listbox",label:t,items:n}),U=M,q=Object.keys,F=Object.hasOwnProperty,V=(e,t)=>F.call(e,t);var $=tinymce.util.Tools.resolve("tinymce.dom.TreeWalker"),z=tinymce.util.Tools.resolve("tinymce.util.URI");const G=e=>s(e)&&"a"===e.nodeName.toLowerCase(),H=e=>G(e)&&!!Q(e),J=(e,t)=>{if(e.collapsed)return[];{const n=e.cloneContents(),o=n.firstChild,r=new $(o,n),l=[];let a=o;do{t(a)&&l.push(a)}while(a=r.next());return l}},W=e=>/^\w+:/i.test(e),Q=e=>{var t,n;return null!==(n=null!==(t=e.getAttribute("data-mce-href"))&&void 0!==t?t:e.getAttribute("href"))&&void 0!==n?n:""},X=(e,t)=>{const n=["noopener"],o=e?e.split(/\s+/):[],r=e=>e.filter((e=>-1===L.inArray(n,e))),l=t?(e=>(e=r(e)).length>0?e.concat(n):n)(o):r(o);return l.length>0?(e=>L.trim(e.sort().join(" ")))(l):""},Y=(e,t)=>(t=t||te(e.selection.getRng())[0]||e.selection.getNode(),le(t)?m.from(e.dom.select("a[href]",t)[0]):m.from(e.dom.getParent(t,"a[href]"))),Z=(e,t)=>Y(e,t).isSome(),ee=(e,t)=>t.fold((()=>e.getContent({format:"text"})),(e=>e.innerText||e.textContent||"")).replace(/\uFEFF/g,""),te=e=>J(e,H),ne=e=>L.grep(e,H),oe=e=>ne(e).length>0,re=e=>{const t=e.schema.getTextInlineElements();if(Y(e).exists((e=>e.hasAttribute("data-mce-block"))))return!1;const n=e.selection.getRng();return!!n.collapsed||0===J(n,(e=>1===e.nodeType&&!G(e)&&!V(t,e.nodeName.toLowerCase()))).length},le=e=>s(e)&&"FIGURE"===e.nodeName&&/\bimage\b/i.test(e.className),ae=(e,t,n)=>{const o=e.selection.getNode(),r=Y(e,o),l=((e,t)=>{const n={...t};if(0===S(e).length&&!R(e)){const e=X(n.rel,"_blank"===n.target);n.rel=e||null}return m.from(n.target).isNone()&&!1===A(e)&&(n.target=O(e)),n.href=((e,t)=>"http"!==t&&"https"!==t||W(e)?e:t+"://"+e)(n.href,_(e)),n})(e,(e=>{return t=["title","rel","class","target"],n=(t,n)=>(e[n].each((e=>{t[n]=e.length>0?e:null})),t),o={href:e.href},((e,t)=>{for(let n=0,o=e.length;n{o=n(o,e)})),o;var t,n,o})(n));e.undoManager.transact((()=>{n.href===t.href&&t.attach(),r.fold((()=>{((e,t,n,o)=>{const r=e.dom;le(t)?ge(r,t,o):n.fold((()=>{e.execCommand("mceInsertLink",!1,o)}),(t=>{e.insertContent(r.createHTML("a",o,r.encode(t)))}))})(e,o,n.text,l)}),(t=>{e.focus(),((e,t,n,o)=>{n.each((e=>{V(t,"innerText")?t.innerText=e:t.textContent=e})),e.dom.setAttribs(t,o),e.selection.select(t)})(e,t,n.text,l)}))}))},ie=e=>{const{class:t,href:n,rel:o,target:r,text:l,title:i}=e;return((e,t)=>{const n={};var o;return((e,t,n,o)=>{((e,t)=>{const n=q(e);for(let o=0,r=n.length;o{(t(e,r)?n:o)(e,r)}))})(e,((e,t)=>!1===a(e)),(o=n,(e,t)=>{o[t]=e}),g),n})({class:t.getOrNull(),href:n,rel:o.getOrNull(),target:r.getOrNull(),text:l.getOrNull(),title:i.getOrNull()})},se=(e,t,n)=>{const o=((e,t)=>{const n=e.options.get,o={allow_html_data_urls:n("allow_html_data_urls"),allow_script_urls:n("allow_script_urls"),allow_svg_data_urls:n("allow_svg_data_urls")},r=t.href;return{...t,href:z.isDomSafe(r,"a",o)?r:""}})(e,n);e.hasPlugin("rtc",!0)?e.execCommand("createlink",!1,ie(o)):ae(e,t,o)},ce=e=>{e.hasPlugin("rtc",!0)?e.execCommand("unlink"):(e=>{e.undoManager.transact((()=>{const t=e.selection.getNode();le(t)?ue(e,t):(e=>{const t=e.dom,n=e.selection,o=n.getBookmark(),r=n.getRng().cloneRange(),l=t.getParent(r.startContainer,"a[href]",e.getBody()),a=t.getParent(r.endContainer,"a[href]",e.getBody());l&&r.setStartBefore(l),a&&r.setEndAfter(a),n.setRng(r),e.execCommand("unlink"),n.moveToBookmark(o)})(e),e.focus()}))})(e)},ue=(e,t)=>{var n;const o=e.dom.select("img",t)[0];if(o){const r=e.dom.getParents(o,"a[href]",t)[0];r&&(null===(n=r.parentNode)||void 0===n||n.insertBefore(o,r),e.dom.remove(r))}},ge=(e,t,n)=>{var o;const r=e.select("img",t)[0];if(r){const t=e.create("a",n);null===(o=r.parentNode)||void 0===o||o.insertBefore(t,r),t.appendChild(r)}},de=(e,t)=>k(t,(t=>(e=>{return V(t=e,n="items")&&void 0!==t[n]&&null!==t[n];var t,n})(t)?de(e,t.items):x(t.value===e,t))),me=(e,t)=>{const n={text:e.text,title:e.title},o=(e,o)=>{const r=(l=t,a=o,"link"===a?l.link:"anchor"===a?l.anchor:m.none()).getOr([]);var l,a;return((e,t,n,o)=>{const r=o[t],l=e.length>0;return void 0!==r?de(r,n).map((t=>({url:{value:t.value,meta:{text:l?e:t.text,attach:g}},text:l?e:t.text}))):m.none()})(n.text,o,r,e)};return{onChange:(e,t)=>{const r=t.name;return"url"===r?(e=>{const t=(o=e.url,x(n.text.length<=0,m.from(null===(r=o.meta)||void 0===r?void 0:r.text).getOr(o.value)));var o,r;const l=(e=>{var t;return x(n.title.length<=0,m.from(null===(t=e.meta)||void 0===t?void 0:t.title).getOr(""))})(e.url);return t.isSome()||l.isSome()?m.some({...t.map((e=>({text:e}))).getOr({}),...l.map((e=>({title:e}))).getOr({})}):m.none()})(e()):((e,t)=>h.call(e,t))(["anchor","link"],r)>-1?o(e(),r):"text"===r||"title"===r?(n[r]=e()[r],m.none()):m.none()}}};var he=tinymce.util.Tools.resolve("tinymce.util.Delay");const fe=e=>{const t=e.href;return t.indexOf("@")>0&&-1===t.indexOf("/")&&-1===t.indexOf("mailto:")?m.some({message:"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",preprocess:e=>({...e,href:"mailto:"+t})}):m.none()},pe=(e,t)=>n=>{const o=n.href;return 1===e&&!W(o)||0===e&&/^\s*www(\.|\d\.)/i.test(o)?m.some({message:`The URL you entered seems to be an external link. Do you want to add the required ${t}:// prefix?`,preprocess:e=>({...e,href:t+"://"+o})}):m.none()},ke=e=>{const t=e.dom.select("a:not([href])"),n=p(((e,t)=>{const n=e.length,o=new Array(n);for(let r=0;r{const t=e.name||e.id;return t?[{text:t,value:"#"+t}]:[]})));return n.length>0?m.some([{text:"None",value:""}].concat(n)):m.none()},ve=e=>{const t=E(e);return t.length>0?I(t):m.none()},ye=e=>{try{return m.some(JSON.parse(e))}catch(e){return m.none()}},xe=(e,t)=>{const n=S(e);if(n.length>0){const o=v(t,"_blank"),r=e=>X(U(e),o);return(!1===R(e)?j(r):I)(n)}return m.none()},be=[{text:"Current window",value:""},{text:"New window",value:"_blank"}],_e=e=>{const t=A(e);return l(t)?I(t).orThunk((()=>m.some(be))):!1===t?m.none():m.some(be)},we=(e,t,n)=>{const o=e.getAttrib(t,n);return null!==o&&o.length>0?m.some(o):m.none()},Ce=(e,t)=>(e=>{const t=t=>e.convertURL(t.value||t.url||"","href"),n=C(e);return new Promise((e=>{o(n)?fetch(n).then((e=>e.ok?e.text().then(ye):Promise.reject())).then(e,(()=>e(m.none()))):c(n)?n((t=>e(m.some(t)))):e(m.from(n))})).then((e=>e.bind(j(t)).map((e=>e.length>0?[{text:"None",value:""}].concat(e):e))))})(e).then((n=>{const o=((e,t)=>{const n=e.dom,o=re(e)?m.some(ee(e.selection,t)):m.none(),r=t.bind((e=>m.from(n.getAttrib(e,"href")))),l=t.bind((e=>m.from(n.getAttrib(e,"target")))),a=t.bind((e=>we(n,e,"rel"))),i=t.bind((e=>we(n,e,"class")));return{url:r,text:o,title:t.bind((e=>we(n,e,"title"))),target:l,rel:a,linkClass:i}})(e,t);return{anchor:o,catalogs:{targets:_e(e),rels:xe(e,o.target),classes:ve(e),anchor:ke(e),link:n},optNode:t,flags:{titleEnabled:T(e)}}})),Oe=e=>{const t=(e=>{const t=Y(e);return Ce(e,t)})(e);t.then((t=>{const n=((e,t)=>n=>{const o=n.getData();if(!o.url.value)return ce(e),void n.close();const r=e=>m.from(o[e]).filter((n=>!v(t.anchor[e],n))),l={href:o.url.value,text:r("text"),target:r("target"),rel:r("rel"),class:r("linkClass"),title:r("title")},a={href:o.url.value,attach:void 0!==o.url.meta&&o.url.meta.attach?o.url.meta.attach:g};((e,t)=>k([fe,pe(_(e),N(e))],(e=>e(t))).fold((()=>Promise.resolve(t)),(n=>new Promise((o=>{((e,t,n)=>{const o=e.selection.getRng();he.setEditorTimeout(e,(()=>{e.windowManager.confirm(t,(t=>{e.selection.setRng(o),n(t)}))}))})(e,n.message,(e=>{o(e?n.preprocess(t):t)}))})))))(e,l).then((t=>{se(e,a,t)})),n.close()})(e,t);return((e,t,n)=>{const o=e.anchor.text.map((()=>({name:"text",type:"input",label:"Text to display"}))).toArray(),r=e.flags.titleEnabled?[{name:"title",type:"input",label:"Title"}]:[],l=((e,t)=>{const n=e.anchor,o=n.url.getOr("");return{url:{value:o,meta:{original:{value:o}}},text:n.text.getOr(""),title:n.title.getOr(""),anchor:o,link:o,rel:n.rel.getOr(""),target:n.target.or(t).getOr(""),linkClass:n.linkClass.getOr("")}})(e,m.from(O(n))),a=e.catalogs,i=me(l,a);return{title:"Insert/Edit Link",size:"normal",body:{type:"panel",items:p([[{name:"url",type:"urlinput",filetype:"file",label:"URL"}],o,r,y([a.anchor.map(K("anchor","Anchors")),a.rels.map(K("rel","Rel")),a.targets.map(K("target","Open link in...")),a.link.map(K("link","Link list")),a.classes.map(K("linkClass","Class"))])])},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:l,onChange:(e,{name:t})=>{i.onChange(e.getData,{name:t}).each((t=>{e.setData(t)}))},onSubmit:t}})(t,n,e)})).then((t=>{e.windowManager.open(t)}))};var Ne=tinymce.util.Tools.resolve("tinymce.util.VK");const Ae=(e,t)=>e.dom.getParent(t,"a[href]"),Se=e=>Ae(e,e.selection.getStart()),Ee=(e,t)=>{if(t){const n=Q(t);if(/^#/.test(n)){const t=e.dom.select(n);t.length&&e.selection.scrollIntoView(t[0],!0)}else(e=>{const t=document.createElement("a");t.target="_blank",t.href=e,t.rel="noreferrer noopener";const n=document.createEvent("MouseEvents");n.initMouseEvent("click",!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null),((e,t)=>{document.body.appendChild(e),e.dispatchEvent(t),document.body.removeChild(e)})(t,n)})(t.href)}},Te=e=>()=>{e.execCommand("mceLink",!1,{dialog:!0})},Re=e=>()=>{Ee(e,Se(e))},Pe=(e,t)=>(e.on("NodeChange",t),()=>e.off("NodeChange",t)),Le=e=>t=>{const n=()=>{t.setActive(!e.mode.isReadOnly()&&Z(e,e.selection.getNode())),t.setEnabled(e.selection.isEditable())};return n(),Pe(e,n)},Me=e=>t=>{const n=()=>{t.setEnabled(e.selection.isEditable())};return n(),Pe(e,n)},De=e=>t=>{const n=()=>t.setEnabled((e=>1===(e.selection.isCollapsed()?ne(e.dom.getParents(e.selection.getStart())):te(e.selection.getRng())).length)(e));return n(),Pe(e,n)},Be=e=>t=>{const n=e.dom.getParents(e.selection.getStart()),o=n=>{t.setEnabled((t=>{return oe(t)||(n=e.selection.getRng(),te(n).length>0);var n})(n)&&e.selection.isEditable())};return o(n),Pe(e,(e=>o(e.parents)))};e.add("link",(e=>{(e=>{const t=e.options.register;t("link_assume_external_targets",{processor:e=>{const t=o(e)||i(e);return t?!0===e?{value:1,valid:t}:"http"===e||"https"===e?{value:e,valid:t}:{value:0,valid:t}:{valid:!1,message:"Must be a string or a boolean."}},default:!1}),t("link_context_toolbar",{processor:"boolean",default:!1}),t("link_list",{processor:e=>o(e)||c(e)||u(e,r)}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"}),t("link_target_list",{processor:e=>i(e)||u(e,r),default:!0}),t("link_rel_list",{processor:"object[]",default:[]}),t("link_class_list",{processor:"object[]",default:[]}),t("link_title",{processor:"boolean",default:!0}),t("allow_unsafe_link_target",{processor:"boolean",default:!1}),t("link_quicklink",{processor:"boolean",default:!1})})(e),(e=>{e.ui.registry.addToggleButton("link",{icon:"link",tooltip:"Insert/edit link",onAction:Te(e),onSetup:Le(e)}),e.ui.registry.addButton("openlink",{icon:"new-tab",tooltip:"Open link",onAction:Re(e),onSetup:De(e)}),e.ui.registry.addButton("unlink",{icon:"unlink",tooltip:"Remove link",onAction:()=>ce(e),onSetup:Be(e)})})(e),(e=>{e.ui.registry.addMenuItem("openlink",{text:"Open link",icon:"new-tab",onAction:Re(e),onSetup:De(e)}),e.ui.registry.addMenuItem("link",{icon:"link",text:"Link...",shortcut:"Meta+K",onSetup:Me(e),onAction:Te(e)}),e.ui.registry.addMenuItem("unlink",{icon:"unlink",text:"Remove link",onAction:()=>ce(e),onSetup:Be(e)})})(e),(e=>{e.ui.registry.addContextMenu("link",{update:t=>e.dom.isEditable(t)?oe(e.dom.getParents(t,"a"))?"link unlink openlink":"link":""})})(e),(e=>{const t=t=>{const n=e.selection.getNode();return t.setEnabled(Z(e,n)),g};e.ui.registry.addContextForm("quicklink",{launch:{type:"contextformtogglebutton",icon:"link",tooltip:"Link",onSetup:Le(e)},label:"Link",predicate:t=>w(e)&&Z(e,t),initValue:()=>Y(e).fold((()=>""),Q),commands:[{type:"contextformtogglebutton",icon:"link",tooltip:"Link",primary:!0,onSetup:t=>{const n=e.selection.getNode();return t.setActive(Z(e,n)),Le(e)(t)},onAction:t=>{const n=t.getValue(),o=(t=>{const n=Y(e),o=re(e);if(n.isNone()&&o){const o=ee(e.selection,n);return x(0===o.length,t)}return m.none()})(n);se(e,{href:n,attach:g},{href:n,text:o,title:m.none(),rel:m.none(),target:m.none(),class:m.none()}),(e=>{e.selection.collapse(!1)})(e),t.hide()}},{type:"contextformbutton",icon:"unlink",tooltip:"Remove link",onSetup:t,onAction:t=>{ce(e),t.hide()}},{type:"contextformbutton",icon:"new-tab",tooltip:"Open link",onSetup:t,onAction:t=>{Re(e)(),t.hide()}}]})})(e),(e=>{e.on("click",(t=>{const n=Ae(e,t.target);n&&Ne.metaKeyPressed(t)&&(t.preventDefault(),Ee(e,n))})),e.on("keydown",(t=>{if(!t.isDefaultPrevented()&&13===t.keyCode&&(e=>!0===e.altKey&&!1===e.shiftKey&&!1===e.ctrlKey&&!1===e.metaKey)(t)){const n=Se(e);n&&(t.preventDefault(),Ee(e,n))}}))})(e),(e=>{e.addCommand("mceLink",((t,n)=>{!0!==(null==n?void 0:n.dialog)&&P(e)?e.dispatch("contexttoolbar-show",{toolbarKey:"quicklink"}):Oe(e)}))})(e),(e=>{e.addShortcut("Meta+K","",(()=>{e.execCommand("mceLink")}))})(e)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/lists/plugin.min.js b/static/js/tinymce/plugins/lists/plugin.min.js new file mode 100644 index 0000000..e72e133 --- /dev/null +++ b/static/js/tinymce/plugins/lists/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var n,o,r,s})(t)===e,n=e=>t=>typeof t===e,o=t("string"),r=t("object"),s=t("array"),i=n("boolean"),l=e=>!(e=>null==e)(e),a=n("function"),d=n("number"),c=()=>{},m=(e,t)=>e===t,u=e=>t=>!e(t),p=(!1,()=>false);class g{constructor(e,t){this.tag=e,this.value=t}static some(e){return new g(!0,e)}static none(){return g.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?g.some(e(this.value)):g.none()}bind(e){return this.tag?e(this.value):g.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:g.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return l(e)?g.some(e):g.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}g.singletonNone=new g(!1);const h=Array.prototype.slice,f=Array.prototype.indexOf,y=Array.prototype.push,v=(e,t)=>{return n=e,o=t,f.call(n,o)>-1;var n,o},C=(e,t)=>{for(let n=0,o=e.length;n{const n=e.length,o=new Array(n);for(let r=0;r{for(let n=0,o=e.length;n{const n=[];for(let o=0,r=e.length;o(S(e,((e,o)=>{n=t(n,e,o)})),n),O=(e,t,n)=>{for(let o=0,r=e.length;oO(e,t,p),x=(e,t)=>(e=>{const t=[];for(let n=0,o=e.length;n{const t=h.call(e,0);return t.reverse(),t},T=(e,t)=>t>=0&&tT(e,0),w=e=>T(e,e.length-1),D=(e,t)=>{const n=[],o=a(t)?e=>C(n,(n=>t(n,e))):e=>v(n,e);for(let t=0,r=e.length;te.exists((e=>n(e,t))),I=(e,t,n)=>e.isSome()&&t.isSome()?g.some(n(e.getOrDie(),t.getOrDie())):g.none(),P=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},M=(e,t)=>{const n=(t||document).createElement("div");if(n.innerHTML=e,!n.hasChildNodes()||n.childNodes.length>1){const t="HTML does not have a single root node";throw console.error(t,e),new Error(t)}return P(n.childNodes[0])},R=(e,t)=>{const n=(t||document).createElement(e);return P(n)},U=P,$=(e,t)=>e.dom===t.dom;"undefined"!=typeof window?window:Function("return this;")();const _=e=>e.dom.nodeName.toLowerCase(),H=e=>e.dom.nodeType,V=(1,e=>1===H(e));const F=e=>t=>V(t)&&_(t)===e,j=e=>g.from(e.dom.parentNode).map(U),K=e=>b(e.dom.childNodes,U),z=(e,t)=>{const n=e.dom.childNodes;return g.from(n[t]).map(U)},Q=e=>z(e,0),W=e=>z(e,e.dom.childNodes.length-1),q=(e,t,n)=>{let o=e.dom;const r=a(n)?n:p;for(;o.parentNode;){o=o.parentNode;const e=U(o);if(t(e))return g.some(e);if(r(e))break}return g.none()},Z=(e,t,n)=>((e,t,n,o,r)=>o(n)?g.some(n):a(r)&&r(n)?g.none():t(n,o,r))(0,q,e,t,n),G=(e,t)=>{j(e).each((n=>{n.dom.insertBefore(t.dom,e.dom)}))},J=(e,t)=>{e.dom.appendChild(t.dom)},X=(e,t)=>{S(t,(t=>{J(e,t)}))},Y=e=>{e.dom.textContent="",S(K(e),(e=>{ee(e)}))},ee=e=>{const t=e.dom;null!==t.parentNode&&t.parentNode.removeChild(t)};var te=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),ne=tinymce.util.Tools.resolve("tinymce.dom.TreeWalker"),oe=tinymce.util.Tools.resolve("tinymce.util.VK");const re=e=>b(e,U),se=Object.keys,ie=(e,t)=>{const n=se(e);for(let o=0,r=n.length;o{const n=e.dom;ie(t,((e,t)=>{((e,t,n)=>{if(!(o(n)||i(n)||d(n)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",n,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,n+"")})(n,t,e)}))},ae=e=>L(e.dom.attributes,((e,t)=>(e[t.name]=t.value,e)),{}),de=e=>((e,t)=>U(e.dom.cloneNode(!0)))(e),ce=(e,t)=>{const n=((e,t)=>{const n=R(t),o=ae(e);return le(n,o),n})(e,t);var o,r;r=n,(e=>g.from(e.dom.nextSibling).map(U))(o=e).fold((()=>{j(o).each((e=>{J(e,r)}))}),(e=>{G(e,r)}));const s=K(e);return X(n,s),ee(e),n};var me=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),ue=tinymce.util.Tools.resolve("tinymce.util.Tools");const pe=e=>t=>l(t)&&t.nodeName.toLowerCase()===e,ge=e=>t=>l(t)&&e.test(t.nodeName),he=e=>l(e)&&3===e.nodeType,fe=e=>l(e)&&1===e.nodeType,ye=ge(/^(OL|UL|DL)$/),ve=ge(/^(OL|UL)$/),Ce=pe("ol"),be=ge(/^(LI|DT|DD)$/),Se=ge(/^(DT|DD)$/),Ne=ge(/^(TH|TD)$/),Le=pe("br"),Oe=(e,t)=>l(t)&&t.nodeName in e.schema.getTextBlockElements(),Ae=(e,t)=>l(e)&&e.nodeName in t,xe=(e,t)=>l(t)&&t.nodeName in e.schema.getVoidElements(),ke=(e,t,n)=>{const o=e.isEmpty(t);return!(n&&e.select("span[data-mce-type=bookmark]",t).length>0)&&o},Te=(e,t)=>e.isChildOf(t,e.getRoot()),Ee=e=>t=>t.options.get(e),we=Ee("lists_indent_on_tab"),De=Ee("forced_root_block"),Be=Ee("forced_root_block_attrs"),Ie=(e,t)=>{const n=e.dom,o=e.schema.getBlockElements(),r=n.createFragment(),s=De(e),i=Be(e);let l,a,d=!1;for(a=n.create(s,i),Ae(t.firstChild,o)||r.appendChild(a);l=t.firstChild;){const e=l.nodeName;d||"SPAN"===e&&"bookmark"===l.getAttribute("data-mce-type")||(d=!0),Ae(l,o)?(r.appendChild(l),a=null):(a||(a=n.create(s,i),r.appendChild(a)),a.appendChild(l))}return!d&&a&&a.appendChild(n.create("br",{"data-mce-bogus":"1"})),r},Pe=me.DOM,Me=F("dd"),Re=F("dt"),Ue=(e,t)=>{var n;Me(t)?ce(t,"dt"):Re(t)&&(n=t,g.from(n.dom.parentElement).map(U)).each((n=>((e,t,n)=>{const o=Pe.select('span[data-mce-type="bookmark"]',t),r=Ie(e,n),s=Pe.createRng();s.setStartAfter(n),s.setEndAfter(t);const i=s.extractContents();for(let t=i.firstChild;t;t=t.firstChild)if("LI"===t.nodeName&&e.dom.isEmpty(t)){Pe.remove(t);break}e.dom.isEmpty(i)||Pe.insertAfter(i,t),Pe.insertAfter(r,t);const l=n.parentElement;l&&ke(e.dom,l)&&(e=>{const t=e.parentNode;t&&ue.each(o,(e=>{t.insertBefore(e,n.parentNode)})),Pe.remove(e)})(l),Pe.remove(n),ke(e.dom,t)&&Pe.remove(t)})(e,n.dom,t.dom)))},$e=e=>{Re(e)&&ce(e,"dd")},_e=(e,t)=>{if(he(e))return{container:e,offset:t};const n=te.getNode(e,t);return he(n)?{container:n,offset:t>=e.childNodes.length?n.data.length:0}:n.previousSibling&&he(n.previousSibling)?{container:n.previousSibling,offset:n.previousSibling.data.length}:n.nextSibling&&he(n.nextSibling)?{container:n.nextSibling,offset:0}:{container:e,offset:t}},He=e=>{const t=e.cloneRange(),n=_e(e.startContainer,e.startOffset);t.setStart(n.container,n.offset);const o=_e(e.endContainer,e.endOffset);return t.setEnd(o.container,o.offset),t},Ve=["OL","UL","DL"],Fe=Ve.join(","),je=(e,t)=>{const n=t||e.selection.getStart(!0);return e.dom.getParent(n,Fe,Qe(e,n))},Ke=e=>{const t=e.selection.getSelectedBlocks();return N(((e,t)=>{const n=ue.map(t,(t=>e.dom.getParent(t,"li,dd,dt",Qe(e,t))||t));return D(n)})(e,t),be)},ze=(e,t)=>{const n=e.dom.getParents(t,"TD,TH");return n.length>0?n[0]:e.getBody()},Qe=(e,t)=>{const n=e.dom.getParents(t,e.dom.isBlock),o=A(n,(t=>{return n=e.schema,!ye(o=t)&&!be(o)&&C(Ve,(e=>n.isValidChild(o.nodeName,e)));var n,o}));return o.getOr(e.getBody())},We=(e,t)=>{const n=e.dom.getParents(t,"ol,ul",Qe(e,t));return w(n)},qe=(e,t)=>{const n=b(t,(t=>We(e,t).getOr(t)));return D(n)},Ze=e=>/\btox\-/.test(e.className),Ge=(e,t)=>O(e,ye,Ne).exists((e=>e.nodeName===t&&!Ze(e))),Je=(e,t)=>null!==t&&!e.dom.isEditable(t),Xe=(e,t)=>{const n=e.dom.getParent(t,"ol,ul,dl");return Je(e,n)},Ye=(e,t)=>{const n=e.selection.getNode();return t({parents:e.dom.getParents(n),element:n}),e.on("NodeChange",t),()=>e.off("NodeChange",t)},et=(e,t)=>{const n=(t||document).createDocumentFragment();return S(e,(e=>{n.appendChild(e.dom)})),U(n)},tt=(e,t,n)=>e.dispatch("ListMutation",{action:t,element:n}),nt=(ot=/^\s+|\s+$/g,e=>e.replace(ot,""));var ot;const rt=(e,t,n)=>{((e,t,n)=>{if(!o(n))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",n,":: Element ",e),new Error("CSS value must be a string: "+n);(e=>void 0!==e.style&&a(e.style.getPropertyValue))(e)&&e.style.setProperty(t,n)})(e.dom,t,n)},st=e=>((e,t)=>{const n=e.dom;if(1!==n.nodeType)return!1;{const e=n;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}})(e,"OL,UL"),it=e=>Q(e).exists(st),lt=e=>"listAttributes"in e,at=e=>"isComment"in e,dt=e=>e.depth>0,ct=e=>e.isSelected,mt=e=>{const t=K(e),n=W(e).exists(st)?t.slice(0,-1):t;return b(n,de)},ut=(e,t)=>{J(e.item,t.list)},pt=(e,t)=>{const n={list:R(t,e),item:R("li",e)};return J(n.list,n.item),n},gt=(e,t,n)=>{const o=t.slice(0,n.depth);return w(o).each((t=>{if(lt(n)){const o=((e,t,n)=>{const o=R("li",e);return le(o,t),X(o,n),o})(e,n.itemAttributes,n.content);((e,t)=>{J(e.list,t),e.item=t})(t,o),((e,t)=>{_(e.list)!==t.listType&&(e.list=ce(e.list,t.listType)),le(e.list,t.listAttributes)})(t,n)}else if((e=>"isInPreviousLi"in e)(n)){if(n.isInPreviousLi){const o=((e,t,n,o)=>{const r=R(o,e);return le(r,t),X(r,n),r})(e,n.attributes,n.content,n.type);J(t.item,o)}}else{const e=M(`\x3c!--${n.content}--\x3e`);J(t.list,e)}})),o},ht=(e,t)=>{let n=g.none();const o=L(t,((t,o,r)=>lt(o)?o.depth>t.length?((e,t,n)=>{const o=((e,t,n)=>{const o=[];for(let r=0;r{for(let t=1;t{for(let t=0;t{le(e.list,t.listAttributes),le(e.item,t.itemAttributes),X(e.item,t.content)}))})(o,n),r=o,I(w(t),E(r),ut),t.concat(o)})(e,t,o):gt(e,t,o):0===r&&at(o)?(n=g.some(o),t):gt(e,t,o)),[]);return n.each((e=>{const t=M(`\x3c!--${e.content}--\x3e`);E(o).each((e=>{((e,t)=>{Q(e).fold((()=>{J(e,t)}),(n=>{e.dom.insertBefore(t.dom,n.dom)}))})(e.list,t)}))})),E(o).map((e=>e.list))},ft=e=>(S(e,((t,n)=>{((e,t)=>{const n=e[t].depth,o=e=>e.depth===n&&!e.dirty,r=e=>e.depthO(e.slice(t+1),o,r)))})(e,n).fold((()=>{t.dirty&<(t)&&(e=>{e.listAttributes=((e,t)=>{const n={};var o;return((e,t,n,o)=>{ie(e,((e,r)=>{(t(e,r)?n:o)(e,r)}))})(e,t,(o=n,(e,t)=>{o[t]=e}),c),n})(e.listAttributes,((e,t)=>"start"!==t))})(t)}),(e=>{return o=e,void(lt(n=t)&<(o)&&(n.listType=o.listType,n.listAttributes={...o.listAttributes}));var n,o}))})),e),yt=(e,t,n,o)=>{var r,s;if(8===H(s=o)||"#comment"===_(s))return[{depth:e+1,content:null!==(r=o.dom.nodeValue)&&void 0!==r?r:"",dirty:!1,isSelected:!1,isComment:!0}];t.each((e=>{$(e.start,o)&&n.set(!0)}));const i=((e,t,n)=>j(e).filter(V).map((o=>({depth:t,dirty:!1,isSelected:n,content:mt(e),itemAttributes:ae(e),listAttributes:ae(o),listType:_(o),isInPreviousLi:!1}))))(o,e,n.get());t.each((e=>{$(e.end,o)&&n.set(!1)}));const l=W(o).filter(st).map((o=>Ct(e,t,n,o))).getOr([]);return i.toArray().concat(l)},vt=(e,t,n,o)=>Q(o).filter(st).fold((()=>yt(e,t,n,o)),(r=>{const s=L(K(o),((o,r,s)=>{if(0===s)return o;{const s=yt(e,t,n,r).map((e=>((e,t,n)=>lt(e)?{depth:e.depth,dirty:e.dirty,content:e.content,isSelected:e.isSelected,type:t,attributes:e.itemAttributes,isInPreviousLi:!0}:e)(e,r.dom.nodeName.toLowerCase())));return o.concat(s)}}),[]);return Ct(e,t,n,r).concat(s)})),Ct=(e,t,n,o)=>x(K(o),(o=>(st(o)?Ct:vt)(e+1,t,n,o))),bt=(e,t,n)=>{const o=((e,t)=>{const n=(e=>{let t=!1;return{get:()=>t,set:e=>{t=e}}})();return b(e,(e=>({sourceList:e,entries:Ct(0,t,n,e)})))})(t,(e=>{const t=b(Ke(e),U);return I(A(t,u(it)),A(k(t),u(it)),((e,t)=>({start:e,end:t})))})(e));S(o,(t=>{((e,t)=>{S(N(e,ct),(e=>((e,t)=>{switch(e){case"Indent":t.depth++;break;case"Outdent":t.depth--;break;case"Flatten":t.depth=0}t.dirty=!0})(t,e)))})(t.entries,n);const o=((e,t)=>x(((e,t)=>{if(0===e.length)return[];{let n=t(e[0]);const o=[];let r=[];for(let s=0,i=e.length;sE(t).exists(dt)?((e,t)=>{const n=ft(t);return ht(e.contentDocument,n).toArray()})(e,t):((e,t)=>{const n=ft(t);return b(n,(t=>{const n=at(t)?et([M(`\x3c!--${t.content}--\x3e`)]):et(t.content);return U(Ie(e,n.dom))}))})(e,t))))(e,t.entries);var r;S(o,(t=>{tt(e,"Indent"===n?"IndentList":"OutdentList",t.dom)})),r=t.sourceList,S(o,(e=>{G(r,e)})),ee(t.sourceList)}))},St=(e,t)=>{const n=re((e=>{const t=(e=>{const t=We(e,e.selection.getStart()),n=N(e.selection.getSelectedBlocks(),ve);return t.toArray().concat(n)})(e),n=(e=>{const t=e.selection.getStart();return e.dom.getParents(t,"ol,ul",Qe(e,t))})(e);return A(n,(e=>{return t=U(e),j(t).exists((e=>be(e.dom)&&Q(e).exists((e=>!ye(e.dom)))&&W(e).exists((e=>!ye(e.dom)))));var t})).fold((()=>qe(e,t)),(e=>[e]))})(e)),o=re((e=>N(Ke(e),Se))(e));let r=!1;if(n.length||o.length){const s=e.selection.getBookmark();bt(e,n,t),((e,t,n)=>{S(n,"Indent"===t?$e:t=>Ue(e,t))})(e,t,o),e.selection.moveToBookmark(s),e.selection.setRng(He(e.selection.getRng())),e.nodeChanged(),r=!0}return r},Nt=(e,t)=>!(e=>{const t=je(e);return Je(e,t)})(e)&&St(e,t),Lt=e=>Nt(e,"Indent"),Ot=e=>Nt(e,"Outdent"),At=e=>Nt(e,"Flatten"),xt=e=>"\ufeff"===e;var kt=tinymce.util.Tools.resolve("tinymce.dom.BookmarkManager");const Tt=me.DOM,Et=e=>{const t={},n=n=>{let o=e[n?"startContainer":"endContainer"],r=e[n?"startOffset":"endOffset"];if(fe(o)){const e=Tt.create("span",{"data-mce-type":"bookmark"});o.hasChildNodes()?(r=Math.min(r,o.childNodes.length-1),n?o.insertBefore(e,o.childNodes[r]):Tt.insertAfter(e,o.childNodes[r])):o.appendChild(e),o=e,r=0}t[n?"startContainer":"endContainer"]=o,t[n?"startOffset":"endOffset"]=r};return n(!0),e.collapsed||n(),t},wt=e=>{const t=t=>{let n=e[t?"startContainer":"endContainer"],o=e[t?"startOffset":"endOffset"];if(n){if(fe(n)&&n.parentNode){const e=n;o=(e=>{var t;let n=null===(t=e.parentNode)||void 0===t?void 0:t.firstChild,o=0;for(;n;){if(n===e)return o;fe(n)&&"bookmark"===n.getAttribute("data-mce-type")||o++,n=n.nextSibling}return-1})(n),n=n.parentNode,Tt.remove(e),!n.hasChildNodes()&&Tt.isBlock(n)&&n.appendChild(Tt.create("br"))}e[t?"startContainer":"endContainer"]=n,e[t?"startOffset":"endOffset"]=o}};t(!0),t();const n=Tt.createRng();return n.setStart(e.startContainer,e.startOffset),e.endContainer&&n.setEnd(e.endContainer,e.endOffset),He(n)},Dt=e=>{switch(e){case"UL":return"ToggleUlList";case"OL":return"ToggleOlList";case"DL":return"ToggleDLList"}},Bt=(e,t)=>{ue.each(t,((t,n)=>{e.setAttribute(n,t)}))},It=(e,t,n)=>{((e,t,n)=>{const o=n["list-style-type"]?n["list-style-type"]:null;e.setStyle(t,"list-style-type",o)})(e,t,n),((e,t,n)=>{Bt(t,n["list-attributes"]),ue.each(e.select("li",t),(e=>{Bt(e,n["list-item-attributes"])}))})(e,t,n)},Pt=(e,t)=>l(t)&&!Ae(t,e.schema.getBlockElements()),Mt=(e,t,n,o)=>{let r=t[n?"startContainer":"endContainer"];const s=t[n?"startOffset":"endOffset"];fe(r)&&(r=r.childNodes[Math.min(s,r.childNodes.length-1)]||r),!n&&Le(r.nextSibling)&&(r=r.nextSibling);const i=(t,n)=>{var r;const s=new ne(t,(t=>{for(;!e.dom.isBlock(t)&&t.parentNode&&o!==t;)t=t.parentNode;return t})(t)),i=n?"next":"prev";let l;for(;l=s[i]();)if(!xe(e,l)&&!xt(l.textContent)&&0!==(null===(r=l.textContent)||void 0===r?void 0:r.length))return g.some(l);return g.none()};if(n&&he(r))if(xt(r.textContent))r=i(r,!1).getOr(r);else for(null!==r.parentNode&&Pt(e,r.parentNode)&&(r=r.parentNode);null!==r.previousSibling&&(Pt(e,r.previousSibling)||he(r.previousSibling));)r=r.previousSibling;if(!n&&he(r))if(xt(r.textContent))r=i(r,!0).getOr(r);else for(null!==r.parentNode&&Pt(e,r.parentNode)&&(r=r.parentNode);null!==r.nextSibling&&(Pt(e,r.nextSibling)||he(r.nextSibling));)r=r.nextSibling;for(;r.parentNode!==o;){const t=r.parentNode;if(Oe(e,r))return r;if(/^(TD|TH)$/.test(t.nodeName))return r;r=t}return r},Rt=(e,t,n)=>{const o=e.selection.getRng();let r="LI";const s=Qe(e,((e,t)=>{const n=e.selection.getStart(!0),o=Mt(e,t,!0,e.getBody());return r=U(o),s=U(t.commonAncestorContainer),i=r,l=function(e,...t){return(...n)=>{const o=t.concat(n);return e.apply(null,o)}}($,s),q(i,l,void 0).isSome()?t.commonAncestorContainer:n;var r,s,i,l})(e,o)),i=e.dom;if("false"===i.getContentEditable(e.selection.getNode()))return;"DL"===(t=t.toUpperCase())&&(r="DT");const l=Et(o),a=N(((e,t,n)=>{const o=[],r=e.dom,s=Mt(e,t,!0,n),i=Mt(e,t,!1,n);let l;const a=[];for(let e=s;e&&(a.push(e),e!==i);e=e.nextSibling);return ue.each(a,(t=>{var s;if(Oe(e,t))return o.push(t),void(l=null);if(r.isBlock(t)||Le(t))return Le(t)&&r.remove(t),void(l=null);const i=t.nextSibling;kt.isBookmarkNode(t)&&(ye(i)||Oe(e,i)||!i&&t.parentNode===n)?l=null:(l||(l=r.create("p"),null===(s=t.parentNode)||void 0===s||s.insertBefore(l,t),o.push(l)),l.appendChild(t))})),o})(e,o,s),e.dom.isEditable);ue.each(a,(o=>{let s;const l=o.previousSibling,a=o.parentNode;be(a)||(l&&ye(l)&&l.nodeName===t&&((e,t,n)=>{const o=e.getStyle(t,"list-style-type");let r=n?n["list-style-type"]:"";return r=null===r?"":r,o===r})(i,l,n)?(s=l,o=i.rename(o,r),l.appendChild(o)):(s=i.create(t),a.insertBefore(s,o),s.appendChild(o),o=i.rename(o,r)),((e,t,n)=>{ue.each(["margin","margin-right","margin-bottom","margin-left","margin-top","padding","padding-right","padding-bottom","padding-left","padding-top"],(n=>e.setStyle(t,n,"")))})(i,o),It(i,s,n),$t(e.dom,s))})),e.selection.setRng(wt(l))},Ut=(e,t,n)=>{return((e,t)=>ye(e)&&e.nodeName===(null==t?void 0:t.nodeName))(t,n)&&((e,t,n)=>e.getStyle(t,"list-style-type",!0)===e.getStyle(n,"list-style-type",!0))(e,t,n)&&(o=n,t.className===o.className);var o},$t=(e,t)=>{let n,o=t.nextSibling;if(Ut(e,t,o)){const r=o;for(;n=r.firstChild;)t.appendChild(n);e.remove(r)}if(o=t.previousSibling,Ut(e,t,o)){const r=o;for(;n=r.lastChild;)t.insertBefore(n,t.firstChild);e.remove(r)}},_t=(e,t,n,o)=>{if(t.nodeName!==n){const r=e.dom.rename(t,n);It(e.dom,r,o),tt(e,Dt(n),r)}else It(e.dom,t,o),tt(e,Dt(n),t)},Ht=(e,t,n,o)=>{if(t.classList.forEach(((e,n,o)=>{e.startsWith("tox-")&&(o.remove(e),0===o.length&&t.removeAttribute("class"))})),t.nodeName!==n){const r=e.dom.rename(t,n);It(e.dom,r,o),tt(e,Dt(n),r)}else It(e.dom,t,o),tt(e,Dt(n),t)},Vt=e=>"list-style-type"in e,Ft=(e,t,n)=>{const o=je(e);if(Xe(e,o))return;const s=(e=>{const t=je(e),n=e.selection.getSelectedBlocks();return((e,t)=>l(e)&&1===t.length&&t[0]===e)(t,n)?(e=>N(e.querySelectorAll(Fe),ye))(t):N(n,(e=>ye(e)&&t!==e))})(e),i=r(n)?n:{};s.length>0?((e,t,n,o,r)=>{const s=ye(t);if(!s||t.nodeName!==o||Vt(r)||Ze(t)){Rt(e,o,r);const i=Et(e.selection.getRng()),l=s?[t,...n]:n,a=s&&Ze(t)?Ht:_t;ue.each(l,(t=>{a(e,t,o,r)})),e.selection.setRng(wt(i))}else At(e)})(e,o,s,t,i):((e,t,n,o)=>{if(t!==e.getBody())if(t)if(t.nodeName!==n||Vt(o)||Ze(t)){const r=Et(e.selection.getRng());Ze(t)&&t.classList.forEach(((e,n,o)=>{e.startsWith("tox-")&&(o.remove(e),0===o.length&&t.removeAttribute("class"))})),It(e.dom,t,o);const s=e.dom.rename(t,n);$t(e.dom,s),e.selection.setRng(wt(r)),Rt(e,n,o),tt(e,Dt(n),s)}else At(e);else Rt(e,n,o),tt(e,Dt(n),t)})(e,o,t,i)},jt=me.DOM,Kt=(e,t)=>{const n=ue.grep(e.select("ol,ul",t));ue.each(n,(t=>{((e,t)=>{const n=t.parentElement;if(n&&"LI"===n.nodeName&&n.firstChild===t){const o=n.previousSibling;o&&"LI"===o.nodeName?(o.appendChild(t),ke(e,n)&&jt.remove(n)):jt.setStyle(n,"listStyleType","none")}if(ye(n)){const e=n.previousSibling;e&&"LI"===e.nodeName&&e.appendChild(t)}})(e,t)}))},zt=(e,t,n,o)=>{let r=t.startContainer;const s=t.startOffset;if(he(r)&&(n?s0))return r;const i=e.schema.getNonEmptyElements();fe(r)&&(r=te.getNode(r,s));const l=new ne(r,o);n&&((e,t)=>!!Le(t)&&e.isBlock(t.nextSibling)&&!Le(t.previousSibling))(e.dom,r)&&l.next();const a=n?l.next.bind(l):l.prev2.bind(l);for(;r=a();){if("LI"===r.nodeName&&!r.hasChildNodes())return r;if(i[r.nodeName])return r;if(he(r)&&r.data.length>0)return r}return null},Qt=(e,t)=>{const n=t.childNodes;return 1===n.length&&!ye(n[0])&&e.isBlock(n[0])},Wt=(e,t,n)=>{let o;const r=t.parentNode;if(!Te(e,t)||!Te(e,n))return;ye(n.lastChild)&&(o=n.lastChild),r===n.lastChild&&Le(r.previousSibling)&&e.remove(r.previousSibling);const s=n.lastChild;s&&Le(s)&&t.hasChildNodes()&&e.remove(s),ke(e,n,!0)&&Y(U(n)),((e,t,n)=>{let o;const r=Qt(e,n)?n.firstChild:n;if(((e,t)=>{Qt(e,t)&&e.remove(t.firstChild,!0)})(e,t),!ke(e,t,!0))for(;o=t.firstChild;)r.appendChild(o)})(e,t,n),o&&n.appendChild(o);const i=((e,t)=>{const n=e.dom,o=t.dom;return n!==o&&n.contains(o)})(U(n),U(t))?e.getParents(t,ye,n):[];e.remove(t),S(i,(t=>{ke(e,t)&&t!==e.getRoot()&&e.remove(t)}))},qt=(e,t)=>{const n=e.dom,o=e.selection,r=o.getStart(),s=ze(e,r),i=n.getParent(o.getStart(),"LI",s);if(i){const r=i.parentElement;if(r===e.getBody()&&ke(n,r))return!0;const l=He(o.getRng()),a=n.getParent(zt(e,l,t,s),"LI",s);if(a&&a!==i)return e.undoManager.transact((()=>{var n,o;t?((e,t,n,o)=>{const r=e.dom;if(r.isEmpty(o))((e,t,n)=>{Y(U(n)),Wt(e.dom,t,n),e.selection.setCursorLocation(n,0)})(e,n,o);else{const s=Et(t);Wt(r,n,o),e.selection.setRng(wt(s))}})(e,l,a,i):(null===(o=(n=i).parentNode)||void 0===o?void 0:o.firstChild)===n?Ot(e):((e,t,n,o)=>{const r=Et(t);Wt(e.dom,n,o);const s=wt(r);e.selection.setRng(s)})(e,l,i,a)})),!0;if(!a&&!t&&0===l.startOffset&&0===l.endOffset)return e.undoManager.transact((()=>{At(e)})),!0}return!1},Zt=e=>{const t=e.selection.getStart(),n=ze(e,t);return e.dom.getParent(t,"LI,DT,DD",n)||Ke(e).length>0},Gt=(e,t)=>{const n=e.selection;return!Xe(e,n.getNode())&&(n.isCollapsed()?((e,t)=>qt(e,t)||((e,t)=>{const n=e.dom,o=e.selection.getStart(),r=ze(e,o),s=n.getParent(o,n.isBlock,r);if(s&&n.isEmpty(s)){const o=He(e.selection.getRng()),i=n.getParent(zt(e,o,t,r),"LI",r);if(i){const l=e=>v(["td","th","caption"],_(e)),a=e=>e.dom===r;return!!((e,t,n=m)=>I(e,t,n).getOr(e.isNone()&&t.isNone()))(Z(U(i),l,a),Z(U(o.startContainer),l,a),$)&&(e.undoManager.transact((()=>{const o=i.parentNode;((e,t,n)=>{const o=e.getParent(t.parentNode,e.isBlock,n);e.remove(t),o&&e.isEmpty(o)&&e.remove(o)})(n,s,r),$t(n,o),e.selection.select(i,!0),e.selection.collapse(t)})),!0)}}return!1})(e,t))(e,t):(e=>!!Zt(e)&&(e.undoManager.transact((()=>{e.execCommand("Delete"),Kt(e.dom,e.getBody())})),!0))(e))},Jt=e=>{const t=k(nt(e).split("")),n=b(t,((e,t)=>{const n=e.toUpperCase().charCodeAt(0)-"A".charCodeAt(0)+1;return Math.pow(26,t)*n}));return L(n,((e,t)=>e+t),0)},Xt=e=>{if(--e<0)return"";{const t=e%26,n=Math.floor(e/26);return Xt(n)+String.fromCharCode("A".charCodeAt(0)+t)}},Yt=e=>{const t=parseInt(e.start,10);return B(e.listStyleType,"upper-alpha")?Xt(t):B(e.listStyleType,"lower-alpha")?Xt(t).toLowerCase():e.start},en=(e,t)=>()=>{const n=je(e);return l(n)&&n.nodeName===t},tn=e=>{e.addCommand("mceListProps",(()=>{(e=>{const t=je(e);Ce(t)&&!Xe(e,t)&&e.windowManager.open({title:"List Properties",body:{type:"panel",items:[{type:"input",name:"start",label:"Start list at number",inputMode:"numeric"}]},initialData:{start:Yt({start:e.dom.getAttrib(t,"start","1"),listStyleType:g.from(e.dom.getStyle(t,"list-style-type"))})},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],onSubmit:t=>{(e=>{switch((e=>/^[0-9]+$/.test(e)?2:/^[A-Z]+$/.test(e)?0:/^[a-z]+$/.test(e)?1:e.length>0?4:3)(e)){case 2:return g.some({listStyleType:g.none(),start:e});case 0:return g.some({listStyleType:g.some("upper-alpha"),start:Jt(e).toString()});case 1:return g.some({listStyleType:g.some("lower-alpha"),start:Jt(e).toString()});case 3:return g.some({listStyleType:g.none(),start:""});case 4:return g.none()}})(t.getData().start).each((t=>{e.execCommand("mceListUpdate",!1,{attrs:{start:"1"===t.start?"":t.start},styles:{"list-style-type":t.listStyleType.getOr("")}})})),t.close()}})})(e)}))};var nn=tinymce.util.Tools.resolve("tinymce.html.Node");const on=e=>3===e.type,rn=e=>0===e.length,sn=e=>{const t=(t,n)=>{const o=nn.create("li");S(t,(e=>o.append(e))),n?e.insert(o,n,!0):e.append(o)},n=L(e.children(),((e,n)=>on(n)?[...e,n]:rn(e)||on(n)?e:(t(e,n),[])),[]);rn(n)||t(n)},ln=(e,t)=>n=>(n.setEnabled(e.selection.isEditable()),Ye(e,(o=>{n.setActive(Ge(o.parents,t)),n.setEnabled(!Xe(e,o.element)&&e.selection.isEditable())}))),an=(e,t)=>n=>Ye(e,(o=>n.setEnabled(Ge(o.parents,t)&&!Xe(e,o.element))));e.add("lists",(e=>((e=>{(0,e.options.register)("lists_indent_on_tab",{processor:"boolean",default:!0})})(e),(e=>{e.on("PreInit",(()=>{const{parser:t}=e;t.addNodeFilter("ul,ol",(e=>S(e,sn)))}))})(e),e.hasPlugin("rtc",!0)?tn(e):((e=>{we(e)&&(e=>{e.on("keydown",(t=>{t.keyCode!==oe.TAB||oe.metaKeyPressed(t)||e.undoManager.transact((()=>{(t.shiftKey?Ot(e):Lt(e))&&t.preventDefault()}))}))})(e),(e=>{e.on("ExecCommand",(t=>{const n=t.command.toLowerCase();"delete"!==n&&"forwarddelete"!==n||!Zt(e)||Kt(e.dom,e.getBody())})),e.on("keydown",(t=>{t.keyCode===oe.BACKSPACE?Gt(e,!1)&&t.preventDefault():t.keyCode===oe.DELETE&&Gt(e,!0)&&t.preventDefault()}))})(e)})(e),(e=>{e.on("BeforeExecCommand",(t=>{const n=t.command.toLowerCase();"indent"===n?Lt(e):"outdent"===n&&Ot(e)})),e.addCommand("InsertUnorderedList",((t,n)=>{Ft(e,"UL",n)})),e.addCommand("InsertOrderedList",((t,n)=>{Ft(e,"OL",n)})),e.addCommand("InsertDefinitionList",((t,n)=>{Ft(e,"DL",n)})),e.addCommand("RemoveList",(()=>{At(e)})),tn(e),e.addCommand("mceListUpdate",((t,n)=>{r(n)&&((e,t)=>{const n=je(e);null===n||Xe(e,n)||e.undoManager.transact((()=>{r(t.styles)&&e.dom.setStyles(n,t.styles),r(t.attrs)&&ie(t.attrs,((t,o)=>e.dom.setAttrib(n,o,t)))}))})(e,n)})),e.addQueryStateHandler("InsertUnorderedList",en(e,"UL")),e.addQueryStateHandler("InsertOrderedList",en(e,"OL")),e.addQueryStateHandler("InsertDefinitionList",en(e,"DL"))})(e)),(e=>{const t=t=>()=>e.execCommand(t);e.hasPlugin("advlist")||(e.ui.registry.addToggleButton("numlist",{icon:"ordered-list",active:!1,tooltip:"Numbered list",onAction:t("InsertOrderedList"),onSetup:ln(e,"OL")}),e.ui.registry.addToggleButton("bullist",{icon:"unordered-list",active:!1,tooltip:"Bullet list",onAction:t("InsertUnorderedList"),onSetup:ln(e,"UL")}))})(e),(e=>{const t={text:"List properties...",icon:"ordered-list",onAction:()=>e.execCommand("mceListProps"),onSetup:an(e,"OL")};e.ui.registry.addMenuItem("listprops",t),e.ui.registry.addContextMenu("lists",{update:t=>{const n=je(e,t);return Ce(n)?["listprops"]:[]}})})(e),(e=>({backspaceDelete:t=>{Gt(e,t)}}))(e))))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/media/plugin.min.js b/static/js/tinymce/plugins/media/plugin.min.js new file mode 100644 index 0000000..f9b72e9 --- /dev/null +++ b/static/js/tinymce/plugins/media/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(r=o=e,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":t;var r,o,a,s})(t)===e,r=t("string"),o=t("object"),a=t("array"),s=e=>!(e=>null==e)(e);class i{constructor(e,t){this.tag=e,this.value=t}static some(e){return new i(!0,e)}static none(){return i.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?i.some(e(this.value)):i.none()}bind(e){return this.tag?e(this.value):i.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:i.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return s(e)?i.some(e):i.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}i.singletonNone=new i(!1);const n=Array.prototype.push,l=(e,t)=>{for(let r=0,o=e.length;r{const t=[];for(let r=0,o=e.length;rh(e,t)?i.from(e[t]):i.none(),h=(e,t)=>u.call(e,t),p=e=>t=>t.options.get(e),g=p("audio_template_callback"),b=p("video_template_callback"),w=p("iframe_template_callback"),v=p("media_live_embeds"),f=p("media_filter_html"),y=p("media_url_resolver"),x=p("media_alt_source"),_=p("media_poster"),k=p("media_dimensions");var j=tinymce.util.Tools.resolve("tinymce.util.Tools"),O=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),A=tinymce.util.Tools.resolve("tinymce.html.DomParser");const S=O.DOM,$=e=>e.replace(/px$/,""),C=e=>{const t=e.attr("style"),r=t?S.parseStyle(t):{};return{type:"ephox-embed-iri",source:e.attr("data-ephox-embed-iri"),altsource:"",poster:"",width:d(r,"max-width").map($).getOr(""),height:d(r,"max-height").map($).getOr("")}},T=(e,t)=>{let r={};for(let o=A({validate:!1,forced_root_block:!1},t).parse(e);o;o=o.walk())if(1===o.type){const e=o.name;if(o.attr("data-ephox-embed-iri")){r=C(o);break}r.source||"param"!==e||(r.source=o.attr("movie")),"iframe"!==e&&"object"!==e&&"embed"!==e&&"video"!==e&&"audio"!==e||(r.type||(r.type=e),r=j.extend(o.attributes.map,r)),"source"===e&&(r.source?r.altsource||(r.altsource=o.attr("src")):r.source=o.attr("src")),"img"!==e||r.poster||(r.poster=o.attr("src"))}return r.source=r.source||r.src||"",r.altsource=r.altsource||"",r.poster=r.poster||"",r},z=e=>{var t;const r=null!==(t=e.toLowerCase().split(".").pop())&&void 0!==t?t:"";return d({mp3:"audio/mpeg",m4a:"audio/x-m4a",wav:"audio/wav",mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",swf:"application/x-shockwave-flash"},r).getOr("")};var D=tinymce.util.Tools.resolve("tinymce.html.Node"),F=tinymce.util.Tools.resolve("tinymce.html.Serializer");const M=(e,t={})=>A({forced_root_block:!1,validate:!1,allow_conditional_comments:!0,...t},e),N=O.DOM,R=e=>/^[0-9.]+$/.test(e)?e+"px":e,E=(e,t)=>{const r=t.attr("style"),o=r?N.parseStyle(r):{};s(e.width)&&(o["max-width"]=R(e.width)),s(e.height)&&(o["max-height"]=R(e.height)),t.attr("style",N.serializeStyle(o))},U=["source","altsource"],P=(e,t,r,o)=>{let a=0,s=0;const i=M(o);i.addNodeFilter("source",(e=>a=e.length));const n=i.parse(e);for(let e=n;e;e=e.walk())if(1===e.type){const o=e.name;if(e.attr("data-ephox-embed-iri")){E(t,e);break}switch(o){case"video":case"object":case"embed":case"img":case"iframe":void 0!==t.height&&void 0!==t.width&&(e.attr("width",t.width),e.attr("height",t.height))}if(r)switch(o){case"video":e.attr("poster",t.poster),e.attr("src",null);for(let r=a;r<2;r++)if(t[U[r]]){const o=new D("source",1);o.attr("src",t[U[r]]),o.attr("type",t[U[r]+"mime"]||null),e.append(o)}break;case"iframe":e.attr("src",t.source);break;case"object":const r=e.getAll("img").length>0;if(t.poster&&!r){e.attr("src",t.poster);const r=new D("img",1);r.attr("src",t.poster),r.attr("width",t.width),r.attr("height",t.height),e.append(r)}break;case"source":if(s<2&&(e.attr("src",t[U[s]]),e.attr("type",t[U[s]+"mime"]||null),!t[U[s]])){e.remove();continue}s++;break;case"img":t.poster||e.remove()}}return F({},o).serialize(n)},L=[{regex:/youtu\.be\/([\w\-_\?&=.]+)/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/youtube\.com(.+)v=([^&]+)(&([a-z0-9&=\-_]+))?/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$2?$4",allowFullscreen:!0},{regex:/youtube.com\/embed\/([a-z0-9\?&=\-_]+)/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/vimeo\.com\/([0-9]+)\?h=(\w+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$1?h=$2&title=0&byline=0&portrait=0&color=8dc7dc",allowFullscreen:!0},{regex:/vimeo\.com\/(.*)\/([0-9]+)\?h=(\w+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$2?h=$3&title=0&byline=0",allowFullscreen:!0},{regex:/vimeo\.com\/([0-9]+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc",allowFullscreen:!0},{regex:/vimeo\.com\/(.*)\/([0-9]+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$2?title=0&byline=0",allowFullscreen:!0},{regex:/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,type:"iframe",w:425,h:350,url:'maps.google.com/maps/ms?msid=$2&output=embed"',allowFullscreen:!1},{regex:/dailymotion\.com\/video\/([^_]+)/,type:"iframe",w:480,h:270,url:"www.dailymotion.com/embed/video/$1",allowFullscreen:!0},{regex:/dai\.ly\/([^_]+)/,type:"iframe",w:480,h:270,url:"www.dailymotion.com/embed/video/$1",allowFullscreen:!0}],I=(e,t)=>{const r=(e=>{const t=e.match(/^(https?:\/\/|www\.)(.+)$/i);return t&&t.length>1?"www."===t[1]?"https://":t[1]:"https://"})(t),o=e.regex.exec(t);let a=r+e.url;if(s(o))for(let e=0;eo[e]?o[e]:""));return a.replace(/\?$/,"")},B=e=>{const t=L.filter((t=>t.regex.test(e)));return t.length>0?j.extend({},t[0],{url:I(t[0],e)}):null},G=(e,t)=>{var r;const o=j.extend({},t);if(!o.source&&(j.extend(o,T(null!==(r=o.embed)&&void 0!==r?r:"",e.schema)),!o.source))return"";o.altsource||(o.altsource=""),o.poster||(o.poster=""),o.source=e.convertURL(o.source,"source"),o.altsource=e.convertURL(o.altsource,"source"),o.sourcemime=z(o.source),o.altsourcemime=z(o.altsource),o.poster=e.convertURL(o.poster,"poster");const a=B(o.source);if(a&&(o.source=a.url,o.type=a.type,o.allowfullscreen=a.allowFullscreen,o.width=o.width||String(a.w),o.height=o.height||String(a.h)),o.embed)return P(o.embed,o,!0,e.schema);{const t=g(e),r=b(e),a=w(e);return o.width=o.width||"300",o.height=o.height||"150",j.each(o,((t,r)=>{o[r]=e.dom.encode(""+t)})),"iframe"===o.type?((e,t)=>{if(t)return t(e);{const t=e.allowfullscreen?' allowFullscreen="1"':"";return'"}})(o,a):"application/x-shockwave-flash"===o.sourcemime?(e=>{let t='';return e.poster&&(t+=''),t+="",t})(o):-1!==o.sourcemime.indexOf("audio")?((e,t)=>t?t(e):'")(o,t):((e,t)=>t?t(e):'")(o,r)}},W=e=>e.hasAttribute("data-mce-object")||e.hasAttribute("data-ephox-embed-iri"),q={},H=e=>t=>G(e,t),J=(e,t)=>{const r=y(e);return r?((e,t,r)=>new Promise(((o,a)=>{const s=r=>(r.html&&(q[e.source]=r),o({url:e.source,html:r.html?r.html:t(e)}));q[e.source]?s(q[e.source]):r({url:e.source},s,a)})))(t,H(e),r):((e,t)=>Promise.resolve({html:t(e),url:e.source}))(t,H(e))},K=(e,t)=>{const r={};return d(e,"dimensions").each((e=>{l(["width","height"],(o=>{d(t,o).orThunk((()=>d(e,o))).each((e=>r[o]=e))}))})),r},Q=(e,t)=>{const r=t&&"dimensions"!==t?((e,t)=>d(t,e).bind((e=>d(e,"meta"))))(t,e).getOr({}):{},a=((e,t,r)=>a=>{const s=()=>d(e,a),n=()=>d(t,a),l=e=>d(e,"value").bind((e=>e.length>0?i.some(e):i.none()));return{[a]:(a===r?s().bind((e=>o(e)?l(e).orThunk(n):n().orThunk((()=>i.from(e))))):n().orThunk((()=>s().bind((e=>o(e)?l(e):i.from(e)))))).getOr("")}})(e,r,t);return{...a("source"),...a("altsource"),...a("poster"),...a("embed"),...K(e,r)}},V=e=>{const t={...e,source:{value:d(e,"source").getOr("")},altsource:{value:d(e,"altsource").getOr("")},poster:{value:d(e,"poster").getOr("")}};return l(["width","height"],(r=>{d(e,r).each((e=>{const o=t.dimensions||{};o[r]=e,t.dimensions=o}))})),t},X=e=>t=>{const r=t&&t.msg?"Media embed handler error: "+t.msg:"Media embed handler threw unknown error.";e.notificationManager.open({type:"error",text:r})},Y=(e,t)=>o=>{if(r(o.url)&&o.url.trim().length>0){const r=o.html,a={...T(r,t.schema),source:o.url,embed:r};e.setData(V(a))}},Z=(e,t)=>{const r=e.dom.select("*[data-mce-object]");e.insertContent(t),((e,t)=>{const r=e.dom.select("*[data-mce-object]");for(let e=0;e=0;o--)t[e]===r[o]&&r.splice(o,1);e.selection.select(r[0])})(e,r),e.nodeChanged()},ee=(e,t)=>s(t)&&"ephox-embed-iri"===t&&s(B(e)),te=(e,t)=>((e,t)=>e.width!==t.width||e.height!==t.height)(e,t)&&ee(t.source,e.type),re=e=>{const t=(e=>{const t=e.selection.getNode(),r=W(t)?e.serializer.serialize(t,{selection:!0}):"",o=T(r,e.schema),a=(()=>{if(ee(o.source,o.type)){const r=e.dom.getRect(t);return{width:r.w.toString().replace(/px$/,""),height:r.h.toString().replace(/px$/,"")}}return{}})();return{embed:r,...o,...a}})(e),r=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})(t),o=V(t),a=k(e)?[{type:"sizeinput",name:"dimensions",label:"Constrain proportions",constrain:!0}]:[],s={title:"General",name:"general",items:c([[{name:"source",type:"urlinput",filetype:"media",label:"Source"}],a])},i=[];x(e)&&i.push({name:"altsource",type:"urlinput",filetype:"media",label:"Alternative source URL"}),_(e)&&i.push({name:"poster",type:"urlinput",filetype:"image",label:"Media poster (Image URL)"});const n={title:"Advanced",name:"advanced",items:i},l=[s,{title:"Embed",items:[{type:"textarea",name:"embed",label:"Paste your embed code below:"}]}];i.length>0&&l.push(n);const m={type:"tabpanel",tabs:l},u=e.windowManager.open({title:"Insert/Edit Media",size:"normal",body:m,buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],onSubmit:t=>{const o=Q(t.getData());((e,t,r)=>{var o,a;t.embed=te(e,t)&&k(r)?G(r,{...t,embed:""}):P(null!==(o=t.embed)&&void 0!==o?o:"",t,!1,r.schema),t.embed&&(e.source===t.source||(a=t.source,h(q,a)))?Z(r,t.embed):J(r,t).then((e=>{Z(r,e.html)})).catch(X(r))})(r.get(),o,e),t.close()},onChange:(t,o)=>{switch(o.name){case"source":((t,r)=>{const o=Q(r.getData(),"source");t.source!==o.source&&(Y(u,e)({url:o.source,html:""}),J(e,o).then(Y(u,e)).catch(X(e)))})(r.get(),t);break;case"embed":(t=>{var r;const o=Q(t.getData()),a=T(null!==(r=o.embed)&&void 0!==r?r:"",e.schema);t.setData(V(a))})(t);break;case"dimensions":case"altsource":case"poster":((t,r,o)=>{const a=Q(t.getData(),r),s=te(o,a)&&k(e)?{...a,embed:""}:a,i=G(e,s);t.setData(V({...s,embed:i}))})(t,o.name,r.get())}r.set(Q(t.getData()))},initialData:o})};var oe=tinymce.util.Tools.resolve("tinymce.Env");const ae=e=>{const t=e.name;return"iframe"===t||"video"===t||"audio"===t},se=(e,t,r,o=null)=>{const a=e.attr(r);return s(a)?a:h(t,r)?null:o},ie=(e,t,r)=>{const o="img"===t.name||"video"===e.name,a=o?"300":null,s="audio"===e.name?"30":"150",i=o?s:null;t.attr({width:se(e,r,"width",a),height:se(e,r,"height",i)})},ne=(e,t)=>{const r=t.name,o=new D("img",1);return ce(e,t,o),ie(t,o,{}),o.attr({style:t.attr("style"),src:oe.transparentSrc,"data-mce-object":r,class:"mce-object mce-object-"+r}),o},le=(e,t)=>{var r;const o=t.name,a=new D("span",1);a.attr({contentEditable:"false",style:t.attr("style"),"data-mce-object":o,class:"mce-preview-object mce-object-"+o}),ce(e,t,a);const i=e.dom.parseStyle(null!==(r=t.attr("style"))&&void 0!==r?r:""),n=new D(o,1);if(ie(t,n,i),n.attr({src:t.attr("src"),style:t.attr("style"),class:t.attr("class")}),"iframe"===o)n.attr({allowfullscreen:t.attr("allowfullscreen"),frameborder:"0"});else{l(["controls","crossorigin","currentTime","loop","muted","poster","preload"],(e=>{n.attr(e,t.attr(e))}));const r=a.attr("data-mce-html");s(r)&&((e,t,r,o)=>{const a=M(e.schema).parse(o,{context:t});for(;a.firstChild;)r.append(a.firstChild)})(e,o,n,unescape(r))}const c=new D("span",1);return c.attr("class","mce-shim"),a.append(n),a.append(c),a},ce=(e,t,r)=>{var o;const a=null!==(o=t.attributes)&&void 0!==o?o:[];let s=a.length;for(;s--;){const t=a[s].name;let o=a[s].value;"width"===t||"height"===t||"style"===t||(n="data-mce-",(i=t).length>=9&&i.substr(0,9)===n)||("data"!==t&&"src"!==t||(o=e.convertURL(o,t)),r.attr("data-mce-p-"+t,o))}var i,n;const c=F({inner:!0},e.schema),m=new D("div",1);l(t.children(),(e=>m.append(e)));const u=c.serialize(m);u&&(r.attr("data-mce-html",escape(u)),r.empty())},me=e=>{const t=e.attr("class");return r(t)&&/\btiny-pageembed\b/.test(t)},ue=e=>{let t=e;for(;t=t.parent;)if(t.attr("data-ephox-embed-iri")||me(t))return!0;return!1},de=(e,t,r)=>{const o=(0,e.options.get)("xss_sanitization"),a=f(e);return M(e.schema,{sanitize:o,validate:a}).parse(r,{context:t})},he=e=>t=>{const r=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",r),r(),()=>{e.off("NodeChange",r)}};e.add("media",(e=>((e=>{const t=e.options.register;t("audio_template_callback",{processor:"function"}),t("video_template_callback",{processor:"function"}),t("iframe_template_callback",{processor:"function"}),t("media_live_embeds",{processor:"boolean",default:!0}),t("media_filter_html",{processor:"boolean",default:!0}),t("media_url_resolver",{processor:"function"}),t("media_alt_source",{processor:"boolean",default:!0}),t("media_poster",{processor:"boolean",default:!0}),t("media_dimensions",{processor:"boolean",default:!0})})(e),(e=>{e.addCommand("mceMedia",(()=>{re(e)}))})(e),(e=>{const t=()=>e.execCommand("mceMedia");e.ui.registry.addToggleButton("media",{tooltip:"Insert/edit media",icon:"embed",onAction:t,onSetup:t=>{const r=e.selection;t.setActive(W(r.getNode()));const o=r.selectorChangedWithUnbind("img[data-mce-object],span[data-mce-object],div[data-ephox-embed-iri]",t.setActive).unbind,a=he(e)(t);return()=>{o(),a()}}}),e.ui.registry.addMenuItem("media",{icon:"embed",text:"Media...",onAction:t,onSetup:he(e)})})(e),(e=>{e.on("ResolveName",(e=>{let t;1===e.target.nodeType&&(t=e.target.getAttribute("data-mce-object"))&&(e.name=t)}))})(e),(e=>{e.on("PreInit",(()=>{const{schema:t,serializer:r,parser:o}=e,a=t.getBoolAttrs();l("webkitallowfullscreen mozallowfullscreen".split(" "),(e=>{a[e]={}})),((e,t)=>{const r=m(e);for(let o=0,a=r.length;o{const o=t.getElementRule(r);o&&l(e,(e=>{o.attributes[e]={},o.attributesOrder.push(e)}))})),o.addNodeFilter("iframe,video,audio,object,embed",(e=>t=>{let r,o=t.length;for(;o--;)r=t[o],r.parent&&(r.parent.attr("data-mce-object")||(ae(r)&&v(e)?ue(r)||r.replace(le(e,r)):ue(r)||r.replace(ne(e,r))))})(e)),r.addAttributeFilter("data-mce-object",((t,r)=>{var o;let a=t.length;for(;a--;){const s=t[a];if(!s.parent)continue;const i=s.attr(r),n=new D(i,1);if("audio"!==i){const e=s.attr("class");e&&-1!==e.indexOf("mce-preview-object")&&s.firstChild?n.attr({width:s.firstChild.attr("width"),height:s.firstChild.attr("height")}):n.attr({width:s.attr("width"),height:s.attr("height")})}n.attr({style:s.attr("style")});const c=null!==(o=s.attributes)&&void 0!==o?o:[];let m=c.length;for(;m--;){const e=c[m].name;0===e.indexOf("data-mce-p-")&&n.attr(e.substr(11),c[m].value)}const u=s.attr("data-mce-html");if(u){const t=de(e,i,unescape(u));l(t.children(),(e=>n.append(e)))}s.replace(n)}}))})),e.on("SetContent",(()=>{const t=e.dom;l(t.select("span.mce-preview-object"),(e=>{0===t.select("span.mce-shim",e).length&&t.add(e,"span",{class:"mce-shim"})}))}))})(e),(e=>{e.on("click keyup touchend",(()=>{const t=e.selection.getNode();t&&e.dom.hasClass(t,"mce-preview-object")&&e.dom.getAttrib(t,"data-mce-selected")&&t.setAttribute("data-mce-selected","2")})),e.on("ObjectResized",(t=>{const r=t.target;if(r.getAttribute("data-mce-object")){let o=r.getAttribute("data-mce-html");o&&(o=unescape(o),r.setAttribute("data-mce-html",escape(P(o,{width:String(t.width),height:String(t.height)},!1,e.schema))))}}))})(e),(e=>({showDialog:()=>{re(e)}}))(e))))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/nonbreaking/plugin.min.js b/static/js/tinymce/plugins/nonbreaking/plugin.min.js new file mode 100644 index 0000000..1406420 --- /dev/null +++ b/static/js/tinymce/plugins/nonbreaking/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,o=e("boolean"),a=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),s=t("nonbreaking_wrap"),r=(n,e)=>{let o="";for(let a=0;a{const o=s(n)||n.plugins.visualchars?`${r(" ",e)}`:r(" ",e);n.undoManager.transact((()=>n.insertContent(o)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");const u=n=>e=>{const o=()=>{e.setEnabled(n.selection.isEditable())};return n.on("NodeChange",o),o(),()=>{n.off("NodeChange",o)}};n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>o(n)?{value:n?3:0,valid:!0}:a(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e,onSetup:u(n)}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e,onSetup:u(n)})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(o=>{if(o.keyCode===l.TAB&&!o.isDefaultPrevented()){if(o.shiftKey)return;o.preventDefault(),o.stopImmediatePropagation(),c(n,e)}}))})(n)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/pagebreak/plugin.min.js b/static/js/tinymce/plugins/pagebreak/plugin.min.js new file mode 100644 index 0000000..d27a40e --- /dev/null +++ b/static/js/tinymce/plugins/pagebreak/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),n=t("pagebreak_separator"),o=t("pagebreak_split_block"),r="mce-pagebreak",s=e=>{const t=``;return e?`

      ${t}

      `:t},c=e=>a=>{const t=()=>{a.setEnabled(e.selection.isEditable())};return e.on("NodeChange",t),t(),()=>{e.off("NodeChange",t)}};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(o(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a,onSetup:c(e)}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a,onSetup:c(e)})})(e),(e=>{const a=n(e),t=()=>o(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(n=>{let o,s,c=n.length;for(;c--;)if(o=n[c],s=o.attr("class"),s&&-1!==s.indexOf(r)){const n=o.parent;if(n&&e.schema.getBlockElements()[n.name]&&t()){n.type=3,n.value=a,n.raw=!0,o.remove();continue}o.type=3,o.value=a,o.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,r)&&(a.name="pagebreak")}))})(e)}))}(); \ No newline at end of file diff --git a/static/js/tinymce/plugins/preview/plugin.min.js b/static/js/tinymce/plugins/preview/plugin.min.js new file mode 100644 index 0000000..97eb376 --- /dev/null +++ b/static/js/tinymce/plugins/preview/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 6.7.2 (2023-10-25) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='"})),d&&(l+='");const y=r(e),u=c(e),v=' diff --git a/templates/builder/_search_results_th.html b/templates/builder/_search_results_th.html new file mode 100644 index 0000000..d565b1d --- /dev/null +++ b/templates/builder/_search_results_th.html @@ -0,0 +1,9 @@ + diff --git a/templates/builder/base-menu.html b/templates/builder/base-menu.html new file mode 100644 index 0000000..6ba094b --- /dev/null +++ b/templates/builder/base-menu.html @@ -0,0 +1,67 @@ +
      +
      + +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      +
      diff --git a/templates/builder/current-deck-list.html b/templates/builder/current-deck-list.html new file mode 100644 index 0000000..f67a672 --- /dev/null +++ b/templates/builder/current-deck-list.html @@ -0,0 +1,27 @@ +
      + +
      + +
      + {% include "builder/_current_deck_list-1.html" + :class "main" + :deck-list main-deck + :title "Main Deck" + %} + + {% include "builder/_current_deck_list-1.html" + :class "extra" + :deck-list extra-deck + :title "Extra Deck" + %} + + {% include "builder/_current_deck_list-1.html" + :class "side" + :deck-list side-deck + :title "Side Deck" + %} +
      + +{% include "builder/_jquery_droppable.html" %} diff --git a/templates/builder/deck-controls.html b/templates/builder/deck-controls.html new file mode 100644 index 0000000..784c0a0 --- /dev/null +++ b/templates/builder/deck-controls.html @@ -0,0 +1,99 @@ +
      +
      +
      +
      +
      + +
      +
      +
      + +
      +
      + +
      + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      + +
      + +
      + +
      + +
      +
      + +
      +
      + +
      + +
      + +
      + + +
      + +
      + +
      +
      +
      diff --git a/templates/builder/index.html b/templates/builder/index.html new file mode 100644 index 0000000..2246a62 --- /dev/null +++ b/templates/builder/index.html @@ -0,0 +1,48 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Deck Builder" _}{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + + +
      +
      + {% include "builder/base-menu.html" %} +
      +
      + + + + +
      + {% include "builder/deck-controls.html" %} +
      + + +
      + +
      + + +
      +   +
      +
      + + +
      +
      +
      +
      +
      +{% endblock %} diff --git a/templates/builder/saved-category-list.html b/templates/builder/saved-category-list.html new file mode 100644 index 0000000..91e1b3b --- /dev/null +++ b/templates/builder/saved-category-list.html @@ -0,0 +1,12 @@ +
      + +
      diff --git a/templates/builder/saved-deck-list.html b/templates/builder/saved-deck-list.html new file mode 100644 index 0000000..8450f2b --- /dev/null +++ b/templates/builder/saved-deck-list.html @@ -0,0 +1,12 @@ +
      + +
      diff --git a/templates/builder/search-results.html b/templates/builder/search-results.html new file mode 100644 index 0000000..fa4c699 --- /dev/null +++ b/templates/builder/search-results.html @@ -0,0 +1,121 @@ +{% if length > 1 %} +{% include "pages.html" :url "builder/search" :hx-target "#search-results" :hx-include "[name='search-form']" :class "is-small" %} +{% endif %} + +{% if cards %} + + + + + + + +
      + {% include "builder/_search_results_th.html" :url "/builder/search" :target "search-results" :name "ID" :sort "id" %} + + {% include "builder/_search_results_th.html" :url "/builder/search" :target "search-results" :name "Name" :sort "name" %} +
      +{% for card in cards %} + + + + + + + +
      + + Card Image for Yu-Gi-Oh! #{{ card.passcode }} + + + {{ card.name }} +
      + +{% endfor %} + +{% if length > 1 %} +

      {{ length }} result{% if length > 2 %}s{% endif %} found.

      +{% include "pages.html" :url "builder/search" :hx-target "#search-results" :hx-include "[name='search-form']" :class "is-small" %} +{% endif %} + + +{% endif %} diff --git a/templates/cards/_advanced_search_attrs.html b/templates/cards/_advanced_search_attrs.html new file mode 100644 index 0000000..217281a --- /dev/null +++ b/templates/cards/_advanced_search_attrs.html @@ -0,0 +1,113 @@ +
      +
      +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      + +
      +
      +
      +
      diff --git a/templates/cards/_advanced_search_input_numeric.html b/templates/cards/_advanced_search_input_numeric.html new file mode 100644 index 0000000..e83493d --- /dev/null +++ b/templates/cards/_advanced_search_input_numeric.html @@ -0,0 +1,12 @@ +
      + +
      diff --git a/templates/cards/_advanced_search_numeric.html b/templates/cards/_advanced_search_numeric.html new file mode 100644 index 0000000..01ab5a8 --- /dev/null +++ b/templates/cards/_advanced_search_numeric.html @@ -0,0 +1,23 @@ +
      +
      +
      + {% include "cards/_advanced_search_select_kind.html" :name "scale-kind" %} + {% include "cards/_advanced_search_input_numeric.html" :name "Scale" %} +
      + +
      + {% include "cards/_advanced_search_select_kind.html" :name "level-kind" %} + {% include "cards/_advanced_search_input_numeric.html" :name "Level" %} +
      + +
      + {% include "cards/_advanced_search_select_kind.html" :name "atk-kind" %} + {% include "cards/_advanced_search_input_numeric.html" :name "ATK" %} +
      + +
      + {% include "cards/_advanced_search_select_kind.html" :name "def-kind" %} + {% include "cards/_advanced_search_input_numeric.html" :name "DEF" %} +
      +
      +
      diff --git a/templates/cards/_advanced_search_select.html b/templates/cards/_advanced_search_select.html new file mode 100644 index 0000000..9c721a3 --- /dev/null +++ b/templates/cards/_advanced_search_select.html @@ -0,0 +1,15 @@ +
      + +
      diff --git a/templates/cards/_advanced_search_select_kind.html b/templates/cards/_advanced_search_select_kind.html new file mode 100644 index 0000000..99be635 --- /dev/null +++ b/templates/cards/_advanced_search_select_kind.html @@ -0,0 +1,16 @@ +
      +
      + +
      +
      diff --git a/templates/cards/_advanced_search_top.html b/templates/cards/_advanced_search_top.html new file mode 100644 index 0000000..beffe44 --- /dev/null +++ b/templates/cards/_advanced_search_top.html @@ -0,0 +1,57 @@ +
      +
      + +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      + +
      + +
      + + +
      + +
      +
      diff --git a/templates/cards/_search_result_th.html b/templates/cards/_search_result_th.html new file mode 100644 index 0000000..4e8a8f5 --- /dev/null +++ b/templates/cards/_search_result_th.html @@ -0,0 +1,9 @@ + + + diff --git a/templates/cards/index.html b/templates/cards/index.html new file mode 100644 index 0000000..7de514b --- /dev/null +++ b/templates/cards/index.html @@ -0,0 +1,35 @@ +{% extends "layouts/default.html" %} +{% block title %}Card List{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + + +
      +
      + {% include "cards/_advanced_search_top.html" %} +
      +
      + + + +
      + {% include "cards/search-results.html" %} +
      + +
      + + +{% endblock %} diff --git a/templates/cards/search-results.html b/templates/cards/search-results.html new file mode 100644 index 0000000..ccb5d17 --- /dev/null +++ b/templates/cards/search-results.html @@ -0,0 +1,78 @@ +{% include "pages.html" :hx-include "[name='search-form']" :hx-target "#search-results" :url "/cards/search" %} + +{% if cards %} + + + + + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Image" :sort "id" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Passcode" :sort "passcode" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Attribute" :sort "attribute" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Name" :sort "name" :form "search-form" %} + + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Archetype" :sort "archetype" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Race" :sort "race" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Type" :sort "type" :form "search-form" %} + + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Card Text" :sort "card-text" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "ATK" :sort "atk" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "DEF" :sort "def" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Level" :sort "level" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Scale" :sort "scale" :form "search-form" %} + {% include "cards/_search_result_th.html" :url "/cards/search" :hx-target "#search-results" :name "Frame" :sort "frame-type" :form "search-form" %} + + + + {% for card in cards %} + + + + + + + + + + + + + + + + {% endfor %} + +
      + + + Card Image for Yu-Gi-Oh! #{{card.passcode}} - {{card.name}} + + {{ card.passcode }}{% if card.attribute %} + {{ card.attribute }} + {% elif card.frame-type == "spell" %} + Spell Card + {% elif card.frame-type == "trap" %} + Spell Card + {% endif %} + {{ card.name }}{{ card.archetype }}{{ card.race }}{{ card.type }}{{ card.desc }}{{ card.atk }}{{ card.def }}{{ card.level }}{{ card.scale }}{{ card.frame-type }}
      + +
      +

      {{ length }} result{% if length > 2 %}s{% endif %} found.

      + {% include "pages.html" :url "cards/search" :hx-target "#search-results" :hx-include "[name='search-form']" %} +
      + +{% else %} +
      +

      Nothing to see here!

      +
      +{% endif %} diff --git a/templates/cards/variant-results.html b/templates/cards/variant-results.html new file mode 100644 index 0000000..88bcea9 --- /dev/null +++ b/templates/cards/variant-results.html @@ -0,0 +1,61 @@ +{% if item %} +
      + {{ token | safe }} + + +
      + +
      +
      +
      + +
      + +
      +
      + +
      + +
      + +
      +
      + + +
      + +
      + +
      +
      +
      +
      + +
      + +
      +{% endif %} diff --git a/templates/cards/view.html b/templates/cards/view.html new file mode 100644 index 0000000..a1483e3 --- /dev/null +++ b/templates/cards/view.html @@ -0,0 +1,57 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Card Set List" _}{% endblock %} +{% block content %} +
      + {% if ygo-card-sets %} +
      + {{ token | safe }} +
      +
      +
      +
      +
      +
      +

      + + + +

      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + {{ ygo-card-sets.0.passcode.id }} - {{ ygo-card-sets.0.passcode.name }} +
      + + {% for set in ygo-card-sets %} + + + + {{ ygo-card-sets.0.passcode.name }} - {{ set.code.name }} - {{ set.rarity.name }} - {{ set.edition.name }} - {{ set.name.name }} - {{ set.price }} + + +
      + {% endfor %} + +
      +
      +
      +
      + {% endif %} +
      +{% endblock %} diff --git a/templates/cards/ygo-price.html b/templates/cards/ygo-price.html new file mode 100644 index 0000000..0fd616e --- /dev/null +++ b/templates/cards/ygo-price.html @@ -0,0 +1,46 @@ +{% if price %} +
      + +
      + +
      +
      + +
      + +
      + +
      +
      + +
      + +
      + +
      +
      + +
      + +
      + +
      +
      + +
      + +
      + +
      +
      +{% endif %} diff --git a/templates/category/category-menu-bar.html b/templates/category/category-menu-bar.html new file mode 100644 index 0000000..b851151 --- /dev/null +++ b/templates/category/category-menu-bar.html @@ -0,0 +1,4 @@ + diff --git a/templates/category/child-form.html b/templates/category/child-form.html new file mode 100644 index 0000000..0bb5f29 --- /dev/null +++ b/templates/category/child-form.html @@ -0,0 +1,44 @@ + + + + + + + + + +
      + + + +
      + + + + + + + +
      diff --git a/templates/category/child-list.html b/templates/category/child-list.html new file mode 100644 index 0000000..93c2117 --- /dev/null +++ b/templates/category/child-list.html @@ -0,0 +1,10 @@ +
      + {% if children %} +
      +
      {% for child in children %}{{ child.name }}
      +{% endfor %}
      +
      + {% else %} +

      Node has no children.

      + {% endif %} +
      diff --git a/templates/category/explain.html b/templates/category/explain.html new file mode 100644 index 0000000..295320d --- /dev/null +++ b/templates/category/explain.html @@ -0,0 +1,58 @@ +{% extends "layouts/default.html" %} +{% block title %}Category Tree{% endblock %} +{% block content %} +
      +
      + {{ token | safe }} + + +
      +

      You have a hierarchy of data you want to represent:

      + +
      + + categories + +
      + +

      If you want to store this hierarchy in a database (like an SQL database), the data has to be "normalized," or "flattened" ...

      + +
      + + nested_categories + +
      + +

      Using the magic of SQL, the actual category ID becomes irrelevant as we use the left bounding number as the new index...

      + +
      + + nested_numbered + +
      + +

      If you imagine it like a bunch of rubber bands (a different rubber band for each "category"), and the bands can wrap around each other, and there's an infinite number line that the bands line up on, kind of like if they were next to a ruler, and you can move bands around freely ...

      + +

      Original Article SQL Mumbo Jumbo

      +

      TODO: much better documentation on how this works...

      +
      +
      +
      +{% endblock %} diff --git a/templates/category/index.html b/templates/category/index.html new file mode 100644 index 0000000..0378ea0 --- /dev/null +++ b/templates/category/index.html @@ -0,0 +1,49 @@ +{% extends "layouts/default.html" %} +{% block title %}Category Tree{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +{% include "category/category-menu-bar.html" %} + +
      +
      +
      +
      + +
      +
      +
      + + {% if category.id %} +

      Decks

      +
      +
      + +

      Children

      +
      + {% include "category/child-list.html" %} +
      + {% endif %} + +
      + +
      + {% include "category/child-form.html" %} +
      + + + +

      Explain all this to me..

      +
      +{% endblock %} diff --git a/templates/chat.html b/templates/chat.html new file mode 100644 index 0000000..34635b7 --- /dev/null +++ b/templates/chat.html @@ -0,0 +1,21 @@ +{% extends "layouts/default.html" %} +{% block title %}LISP CHAT{% endblock %} +{% block content %} +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +{% endblock %} diff --git a/templates/construct/constructed-deck-list.html b/templates/construct/constructed-deck-list.html new file mode 100644 index 0000000..43575b2 --- /dev/null +++ b/templates/construct/constructed-deck-list.html @@ -0,0 +1,12 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Complete Selected Card Listing" _}{% endblock %} +{% block content %} + +{% include "decks/deck-menu-bar.html" %} + +
      +
      {% for row in table %}{{ row.deck-item.passcode }} - {{ row.card.name }} - {{ row.deck-set.code.name }} - {{ row.deck-set.rarity.name }} - {{ row.deck-set.edition.name }} - {{ row.condition.name }} {{ row.set-item.sell-price }}
      +{% endfor %}
      +
      + +{% endblock %} diff --git a/templates/construct/index.html b/templates/construct/index.html new file mode 100644 index 0000000..f1c3c48 --- /dev/null +++ b/templates/construct/index.html @@ -0,0 +1,99 @@ +{% extends "layouts/default.html" %} +{% block title %}Constructed Deck List{% endblock %} +{% block content %} + +
      + {{ token | safe }} +
      + +{% include "decks/deck-menu-bar.html" %} + +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      + +
      + {% if id %} + {% include "decks/saved-category-list.html" %} + {% endif %} +
      + +
      + {% if id %} + + +
      +
      + {% endif %} +
      + + + + {% if id %} + + +
      + +
      + +
      +
      + +
      + +
      + +
      +
      + +
      +
      + +
      + +
      + +
      +
      + + {% endif %} + +{% endblock %} diff --git a/templates/construct/select-sets.html b/templates/construct/select-sets.html new file mode 100644 index 0000000..387ff06 --- /dev/null +++ b/templates/construct/select-sets.html @@ -0,0 +1,95 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Select Card Sets" _}{% endblock %} +{% block content %} + + +
      + {{ token | safe }} +
      + +
      +
      + {% for card-sets in sets %} +
      +
      +
      +
      +

      + + + +

      +
      +
      +
      +
      +
      +
      + +
      +
      + +
      +
      +
      +
      + +
      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + {% endfor %} +
      +
      + +
      +
      +
      +
      +{% endblock %} diff --git a/templates/contact/admin.html b/templates/contact/admin.html new file mode 100644 index 0000000..771474a --- /dev/null +++ b/templates/contact/admin.html @@ -0,0 +1,38 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Feedback Admin Panel" _}{% endblock %} +{% block content %} +{% if is-admin %} +
      + {{ token | safe }} +
      + +
      + +
      + +
      + {% include "contact/list.html" %} +
      + +
      +
      +
      + +
      +
      +{% else %} +

      {_ "Oops" _}

      +

      + {_ "Nothing to see here!" _} +

      + +{% endif %} +{% endblock %} diff --git a/templates/contact/index.html b/templates/contact/index.html new file mode 100644 index 0000000..d2265f1 --- /dev/null +++ b/templates/contact/index.html @@ -0,0 +1,43 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Contact Us" _}{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +{% if is-admin %} +
      + +
      +{% endif %} + +
      +
      +
      + {_ "You can type up a message here and submit it for the administrator to see." _} + {_ "Try typing it up in Notepad, then Copy/Paste." _} +
      +
      + +
      + +
      + +
      +
      + +
      +
      + +
      +
      +
      +{% endblock %} diff --git a/templates/contact/list.html b/templates/contact/list.html new file mode 100644 index 0000000..006344e --- /dev/null +++ b/templates/contact/list.html @@ -0,0 +1,37 @@ +{% if feedback %} +
      + + + + + + + + {% for message in feedback %} + + + + + + + {% endfor %} +
      User IDUsernameBodyDelete
      {{ message.user-id }}{{ message.user.name }}{{ message.body }} + +
      +
      +{% else %} +
      +
      + No Messages +
      +
      + Squeaky clean! +
      +
      +{% endif %} diff --git a/templates/decks/_field_control_select.html b/templates/decks/_field_control_select.html new file mode 100644 index 0000000..a5364e3 --- /dev/null +++ b/templates/decks/_field_control_select.html @@ -0,0 +1,21 @@ +
      + +
      +
      + +
      +
      +
      diff --git a/templates/decks/cards-in-decks.html b/templates/decks/cards-in-decks.html new file mode 100644 index 0000000..7e4b281 --- /dev/null +++ b/templates/decks/cards-in-decks.html @@ -0,0 +1,33 @@ +{% extends "layouts/default.html" %} +{% block title %}Cards List - Cards In Decks{% endblock %} +{% block content %} + +{% include "decks/deck-menu-bar.html" %} + +{% for (card-id . card-params) in table %} +
      +
      +
      +
      + + Card Image for Yu-Gi-Oh! - {{ card-id }} + +
      +
      +
      +

      + {{ card-params.0.ygo-card.id }} - {{ card-params.0.ygo-card.name }} +
      +

      +

      +
      +
      +
      +{% endfor %} +{% endblock %} diff --git a/templates/decks/category-select.html b/templates/decks/category-select.html new file mode 100644 index 0000000..671ac98 --- /dev/null +++ b/templates/decks/category-select.html @@ -0,0 +1,12 @@ +
      + +
      diff --git a/templates/decks/deck-controls.html b/templates/decks/deck-controls.html new file mode 100644 index 0000000..ec3d50b --- /dev/null +++ b/templates/decks/deck-controls.html @@ -0,0 +1,41 @@ +
      + + + + KDE Team Deck List + + + + Construct Deck + + + +
      diff --git a/templates/decks/deck-html-results-1.html b/templates/decks/deck-html-results-1.html new file mode 100644 index 0000000..13f9882 --- /dev/null +++ b/templates/decks/deck-html-results-1.html @@ -0,0 +1,24 @@ +

      + {{ class | capfirst }} Deck ({{ deck | length }}) +

      + +
      + {% for card in deck %} +
      +
      +
      +
      + + Card Image for Yu-Gi-Oh! #{{ card.passcode }} - {{ card.name }} + +
      +
      +
      +
      + {% endfor %} +
      diff --git a/templates/decks/deck-html-results.html b/templates/decks/deck-html-results.html new file mode 100644 index 0000000..7aa5183 --- /dev/null +++ b/templates/decks/deck-html-results.html @@ -0,0 +1,11 @@ +{% if main-deck %} +{% include "decks/deck-html-results-1.html" :deck main-deck :class "main" %} +{% endif %} + +{% if extra-deck %} +{% include "decks/deck-html-results-1.html" :deck extra-deck :class "extra" %} +{% endif %} + +{% if side-deck %} +{% include "decks/deck-html-results-1.html" :deck side-deck :class "side" %} +{% endif %} diff --git a/templates/decks/deck-image-listing.html b/templates/decks/deck-image-listing.html new file mode 100644 index 0000000..11c57b1 --- /dev/null +++ b/templates/decks/deck-image-listing.html @@ -0,0 +1,18 @@ +{% if files %} +

      Deck Images

      +
      + {% for image in files %} +
      +
      +
      +
      + + Deck image for {{ image }} + +
      +
      +
      +
      + {% endfor %} +
      +{% endif %} diff --git a/templates/decks/deck-menu-bar.html b/templates/decks/deck-menu-bar.html new file mode 100644 index 0000000..b6af254 --- /dev/null +++ b/templates/decks/deck-menu-bar.html @@ -0,0 +1,8 @@ + diff --git a/templates/decks/deck-select.html b/templates/decks/deck-select.html new file mode 100644 index 0000000..b8c20d8 --- /dev/null +++ b/templates/decks/deck-select.html @@ -0,0 +1,13 @@ +
      + +
      diff --git a/templates/decks/deck-text-list-original.html b/templates/decks/deck-text-list-original.html new file mode 100644 index 0000000..a657e88 --- /dev/null +++ b/templates/decks/deck-text-list-original.html @@ -0,0 +1,32 @@ +
      + {% if main-deck %} +
      +

      Main Deck

      + {% for card in main-deck %} +

      {{ card.name }}

      + {% endfor %} +
      + {% endif %} + {% if extra-deck %} +
      +

      Extra Deck

      + {% for card in extra-deck %} +

      {{ card.name }}

      + {% endfor %} +
      + {% endif %} + {% if side-deck %} +
      +

      Side Deck

      + {% for card in side-deck %} +

      {{ card.name }}

      + {% endfor %} +
      + {% endif %} +
      diff --git a/templates/decks/deck-text-list.html b/templates/decks/deck-text-list.html new file mode 100644 index 0000000..408ec27 --- /dev/null +++ b/templates/decks/deck-text-list.html @@ -0,0 +1,3 @@ +{% if html %} +{{ html | safe }} +{% endif %} diff --git a/templates/decks/deck-text-textarea-with-brs.html b/templates/decks/deck-text-textarea-with-brs.html new file mode 100644 index 0000000..092ae9e --- /dev/null +++ b/templates/decks/deck-text-textarea-with-brs.html @@ -0,0 +1,16 @@ +
      +
      + +
      +
      diff --git a/templates/decks/deck-text-textarea.html b/templates/decks/deck-text-textarea.html new file mode 100644 index 0000000..7eeaa01 --- /dev/null +++ b/templates/decks/deck-text-textarea.html @@ -0,0 +1,16 @@ +
      +
      + +
      +
      diff --git a/templates/decks/deck-text-three-column.html b/templates/decks/deck-text-three-column.html new file mode 100644 index 0000000..2b63f97 --- /dev/null +++ b/templates/decks/deck-text-three-column.html @@ -0,0 +1,26 @@ +
      + {% if main-deck-rle %} +
      +

      Main Deck ({{ main-deck | length }})

      + {% for (count . name) in main-deck-rle %} +

      {{ count }} - {{ name }}

      + {% endfor %} +
      + {% endif %} + {% if extra-deck-rle %} +
      +

      Extra Deck ({{ extra-deck | length }})

      + {% for (count . name) in extra-deck-rle %} +

      {{ count }} - {{ name }}

      + {% endfor %} +
      + {% endif %} + {% if side-deck-rle %} +
      +

      Side Deck ({{ side-deck | length }})

      + {% for (count . name) in side-deck-rle %} +

      {{ count }} - {{ name }}

      + {% endfor %} +
      + {% endif %} +
      diff --git a/templates/decks/decks-by-category.html b/templates/decks/decks-by-category.html new file mode 100644 index 0000000..12ce1c1 --- /dev/null +++ b/templates/decks/decks-by-category.html @@ -0,0 +1,3 @@ +{% for deck in table %} +{{ deck.name }} +{% endfor %} diff --git a/templates/decks/index.html b/templates/decks/index.html new file mode 100644 index 0000000..454559e --- /dev/null +++ b/templates/decks/index.html @@ -0,0 +1,107 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Deck List" _}{% endblock %} +{% block content %} + +{% include "tabs-js.html" %} + +
      + {{ token | safe }} +
      + +{% include "decks/deck-menu-bar.html" %} + +
      +
      +
      +
      +
      + {% if id %} +
      +
      + {% endif %} +
      +
      +
      + +{% if id %} +
      +
      + +
      + +
      + +
      +
      + +{% include "decks/deck-controls.html" %} + +
      + +
      + + + +
      +
      + + + + + + + + + + + + +
      +{% endif %} +{% endblock %} diff --git a/templates/decks/saved-category-list.html b/templates/decks/saved-category-list.html new file mode 100644 index 0000000..c5c0ad8 --- /dev/null +++ b/templates/decks/saved-category-list.html @@ -0,0 +1,19 @@ +
      +
      + +
      +
      +
      + +
      +
      +
      diff --git a/templates/decks/search.html b/templates/decks/search.html new file mode 100644 index 0000000..fabad7a --- /dev/null +++ b/templates/decks/search.html @@ -0,0 +1,41 @@ +{% extends "layouts/default.html" %} +{% block title %}Deck List{% endblock %} +{% block content %} + + + +{% include "decks/deck-menu-bar.html" %} + +
      +
      +
      + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +{% if decks %} + +{% endif %} + +{% endblock %} diff --git a/templates/docs.html b/templates/docs.html new file mode 100644 index 0000000..f665f21 --- /dev/null +++ b/templates/docs.html @@ -0,0 +1,44 @@ +{% extends "layouts/default.html" %} +{% block title %}Package Documentation{% endblock %} +{% block content %} +
      +
      + + + + + + + {% for doc in docs %} + + + + + + {% endfor %} +
      Endpoint/URL (method)ParametersDocumentation
      {{ doc.0 }} {{ doc.2 }} + {% if doc.3 %} +
      +
      {{ doc.3 }}
      +
      + {% endif %} +
      + {% if doc.1 %} +
      +
      {{ doc.1 }}
      +
      + {% endif %} +
      +
      +
      +{% endblock %} +{# comment #} +(cl-who:with-html-output-to-string (s nil :prologue t) +(:html +(:head) +(:body +(loop for (url docstring methods params) in docs +do (cl-who:htm (:h3 (cl-who:str url) " " (cl-who:fmt "~a" methods)) +(if params (cl-who:htm (:h4 (cl-who:fmt " Params: ~a" params)))) +(:div (cl-who:str docstring))))))) +{# endcomment #} diff --git a/templates/docs.md b/templates/docs.md new file mode 100644 index 0000000..acac3dd --- /dev/null +++ b/templates/docs.md @@ -0,0 +1,41 @@ +{# doc.0: URL; doc.1: Documentation; doc.2: Methods; doc.3: Params; #} +
      +# Project Documentation
      +
      +This page is automatically generated. Sections are organized like so:
      +
      +    * METHOD ROUTE PARAMETERS
      +      DOCSTRING
      +
      +`METHOD` is usually either `GET` or `POST`, but could be any of the
      +[HTTP Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).
      +
      +The `ROUTE` is the `URI` endpoint on the application, e.g. `/builder`.
      +
      +Each `ROUTE` has `PARAMETERS`. Currently, the parameters are parsed by
      +hand. As the application matures, the parameters will come more into
      +play.
      +
      +The `DOCSTRING` is written by me, the developer, and is intended to
      +provide context on the purpose of the route.
      +
      +The API follows a basic pattern:
      +
      +    GET /<base-page>
      +
      +This will display the main page for the item `<base-page>`.
      +
      +    DELETE /<base-page>
      +    PATCH /<base-page>
      +    POST /<base-page>
      +
      +If `<base-page>` may have an operation done on it (create,
      +retrieve, update, delete some values), there will be a matching method
      +for `DELETE`, `PATCH`, `POST`, etc.
      +
      +# API Index
      +{% for doc in docs %}## {{ doc.2 }} {{ doc.0 }} {{ doc.3 }}
      +{{ doc.1 }}
      +
      +{% endfor %}
      +
      diff --git a/templates/dropdown-js.html b/templates/dropdown-js.html new file mode 100644 index 0000000..25c4146 --- /dev/null +++ b/templates/dropdown-js.html @@ -0,0 +1,9 @@ + diff --git a/templates/file-js.html b/templates/file-js.html new file mode 100644 index 0000000..654e3a5 --- /dev/null +++ b/templates/file-js.html @@ -0,0 +1,11 @@ + diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..dc2d60c --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,14 @@ +
      +
      +
      +

      © 2023

      +
      +
      +
      + {% if git-revision %} +
      +

      {{ git-revision }}

      +
      + {% endif %} +
      +
      diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..ceded0d --- /dev/null +++ b/templates/index.html @@ -0,0 +1,12 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Index" _}{% endblock %} +{% block content %} +
      +
      +

      {_ "Welcome to cl-deck-builder2!" _}

      +

      + {_ "Click the links at the top to get started." _} +

      +
      +
      +{% endblock %} diff --git a/templates/inventory/_advanced_search_input.html b/templates/inventory/_advanced_search_input.html new file mode 100644 index 0000000..e90a87f --- /dev/null +++ b/templates/inventory/_advanced_search_input.html @@ -0,0 +1,10 @@ + diff --git a/templates/inventory/_advanced_search_select.html b/templates/inventory/_advanced_search_select.html new file mode 100644 index 0000000..9c721a3 --- /dev/null +++ b/templates/inventory/_advanced_search_select.html @@ -0,0 +1,15 @@ +
      + +
      diff --git a/templates/inventory/_edit_result.html b/templates/inventory/_edit_result.html new file mode 100644 index 0000000..5920e7a --- /dev/null +++ b/templates/inventory/_edit_result.html @@ -0,0 +1,12 @@ + +
      +
      + +
      +
      + diff --git a/templates/inventory/_edit_row.html b/templates/inventory/_edit_row.html new file mode 100644 index 0000000..358afac --- /dev/null +++ b/templates/inventory/_edit_row.html @@ -0,0 +1,10 @@ + + {{ title }} + +
      +
      + +
      +
      + + diff --git a/templates/inventory/_inventory_variants.html b/templates/inventory/_inventory_variants.html new file mode 100644 index 0000000..b6cd1ba --- /dev/null +++ b/templates/inventory/_inventory_variants.html @@ -0,0 +1,10 @@ +{% comment %} +
      + {% for _variant in variants %} +
      + + +
      + {% endfor %} +
      +{% endcomment %} diff --git a/templates/inventory/_new_row.html b/templates/inventory/_new_row.html new file mode 100644 index 0000000..7e3ec88 --- /dev/null +++ b/templates/inventory/_new_row.html @@ -0,0 +1,10 @@ + + {{ title }} + +
      +
      + +
      +
      + + diff --git a/templates/inventory/_search_result_th.html b/templates/inventory/_search_result_th.html new file mode 100644 index 0000000..ba51199 --- /dev/null +++ b/templates/inventory/_search_result_th.html @@ -0,0 +1,13 @@ + +
      +
      + +
      +
      + diff --git a/templates/inventory/_view_result.html b/templates/inventory/_view_result.html new file mode 100644 index 0000000..f525781 --- /dev/null +++ b/templates/inventory/_view_result.html @@ -0,0 +1,3 @@ + + {{ value }} + diff --git a/templates/inventory/advanced-search.html b/templates/inventory/advanced-search.html new file mode 100644 index 0000000..b8abed3 --- /dev/null +++ b/templates/inventory/advanced-search.html @@ -0,0 +1,164 @@ + diff --git a/templates/inventory/base-menu.html b/templates/inventory/base-menu.html new file mode 100644 index 0000000..97956d2 --- /dev/null +++ b/templates/inventory/base-menu.html @@ -0,0 +1,56 @@ +
      +
      + +
      + +
      + +
      + +
      +
      + +
      +
      + + +
      + +
      + + +
      + +
      +
      diff --git a/templates/inventory/edit.html b/templates/inventory/edit.html new file mode 100644 index 0000000..ad670d5 --- /dev/null +++ b/templates/inventory/edit.html @@ -0,0 +1,59 @@ +{% extends "layouts/default.html" %} +{% block title %}Card Editor{% if card %} - {{ card.id }}{% endif %}{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +
      + {% if card %} +
      + + +
      + +
      + + + + + + + + + + + + {% include "inventory/_edit_row.html" :form "edit-form" :name "name" :value card.name :title "Product Name" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "condition" :value card.condition :title "Condition" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "category" :value card.category :title "Category" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "name" :value card.name :title "Name" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "code" :value card.code :title "Code" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "edition" :value card.edition :title "Edition" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "passcode" :value card.passcode :title "Passcode" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "buy-price" :value card.buy-price :title "Buy Price" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "sell-price" :value card.sell-price :title "Sell Price" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "total-qty" :value card.total-qty :title "Total Qty" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "uri" :value card.uri :title "URI" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "description" :value card.description :title "Description" %} + +
      FieldValue
      Card Image + + Card Image for Yu-Gi-Oh! #{{card.passcode}} - {{card.name}} + +
      +
      + {% else %} +

      Nothing to see here.

      + {% endif %} +
      +{% endblock %} diff --git a/templates/inventory/import.html b/templates/inventory/import.html new file mode 100644 index 0000000..e76c389 --- /dev/null +++ b/templates/inventory/import.html @@ -0,0 +1,34 @@ +{% extends "layouts/default.html" %} +{% block title %}Inventory CSV Import{% endblock %} +{% block content %} +
      + CSV Import +
      + {% for image in files %} +
      + + Icon for {{ image.url }} + +
      + {% if forloop.counter0 == 5 %} +
      +
      + {% endif %} + {% endfor %} +
      +
      +
      + {{ token | safe }} +
      + +
      + +
      +
      +{% endblock %} + + +{% endblock %} diff --git a/templates/inventory/index.html b/templates/inventory/index.html new file mode 100644 index 0000000..4390913 --- /dev/null +++ b/templates/inventory/index.html @@ -0,0 +1,28 @@ +{% extends "layouts/default.html" %} +{% block title %}Inventory List{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +
      + {% include "inventory/base-menu.html" %} + +
      + {% include "inventory/search-results.html" %} +
      + +
      + + +
      +{% endblock %} diff --git a/templates/inventory/new.html b/templates/inventory/new.html new file mode 100644 index 0000000..5db4729 --- /dev/null +++ b/templates/inventory/new.html @@ -0,0 +1,48 @@ +{% extends "layouts/default.html" %} +{% block title %}New Inventory Item{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +
      +
      + + + Return + +
      + + + + + + + + + + + + + {% include "inventory/_edit_row.html" :form "edit-form" :name "name" :title "Product Name" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "category" :title "Category" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "name" :title "Name" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "code" :title "Code" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "edition" :title "Edition" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "passcode" :title "Passcode" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "buy-price" :title "Buy Price" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "sell-price" :title "Sell Price" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "total-qty" :title "Total Qty" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "uri" :title "URI" %} + {% include "inventory/_edit_row.html" :form "edit-form" :name "description" :title "Description" %} + +
      FieldValue
      Card Image + card image +
      + +
      +{% endblock %} diff --git a/templates/inventory/old.html b/templates/inventory/old.html new file mode 100644 index 0000000..c3b39e5 --- /dev/null +++ b/templates/inventory/old.html @@ -0,0 +1,58 @@ + + + + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Product Name" :sort "product-name" + :class "w3-col m5" %} + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Condition" :sort "condition" + :class "w3-col m1" %} + + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Qty" :sort "qty" + :class "w3-col m1" %} + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Opt. Qty" :sort "opt-qty" + :class "w3-col m1" %} + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Buy Price" :sort "buy-price" + :class "w3-col m1" %} + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Sell Price" :sort "sell-price" + :class "w3-col m1" %} + {% include "inventory/_search_result_th.html" :url "/inventory/search" :target "search-results" :name "Category" :sort "category" + :class "w3-col m1" %} + + + {% for condition in variants %} + + + + {% include "inventory/_view_result.html" :id card.id :name "product-name" :type "text" :value card.product-name + :class "w3-col m5" %} + + {% include "inventory/_view_result.html" :id card.id :name "condition" :type "text" :value condition.name + :class "w3-col m1" %} + + + {% include "inventory/_edit_result.html" :id card.id :name "total-qty" :type "number" :value card.total-qty + :class "w3-col m1" %} + {% include "inventory/_edit_result.html" :id card.id :name "opt-qty" :type "number" :value card.opt-qty + :class "w3-col m1" %} + {% include "inventory/_edit_result.html" :id card.id :name "buy-price" :type "number" :value card.buy-price + :class "w3-col m1" %} + {% include "inventory/_edit_result.html" :id card.id :name "sell-price" :type "number" :value card.sell-price + :class "w3-col m1" %} + {% include "inventory/_view_result.html" :id card.id :name "category" :type "text" :value card.category + :class "w3-col m1" %} + + + {% endfor %} +
      Save
      +
      + + {{ token | safe }} + +
      +
      diff --git a/templates/inventory/search-results.html b/templates/inventory/search-results.html new file mode 100644 index 0000000..7b3e82d --- /dev/null +++ b/templates/inventory/search-results.html @@ -0,0 +1,86 @@ +{% include "pages.html" :form "search-form" :hx-include "[name='search-form']" :hx-target "#search-results" :url "/inventory/search" %} + +{% if cards %} +
      + + + + {% include "inventory/_search_result_th.html" :style "width:100px" :url "/inventory/search" :hx-target "#search-results" :name "Image" :sort "id" %} + {% include "inventory/_search_result_th.html" :url "/inventory/search" :hx-target "#search-results" :name "Product Name" :sort "name" %} + + + {% include "inventory/_search_result_th.html" :style "width:10%" url "/inventory/search" :hx-target "#search-results" :name "Total Qty" :sort "total-qty" %} + {% include "inventory/_search_result_th.html" :style "width:10%" url "/inventory/search" :hx-target "#search-results" :name "Opt. Qty" :sort "opt-qty" %} + {% include "inventory/_search_result_th.html" :style "width:10%" url "/inventory/search" :hx-target "#search-results" :name "Buy Price" :sort "buy-price" %} + {% include "inventory/_search_result_th.html" :style "width:10%" url "/inventory/search" :hx-target "#search-results" :name "Sell Price" :sort "sell-price" %} + {% include "inventory/_search_result_th.html" :style "width:10px" url "/inventory/search" :hx-target "#search-results" :name "URI" :sort "uri" %} + + +
       
      +
      +{% for card in cards %} +
      + + + + + + + {% include "inventory/_view_result.html" :id card.id :name "name" :type "text" :value card.name %} + + + + + {% include "inventory/_edit_result.html" :style "width:10%" :id card.id :name "total-qty" :type "number" :value card.total-qty %} + {% include "inventory/_edit_result.html" :style "width:10%" :id card.id :name "opt-qty" :type "number" :value card.opt-qty %} + {% include "inventory/_edit_result.html" :style "width:10%" :id card.id :name "buy-price" :type "number" :value card.buy-price %} + {% include "inventory/_edit_result.html" :style "width:10%" :id card.id :name "sell-price" :type "number" :value card.sell-price %} + + + +
      + +
      + Card Image for Yu-Gi-Oh! #{{ card.passcode }} - {{ card.name }} +
      +
      +
      + + +
      + + + +
      + {{ token | safe }} + +
      +
      +
      + +{% endfor %} + +{% include "pages.html" :hx-target "#search-results" :url "/inventory/search" :hx-include "[name='search-form']" %} + +
      +

      {{ length }} results. {{ total }} entries in db.

      +
      + +{% else %} +
      +

      Nothing to see here!

      +
      +{% endif %} diff --git a/templates/inventory/variant-results.html b/templates/inventory/variant-results.html new file mode 100644 index 0000000..4043fa0 --- /dev/null +++ b/templates/inventory/variant-results.html @@ -0,0 +1,89 @@ +{% for card in cards %} +
      + {{ token | safe }} +
      +{% endfor %} +
      + + + + + + + + + + + + {% for card in cards %} + + + + + + + + + + + + + + + + {% endfor %} +
      ConditionQtyOpt. QtyBuy PriceSell PriceSave
      {{ card.variant.name }} +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      + +
      +
      diff --git a/templates/kde-team.html b/templates/kde-team.html new file mode 100644 index 0000000..f1dbdd6 --- /dev/null +++ b/templates/kde-team.html @@ -0,0 +1,302 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "KDE Team Deck Listing" _}{% endblock %} +{% block content %} + + + + + + + + + +
      + +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      + Please write all card names completely and legibly. +
      + Please include the quantity for each card. +
      First & Middle Name(s):
      Last Name(s):
      CARD GAME ID:
      +
      + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Judge Use Only:MS
      Event Date:MM/DD/YYYY
      Country of Residency:
      Event Name:
      +
      + + +
      + + + + + + + + + + + + + + + +
      E
      + Last Name Initial +
      +  
      +
      +

      Main Deck Total

      +

      {{ main-deck | length }}

      +
      +
      +
      + +
      +
      + + + + + +
      Team Name:
      +
      +
      + + +
      +
      + + + + + {% for (count . card) in monster-cards-rle %} + + + + + {% endfor %} + + + + +
      MONSTER CARDS
      {{ count }}{{ card.name | default:" "| safe }}
      {{ monster-cards | length }}« TOTAL MONSTER CARDS
      +
      +
      + + + + + {% for (count . card) in spell-cards-rle %} + + + + + {% endfor %} + + + + +
      SPELL CARDS
      {{ count }}{{ card.name | default:" "| safe }}
      {{ spell-cards | length }}« TOTAL SPELL CARDS
      +
      +
      + + + + + {% for (count . card) in trap-cards-rle %} + + + + + {% endfor %} + + + + +
      TRAP CARDS
      {{ count }}{{ card.name | default:" "| safe }}
      {{ trap-cards | length }}« TOTAL TRAP CARDS
      +
      +
      + +
      +
      + + + + + {% for (count . card) in side-deck-rle %} + + + + + {% endfor %} + + + + +
      SIDE DECK
      {{ count }}{{ card.name | default:" "| safe }}
      {{ side-deck | length }}« TOTAL SIDE DECK
      +
      +
      + + + + + {% for (count . card) in extra-deck-rle %} + + + + + {% endfor %} + + + + +
      EXTRA DECK
      {{ count }}{{ card.name | default:" "| safe }}
      {{ extra-deck | length }}« TOTAL EXTRA DECK
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + +
      For Judge Use Only
      Deck List Checked?
      Judge InitialsInfraction:
      Description:
      Deck Check Round:
      Judge InitialsInfraction:
      Description:
      Deck Check Round:
      Judge InitialsInfraction:
      Description:
      +
      +
      +
      +
      + +{% endblock %} diff --git a/templates/label-maker.html b/templates/label-maker.html new file mode 100644 index 0000000..d036bf4 --- /dev/null +++ b/templates/label-maker.html @@ -0,0 +1,71 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "Label Maker" _}{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +
      +

      {_ "Label Maker" _}

      + +
      +
      + +
      + +
      +
      +
      +
      + +
      + {% include "file-js.html" :target "#file-js" %} +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      + +
      +
      +
      +
      +
      + +{% endblock %} diff --git a/templates/latex/Avery-5160.tex b/templates/latex/Avery-5160.tex new file mode 100644 index 0000000..1b3a3e9 --- /dev/null +++ b/templates/latex/Avery-5160.tex @@ -0,0 +1,15 @@ +\LabelCols=3 +\LabelRows=10 +% +\LeftPageMargin=0.21975in +\RightPageMargin=0.21975in +\TopPageMargin=0.5in +\BottomPageMargin=0.5in +% +\InterLabelColumn=0.14in % adjust as required +\InterLabelRow=0mm +% +\RightLabelBorder=1mm% adjust to taste +\LeftLabelBorder=1mm +\TopLabelBorder=1mm +\BottomLabelBorder=1mm diff --git a/templates/latex/Avery-5160.tpl.tex b/templates/latex/Avery-5160.tpl.tex new file mode 100644 index 0000000..c96e96d --- /dev/null +++ b/templates/latex/Avery-5160.tpl.tex @@ -0,0 +1,26 @@ +{% extends "latex/default.tpl.tex" %} +{% block content %} +\input{Avery-5160} +% +\numberoflabels=1 +\begin{document} +% +{% for label in labels %} +\genericlabel{\begin{minipage}{2.625in}% adjust as desired + \begin{tabular}{p{0.95\linewidth}}% + {\scriptsize\frenchspacing\righthyphenmin2\sloppy% + {{ label.description | safe }}}% + \end{tabular} + \begin{tabular}{p{0.60\linewidth} p{0.40\linewidth}}% + {\Huge {{ label.price }}} & % + \vspace*{-3ex}% + \begin{tabular}{c}% + {\includegraphics[width=0.75in]{ {{ label.barcode }} }} \vspace*{-1ex} \\% + {\scriptsize\frenchspacing\righthyphenmin2\sloppy% + {{ label.barcode }}}% + \end{tabular}% + \end{tabular}% + \strut\end{minipage}}{% endfor %} +% +\end{document} +{% endblock %} diff --git a/templates/latex/ULINE-S-20247.tex b/templates/latex/ULINE-S-20247.tex new file mode 100644 index 0000000..96ac7ca --- /dev/null +++ b/templates/latex/ULINE-S-20247.tex @@ -0,0 +1,15 @@ +\LabelCols=4 +\LabelRows=8 +% +\LeftPageMargin=0.25in +\RightPageMargin=0.25in +\TopPageMargin=0.5in +\BottomPageMargin=0.5in +% +\InterLabelColumn=0mm% adjust as required +\InterLabelRow=0mm +% +\RightLabelBorder=1mm% adjust to taste +\LeftLabelBorder=1mm +\TopLabelBorder=6mm +\BottomLabelBorder=1mm diff --git a/templates/latex/ULINE-S-20247.tpl.tex b/templates/latex/ULINE-S-20247.tpl.tex new file mode 100644 index 0000000..b8ce7fd --- /dev/null +++ b/templates/latex/ULINE-S-20247.tpl.tex @@ -0,0 +1,26 @@ +{% extends "latex/default.tpl.tex" %} +{% block content %} +\input{ULINE-S-20247} +% +\numberoflabels=1 +\begin{document} +% +{% for label in labels %} +\genericlabel{\begin{minipage}{2in}% adjust as desired + \begin{tabular}{p{0.95\linewidth}}% + {\scriptsize\frenchspacing\righthyphenmin2\sloppy% + {{ label.description | safe }}}% + \end{tabular} + \begin{tabular}{p{0.60\linewidth} p{0.40\linewidth}}% + {\Huge {{ label.price }}} & % + \vspace*{-3ex}% + \begin{tabular}{c}% + {\includegraphics[width=0.75in]{ {{ label.barcode }} }} \vspace*{-1ex} \\% + {\scriptsize\frenchspacing\righthyphenmin2\sloppy% + {{ label.barcode }}}% + \end{tabular}% + \end{tabular}% + \strut\end{minipage}}{% endfor %} +% +\end{document} +{% endblock %} diff --git a/templates/latex/default.tpl.tex b/templates/latex/default.tpl.tex new file mode 100644 index 0000000..77749ea --- /dev/null +++ b/templates/latex/default.tpl.tex @@ -0,0 +1,24 @@ +% NOTE: things like two { { Together will cause Djula to explode. +\documentclass{article} +\usepackage{microtype} +\usepackage[american]{babel} +\usepackage[paper=letterpaper,layoutwidth=11in,layoutheight=8.5in,portrait]{geometry} +\usepackage{graphicx} +\usepackage[newdimens]{labels}% let the package do the work... +\usepackage{trimclip} +\usepackage[scaled]{helvet} +\usepackage[T1]{fontenc} +\renewcommand\familydefault{\sfdefault} +\graphicspath{ {./} {/tmp/labels/} } +\setlength{\baselineskip}{0pt} +\setlength{\emergencystretch}{3em} +\setlength{\lineskip}{0pt} +\setlength{\parindent}{0pt} +\setlength{\parskip}{0pt} +\setlength{\tabcolsep}{0pt} +\makeatletter +\def\input@path{ {/home/quicklisp/quicklisp/local-projects/cl-deck-builder2/templates/latex/}% + {/home/user/code/cl-deck-builder2/templates/latex/}} +%or: \def\input@path{ {/path/to/folder/}{/path/to/other/folder/}} +\makeatother +{% block content %}{% endblock %} diff --git a/templates/layouts/default.html b/templates/layouts/default.html new file mode 100644 index 0000000..a724091 --- /dev/null +++ b/templates/layouts/default.html @@ -0,0 +1,19 @@ + + + + {% include "layouts/html-head.html" %} + {% block title %}{% endblock %} + + + {% include "navbar.html" %} +
      + + + {% include "messages.html" %} + + + {% block content %}{% endblock %} +
      + {% include "footer.html" %} + + diff --git a/templates/layouts/html-head.html b/templates/layouts/html-head.html new file mode 100644 index 0000000..b39726e --- /dev/null +++ b/templates/layouts/html-head.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/layouts/minimal.html b/templates/layouts/minimal.html new file mode 100644 index 0000000..fe6e551 --- /dev/null +++ b/templates/layouts/minimal.html @@ -0,0 +1,10 @@ + + + + {% include "layouts/html-head.html" %} + {% block title %}{% endblock %} + + + {% block content %}{% endblock %} + + diff --git a/templates/markdown.html b/templates/markdown.html new file mode 100644 index 0000000..56f3596 --- /dev/null +++ b/templates/markdown.html @@ -0,0 +1,13 @@ +{% extends "layouts/default.html" %} +{% block title %}{{ title }}{% endblock %} +{% block content %} +
      + {% if title %} +

      {{ title }}

      + {% endif %} + {% if subtitle %} +

      {{ subtitle }}

      + {% endif %} + {{ html | safe }} +
      +{% endblock %} diff --git a/templates/messages.html b/templates/messages.html new file mode 100644 index 0000000..9582e08 --- /dev/null +++ b/templates/messages.html @@ -0,0 +1,13 @@ +
      + {% for message in messages %} +
      +
      +

      {{ message.title }}

      +
      +
      + {{ message.body | safe }} +
      +
      + {% endfor %} +
      diff --git a/templates/navbar-js.html b/templates/navbar-js.html new file mode 100644 index 0000000..aaefe59 --- /dev/null +++ b/templates/navbar-js.html @@ -0,0 +1,10 @@ + diff --git a/templates/navbar.html b/templates/navbar.html new file mode 100644 index 0000000..09661c3 --- /dev/null +++ b/templates/navbar.html @@ -0,0 +1,85 @@ +{% include "navbar-js.html" %} + diff --git a/templates/news.html b/templates/news.html new file mode 100644 index 0000000..6474392 --- /dev/null +++ b/templates/news.html @@ -0,0 +1,32 @@ +{% extends "layouts/default.html" %} +{% block title %}cl-deck-builder2 News{% endblock %} +{% block content %} +
      +

      + Recent Changes +

      + {% for message in git-log %} +
      +
      + {{ message.subject }} + + + {{ message.commit }} + + + + + +
      + +
      + {% if message.message %} +
      {{ message.message }}
      + {% endif %} +
      + {{ message.date }} +
      +
      + {% endfor %} +
      +{% endblock %} diff --git a/templates/offline.html b/templates/offline.html new file mode 100644 index 0000000..c78800f --- /dev/null +++ b/templates/offline.html @@ -0,0 +1 @@ +

      Offline

      diff --git a/templates/pages.html b/templates/pages.html new file mode 100644 index 0000000..ad8a062 --- /dev/null +++ b/templates/pages.html @@ -0,0 +1,27 @@ +{% if pages %} + +{% endif %} diff --git a/templates/qr.html b/templates/qr.html new file mode 100644 index 0000000..9d46ba8 --- /dev/null +++ b/templates/qr.html @@ -0,0 +1,154 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "QR Code Generator" _}{% endblock %} +{% block content %} +
      + {{ token | safe }} +
      + +
      +

      QR Generator

      + +
      +
      + +
      + +
      + +
      +
      + + +
      + +
      +
      + +
      +
      + +
      +
      +
      +
      + + +
      +
      + +
      +
      + +
      +
      +
      +
      + + +
      +
      + +
      + +
      +
      +
      + + +
      +
      + +
      + +
      +
      +
      +
      + + +
      +
      + +
      +
      + +
      + +
      +
      + Helpful Links: +
      + +
      +
      + + +
      +
      +
      +
      + QR Code +
      +
      +
      +
      +
      +
      +{% endblock %} diff --git a/templates/tabs-js.html b/templates/tabs-js.html new file mode 100644 index 0000000..dfd50dd --- /dev/null +++ b/templates/tabs-js.html @@ -0,0 +1,15 @@ + diff --git a/templates/tinymce-js.html b/templates/tinymce-js.html new file mode 100644 index 0000000..90b55ec --- /dev/null +++ b/templates/tinymce-js.html @@ -0,0 +1,40 @@ + + diff --git a/templates/tinymce/index.html b/templates/tinymce/index.html new file mode 100644 index 0000000..46cf82a --- /dev/null +++ b/templates/tinymce/index.html @@ -0,0 +1,69 @@ +{% extends "layouts/default.html" %} +{% block title %}TinyMCE{% endblock %} +{% block content %} +
      +
      + + {{ token | safe }} +
      +
      +

      TinyMCE

      +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + + +{% endblock %} diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 0000000..8866337 --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,88 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "File Uploader" _}{% endblock %} +{% block content %} +
      + {{ token | safe }} + +
      + +
      + {% if files %} +

      Files

      +
      + {% for image in files %} +
      +
      +
      +
      +
      +

      + + + +

      +
      +
      +
      +

      File Key: {{ image.attachment.file-key }}

      +

      File Size: {{ image.attachment.file-size }}

      +
      +
      +
      + +
      +
      +
      +
      +
      + {% endfor %} +
      + +
      + {% endif %} + +
      +
      +
      +
      +
      + +
      + {% include "file-js.html" :target "#file-js" %} +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +{% endblock %} diff --git a/templates/user/admin.html b/templates/user/admin.html new file mode 100644 index 0000000..3d9d88f --- /dev/null +++ b/templates/user/admin.html @@ -0,0 +1,48 @@ +{% extends "layouts/default.html" %} +{% block title %}{_ "User Admin Panel" _}{% endblock %} +{% block content %} + +{% include "user/user-menu-bar.html" %} + +
      +
      + + + + + + + + + + + + {% for user in user-list %} + + + + + + + + {% endfor %} + +
      IDNameEmailRolesDelete
      {{ user.id }}{{ user.name }}{{ user.email }}
      +
      +
      + {{ token | safe }} + + + +
      +
      +
      +
      +{% endblock %} diff --git a/templates/user/index.html b/templates/user/index.html new file mode 100644 index 0000000..64aa0a7 --- /dev/null +++ b/templates/user/index.html @@ -0,0 +1,23 @@ +{% extends "layouts/default.html" %} +{% block title %}Dashboard{% endblock %} +{% block content %} + +{% include "user/user-menu-bar.html" %} + +
      + {% if session.username %} +
      +
      + You are logged in. +
      +
      + {% endif %} + +
      + Session Info +
      {% for (key . value) in session %}{{ key }} . {{ value }}
      +{% endfor %}
      +
      + +
      +{% endblock %} diff --git a/templates/user/login.html b/templates/user/login.html new file mode 100644 index 0000000..e982f09 --- /dev/null +++ b/templates/user/login.html @@ -0,0 +1,78 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Login" _}{% endblock %} +{% block content %} + + + +
      + {{ token | safe }} +
      + +
      +
      +
      +
      +
      + {% include "messages.html" %} +
      +
      +
      +
      + +
      + + + + +
      +
      +
      + +
      + + + + +
      +
      +
      +
      + +
      +
      + +
      + +
      + +
      + +
      + Register +
      +
      +
      +
      +
      +
      +
      +{% endblock %} diff --git a/templates/user/register.html b/templates/user/register.html new file mode 100644 index 0000000..c0e0fe2 --- /dev/null +++ b/templates/user/register.html @@ -0,0 +1,85 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Register" _}{% endblock %} +{% block content %} + + + +
      + {{ token | safe }} +
      + +
      +
      +
      +
      +
      + {% include "messages.html" %} +
      +
      +
      +
      + +
      + + + + +
      +
      +
      + +
      + + + + +
      +
      +
      + +
      + + + + +
      +
      +
      + +
      + +
      + +
      + +
      + +
      + Login +
      +
      +
      +
      +
      +
      +
      +{% endblock %} diff --git a/templates/user/user-menu-bar.html b/templates/user/user-menu-bar.html new file mode 100644 index 0000000..8a19b20 --- /dev/null +++ b/templates/user/user-menu-bar.html @@ -0,0 +1,14 @@ +
      + +
      diff --git a/templates/workbench/docs.html b/templates/workbench/docs.html new file mode 100644 index 0000000..f665f21 --- /dev/null +++ b/templates/workbench/docs.html @@ -0,0 +1,44 @@ +{% extends "layouts/default.html" %} +{% block title %}Package Documentation{% endblock %} +{% block content %} +
      +
      + + + + + + + {% for doc in docs %} + + + + + + {% endfor %} +
      Endpoint/URL (method)ParametersDocumentation
      {{ doc.0 }} {{ doc.2 }} + {% if doc.3 %} +
      +
      {{ doc.3 }}
      +
      + {% endif %} +
      + {% if doc.1 %} +
      +
      {{ doc.1 }}
      +
      + {% endif %} +
      +
      +
      +{% endblock %} +{# comment #} +(cl-who:with-html-output-to-string (s nil :prologue t) +(:html +(:head) +(:body +(loop for (url docstring methods params) in docs +do (cl-who:htm (:h3 (cl-who:str url) " " (cl-who:fmt "~a" methods)) +(if params (cl-who:htm (:h4 (cl-who:fmt " Params: ~a" params)))) +(:div (cl-who:str docstring))))))) +{# endcomment #} diff --git a/templates/workbench/dropdown.html b/templates/workbench/dropdown.html new file mode 100644 index 0000000..b428b71 --- /dev/null +++ b/templates/workbench/dropdown.html @@ -0,0 +1,36 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Dropdown" _}{% endblock %} +{% block content %} + +{% include "dropdown-js.html" :class ".dropdown" %} + +
      +
      +
      +
      + +
      +
      +
      +
      + +{% endblock %} diff --git a/templates/workbench/pure.html b/templates/workbench/pure.html new file mode 100644 index 0000000..2fc3a71 --- /dev/null +++ b/templates/workbench/pure.html @@ -0,0 +1,44 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Workbench" _}{% endblock %} +{% block content %} + + +
      +
      +

      + Pure CSS Gradient Background Animation +

      +

      + + Pen by Manuel Pinto + +

      +

      + + Source Code + +

      +
      +
      +{% endblock %} diff --git a/templates/workbench/table.html b/templates/workbench/table.html new file mode 100644 index 0000000..9c1a9a4 --- /dev/null +++ b/templates/workbench/table.html @@ -0,0 +1,6 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Workbench" _}{% endblock %} +{% block content %} + + +{% endblock %} diff --git a/templates/workbench/tabs.html b/templates/workbench/tabs.html new file mode 100644 index 0000000..0455373 --- /dev/null +++ b/templates/workbench/tabs.html @@ -0,0 +1,46 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Tabs" _}{% endblock %} +{% block content %} + +{% include "tabs-js.html" %} + +
      +
      +
      +

      + JS for Bulma Tabs +

      +
      +
      + +
      + +
      +
      + +
      +
      +

      + test1
      + Overview Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

      +
      + + +
      +{% endblock %} diff --git a/templates/workbench/template.Dockerfile b/templates/workbench/template.Dockerfile new file mode 100644 index 0000000..98317c6 --- /dev/null +++ b/templates/workbench/template.Dockerfile @@ -0,0 +1,19 @@ +{% if args %} +ARG{% for arg in args %} arg{% endfor %} +{% endif %} +FROM {{ platform }} {{ image }} +RUN {{ apt }} {{ update }} +{% for pkg in packages %}RUN {{ apt }} {{ install }} {{ pkg }} +{% endfor %} +{% if workdir %}WORKDIR {{ workdir }} +{% endif %} +{% for command in pre-commands %}RUN {{ command | safe }} +{% endfor %} +COPY {{ chown }} {{ src }} {{ dst }} +{% for command in post-commands %}RUN {{ command | safe }} +{% endfor %} +{% if expose %}EXPOSE{% for arg in expose %} arg{% endfor %} +{% endif %} +{% if user %}USER user +{% endif %} +CMD {{ cmd | safe }} diff --git a/templates/workbench/test-1.html b/templates/workbench/test-1.html new file mode 100644 index 0000000..cc88735 --- /dev/null +++ b/templates/workbench/test-1.html @@ -0,0 +1,16 @@ +{% if parsed %} +
      +{{ parsed }} +
      +{% else %} +
      +
      + + + + + + +
      +
      +{% endif %} diff --git a/templates/workbench/test.html b/templates/workbench/test.html new file mode 100644 index 0000000..a3a0ec2 --- /dev/null +++ b/templates/workbench/test.html @@ -0,0 +1,5 @@ +{% extends "layouts/minimal.html" %} +{% block title %}{_ "Workbench" _}{% endblock %} +{% block content %} +{% include "workbench/test-1.html" %} +{% endblock %} diff --git a/templates/ydk/index.html b/templates/ydk/index.html new file mode 100644 index 0000000..84a481a --- /dev/null +++ b/templates/ydk/index.html @@ -0,0 +1,85 @@ +{% extends "layouts/default.html" %} +{% block title %}YDK Upload{% endblock %} +{% block content %} +
      + {{ token | safe }} + +
      + +
      +

      YDK Uploader

      +

      + Upload deck text or file +

      + +
      +
      +
      + +
      + +
      +
      + +
      + +
      + +
      +
      +
      + +
      OR
      + +
      +
      +
      +
      +
      + +
      + {% include "file-js.html" :target "#file-js" %} +
      +
      +
      +
      +
      + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +{% endblock %} diff --git a/tests/cl-deck-builder2.lisp b/tests/cl-deck-builder2.lisp new file mode 100644 index 0000000..6f665ec --- /dev/null +++ b/tests/cl-deck-builder2.lisp @@ -0,0 +1,25 @@ +#| + +tests/cl-deck-builder2.lisp + +Main Test Entry Point + +|# + +(in-package :cl-deck-builder2-test) + +(deftest cl-deck-builder2-test-suite + (testing "Test Suite Is Online" + (ok (= 1 1)))) + +(deftest cl-deck-builder2.toolkit.money + (let ((+currency+ '(0.01 0.91 1.91 16.60 99.999)) + (+currency-expect-deflate+ '(1 91 191 1660 10000)) + (+currency-expect-inflate+ '("0.01" "0.91" "1.91" "16.60" "100.00"))) + (testing "currency-deflate" + (ok + (equal +currency-expect-deflate+ + (mapcar #'currency-deflate +currency+)))) + (testing "currency-inflate" + (ok (equal +currency-expect-inflate+ + (mapcar (alexandria:compose #'currency-inflate #'currency-deflate) +currency+)))))) diff --git a/tests/crystal-commerce.lisp b/tests/crystal-commerce.lisp new file mode 100644 index 0000000..10cb7ba --- /dev/null +++ b/tests/crystal-commerce.lisp @@ -0,0 +1,32 @@ +(in-package #:cl-deck-builder2-test) + +;; (with-connection +;; (db) +;; (retrieve-all +;; (select (:product-name :buy-price :sell-price) +;; (from :crystal-commerce) +;; (order-by (:desc (:cast (:as :buy-price :integer)))) +;; (limit 10)))) + +(defun fix-null-passcodes () + (with-connection + (db) + (let ((null-passcode-lst + (retrieve-all + (select :* + (from :crystal-commerce) + (where (:is-null :passcode)))))) + (dolist (itm null-passcode-lst) + (let* ((code (getf itm :code)) + (result (retrieve-one + (select :* + (from :ygoprodeck-card-sets) + (where (:= :code code)))))) + (format t "~A: ~A~%" (getf itm :product-name) result)))))) + + (if result + (progn + (setf (getf itm :passcode) (getf result :passcode)) + (cc-item-update + (apply #'make-cc-item itm)))))) + ))) diff --git a/tests/import.lisp b/tests/import.lisp new file mode 100644 index 0000000..f29839e --- /dev/null +++ b/tests/import.lisp @@ -0,0 +1,11 @@ +(in-package :cl-deck-builder2-test) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CSV/JSON Importing Tests + +;; (cl-deck-builder2.models::drop-table-ygoprodeck-data) +;; (cl-deck-builder2.models::create-table-ygoprodeck-data) +;; (cl-deck-builder2.models::json-import-ygoprodeck +;; #P"~/quicklisp/local-projects/cl-deck-builder2/data/ygoprodeck/cardinfo.json") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/tests/models/labels.lisp b/tests/models/labels.lisp new file mode 100644 index 0000000..5cd2396 --- /dev/null +++ b/tests/models/labels.lisp @@ -0,0 +1,4 @@ +(in-package #:cl-deck-builder2-test) + +(is (latex-escape "testing -- #1 -- & \\2 -- ^3 -- _4 \\5 ~6") + "testing -- \\#1 -- \\& $\\backslash$2 -- \\^{}3 -- \\_4 \\5 \\texttt{\\~{}}6") diff --git a/tests/models/ygo-import.lisp b/tests/models/ygo-import.lisp new file mode 100644 index 0000000..773a933 --- /dev/null +++ b/tests/models/ygo-import.lisp @@ -0,0 +1,20 @@ +(in-package #:cl-deck-builder2-test) + +(defun ensure-unique (class field) + "The idea is to select COUNT(*) and then GROUP BY (field) and see if +the number of entries returned is the same." + (let ((count-1 + (with-datafly-transaction (db) + (datafly:retrieve-one + (sxql:select ((:as (:count :id) :count)) + (sxql:from + (sxql:make-sql-symbol (mito.class:table-name (find-class class)))))))) + (count-2 + (with-datafly-transaction (db) + (datafly:retrieve-one + (sxql:select ((:as (:count :id) :count)) + (sxql:from + (sxql:make-sql-symbol (mito.class:table-name (find-class class)))) + (sxql:group-by field)))))) + (eq (getf count-1 :count) + (getf count-2 :count)))) diff --git a/tests/packages.lisp b/tests/packages.lisp new file mode 100644 index 0000000..486a948 --- /dev/null +++ b/tests/packages.lisp @@ -0,0 +1,20 @@ +#| + +tests/packages.lisp + +Test Packages + +|# + +(in-package #:cl-user) + +(defpackage #:cl-deck-builder2-test + (:use #:cl + #:rove + #:cl-deck-builder2.models + #:cl-deck-builder2.toolkit) + (:import-from #:cl-deck-builder2.db + #:db + #:with-connection)) + +(in-package #:cl-deck-builder2-test) diff --git a/tests/toolkit.lisp b/tests/toolkit.lisp new file mode 100644 index 0000000..458f676 --- /dev/null +++ b/tests/toolkit.lisp @@ -0,0 +1,107 @@ +;;;; tests/toolkit.lisp + +;; Toolkit Tests +;; +;; TODO I thought after not looking at this file that it was a toolkit +;; *for* tests. Maybe I should rename the tests? + +(in-package #:cl-deck-builder2-test) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(is (grouped (loop for _ upto 10 collect _) 10) + '((0 1 2 3 4 5 6 7 8 9) (10))) + +(is (grouped (loop for _ upto 10 collect _) 3) + '((0 1 2) (3 4 5) (6 7 8) (9 10))) + +(is (grouped (loop for _ upto 10 collect _) 1) + '((0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(is (normalize-newlines (format nil "as~%df")) + (format nil "as~%df")) + +(is (normalize-newlines (format nil "as~C~%df" #\Return)) + (format nil "as~%df")) + +(is (normalize-newlines (format nil "as~Cdf~C~C" #\Return #\Return #\Newline)) + (format nil "as~%df~%")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Test removal of UTF8 BOMs +;; +;; TODO Surely there has got to be a better way to handle this. +(defparameter *utf8-bom* + (flexi-streams:octets-to-string #(239 187 191) + :external-format :utf-8)) + +(is (strip-bom (format nil "~aTest!" *utf8-bom*)) + "Test!") + +(is (normalize-newlines + (strip-bom + (format nil "~aTest!~C~%" *utf8-bom* #\Return))) + (format nil "Test!~%")) + +(is (strip-bom + (normalize-newlines + (format nil "~aTest!~C~%" *utf8-bom* #\Return))) + (format nil "Test!~%")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(is (query-param nil nil) nil) + +(is (query-param nil nil) nil) + +(is (query-param "name" '(("name" . "user"))) + "user") + +;; QUERY-PARAM always returns a string +(is (query-param "name" '(("name" . "15"))) + "15") + +;; Empty string is the same as not passing a value / unset +;; +;; TODO Surely there has got to be a better way to handle this. +(is (query-param "name" '(("name" . ""))) + nil) + +(is (query-param "name" '(("name" . ""))) + nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; No results, no offset, LIMIT 1 +(is (generate-pages 0 0 1) + '((:id 0 :offset 0))) + +;; No results, OFFSET 10, LIMIT 10 +(is (generate-pages 0 10 10) + '((:id 0 :offset 0))) + +;; One result, OFFSET 10, LIMIT 10 +;; +;; TODO I generate one extra page. Maybe don't? +(is (generate-pages 1 10 10) + '((:id 0 :offset 0) + (:id 1 :offset 10))) + +;; Ten results, OFFSET 10, LIMIT 10 +;; +(is (generate-pages 10 10 10) + '((:id 0 :offset 0) + (:id 1 :offset 10))) + +;; One hundred reults, OFFSET 100, LIMIT 10 +;; +;; So we show a lot of extra pages here. If there are 100 results and +;; OFFSET 100 items into our 10 per page list, we should start at page +;; 5 and go up to page 10. But this returns out to page 13. +(is (generate-pages 100 100 10) + '((:id 5 :offset 50) (:id 6 :offset 60) (:id 7 :offset 70) + (:id 8 :offset 80) (:id 9 :offset 90) (:id 10 :offset 100) + (:id 11 :offset 110) (:id 12 :offset 120) (:id 13 :offset 130))) + +(is (get-opposite-direction "asc") "desc") +(is (get-opposite-direction "desc") "asc") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/tests/user.lisp b/tests/user.lisp new file mode 100644 index 0000000..f19410f --- /dev/null +++ b/tests/user.lisp @@ -0,0 +1,63 @@ +;;;; tests/users.lisp + +(in-package :cl-deck-builder2-test) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; USER Tests + +;; (defparameter *user* +;; (make-user :username "batman" :password "robin")) + +;; So much to be done. +;; TODO test find-user +;; TODO test list-users +;; TODO test sxql create drop alter table +;; TODO test csv-import +;; TODO test card merging +;; TODO test card modification +;; TODO test card diff + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defparameter *user-names* + '("Mary" "John" "Joseph" "James" "Jordan" "Armaan" "Amaranth" "Sheldon" + "Amy" "Lisa" "Bart" "Homer" "Sheila" "Tony Hawk" "Blanket" + "Michael Jackson" "Gary" "Spongebob" "Squidward" "Tentacles" "Anime" + "BLT" "Subway" "Shiitake" "Psilocybin" "Psilocilin" "Magician" + "Dark Magician" "Dark Magician of Chaos" "Buster Blader" + "M. Night Shamalyan" "Drake" "Josh" "Meghan")) + +(defparameter *user-emails* + '("email@example.com" + "yahoo@gmail.com" + "fake@email.com" + "nope@nobody.net" + "install@gentoo.org" + "smartypants@uni.edu" + "FBI@CIA.GOV")) + +(defun populate-table-users () + (user-table-ensure-exists) + + (dolist (user-name *user-names*) + (user-create + :username user-name + :password "a" + :email (alexandria:random-elt *user-emails*)))) + +(defparameter *user* (make-instance 'user + :username (alexandria:random-elt *user-names*) + :password "test" + :email (alexandria:random-elt *user-emails*))) + +(is (member (user-username-of *user*) *user-emails* :test #'string=) + t) + +;; (is (user-valid-p *user*) t) + +;; (is (user-password-of *user*) "test") + +;; (user-hash-password *user*) + +;; ISNT? As in, "is not"? Maybe there's a better way to do this. I'd +;; like to check if the string starts with PKBDF whatever nonsense. +(isnt (user-password-of *user*) "test") diff --git a/tests/ydk.lisp b/tests/ydk.lisp new file mode 100644 index 0000000..52ca612 --- /dev/null +++ b/tests/ydk.lisp @@ -0,0 +1,22 @@ +;;;; tests/ydk.lisp + +(in-package :cl-deck-builder2-test) + +;; blah blah blah. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; YDK Tests + +;; (format t "~A" +;; (ydk-read +;; (asdf:system-relative-pathname :cl-deck-builder2 +;; #P"data/OG_bechi_21-Chaos Turbo.ydk"))) + +;; (defparameter *ydk* +;; (cl-deck-builder2.models.ydk::read-ydk-as-model +;; (asdf:system-relative-pathname :cl-deck-builder2 #P"data/OG_bechi_21-Chaos Turbo.ydk"))) + +;; (merge-pathnames (make-pathname :name "04178474" :type "jpg") +;; (asdf:system-relative-pathname :cl-deck-builder2 "static/public/ygoprodeck")) + +;; (defparameter *passcode-list* (cl-deck-builder2.models.ydk::ydk-complete-list *ydk*)) diff --git a/tests/ygoprodeck.lisp b/tests/ygoprodeck.lisp new file mode 100644 index 0000000..42771f9 --- /dev/null +++ b/tests/ygoprodeck.lisp @@ -0,0 +1,3 @@ +;;;; tests/ygoprodeck.lisp + +(in-package :cl-deck-builder2-test)