├── .deepsource.toml ├── .dockerignore ├── .github └── workflows │ ├── e2e-docker.yaml │ ├── e2e-k3s.yaml │ └── remove-label.yaml ├── .gitignore ├── Dockerfile ├── Dockerfile.multistage ├── README.md ├── app.py ├── docker-compose.yaml ├── helm ├── Chart.yaml ├── README.md ├── templates │ ├── _helper.tpl │ ├── flaskapp-persistentvolumeclaim.yaml │ ├── mongo-deployment.yaml │ ├── mongo-service.yaml │ ├── todo-flaskapp-deployment.yaml │ └── todo-flaskapp-service.yaml └── values.yaml ├── k8s ├── flaskapp-persistentvolumeclaim.yaml ├── mongo-deployment.yaml ├── mongo-service.yaml ├── todo-flaskapp-deployment.yaml └── todo-flaskapp-service.yaml ├── requirements.txt ├── static ├── assets │ ├── emoji.css │ ├── emoji.js │ ├── style.css │ ├── twemoji.js │ └── twemoji.min.js └── images │ ├── no.png │ ├── no_xl.png │ ├── screenshot.jpg │ ├── yes.png │ └── yes_xl.png └── templates ├── credits.html ├── index.html ├── searchlist.html └── update.html /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "3.x.x" 9 | 10 | [[analyzers]] 11 | name = "javascript" 12 | enabled = true -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | .Python 6 | env 7 | pip-log.txt 8 | pip-delete-this-directory.txt 9 | .tox 10 | .coverage 11 | .coverage.* 12 | .cache 13 | nosetests.xml 14 | coverage.xml 15 | *.cover 16 | *.log 17 | .git 18 | .mypy_cache 19 | .pytest_cache 20 | .hypothesis -------------------------------------------------------------------------------- /.github/workflows/e2e-docker.yaml: -------------------------------------------------------------------------------- 1 | name: e2e-docker 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | jobs: 8 | 9 | e2e-docker: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.event.label.name == 'ok-to-test' }} 12 | env: 13 | DOCKER_TAG: pull-${{ github.event.number }} 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Build Docker image 19 | run: docker build -t coolboi567/todo-flaskapp:$DOCKER_TAG . 20 | 21 | - name: Deploy the FlaskApp 22 | run: docker-compose up -d --remove-orphans 23 | 24 | - name: Get short commit SHA and display tunnel URL 25 | id: get-subdomain 26 | run: | 27 | subdomain="todo-flaskapp-docker-$(git rev-parse --short HEAD)" 28 | echo "URL for tunnelling: http://$subdomain.loca.lt" 29 | echo "::set-output name=subdomain::$subdomain" 30 | 31 | - name: Start tunnel 32 | env: 33 | SUBDOMAIN: ${{ steps.get-subdomain.outputs.subdomain }} 34 | run: | 35 | npm install -g localtunnel 36 | port=$(docker port flask-app | grep -E '[0-9]{4,5}' -o | head -n 1) 37 | lt -p $port -s $SUBDOMAIN 38 | -------------------------------------------------------------------------------- /.github/workflows/e2e-k3s.yaml: -------------------------------------------------------------------------------- 1 | name: e2e-k3s 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | jobs: 8 | 9 | e2e-k3s: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.event.label.name == 'ok-to-test' }} 12 | env: 13 | DOCKER_TAG: pull-${{ github.event.number }} 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Build Docker image 19 | run: docker build -t coolboi567/todo-flaskapp:$DOCKER_TAG . 20 | 21 | - name: Create a k3s cluster 22 | uses: AbsaOSS/k3d-action@v2 23 | with: 24 | cluster-name: "flaskapp" 25 | 26 | - name: Inject the images to the cluster 27 | run: k3d image import coolboi567/todo-flaskapp:$DOCKER_TAG -c flaskapp 28 | 29 | - name: Deploy the FlaskApp 30 | run: | 31 | # create flaskapp namespace 32 | kubectl create ns flaskapp 33 | 34 | # installing FlaskApp using helm 35 | helm install my-release -n flaskapp ./helm \ 36 | --wait \ 37 | --timeout 10m0s \ 38 | --set flaskapp.service.type=LoadBalancer \ 39 | --set flaskapp.image.tag=$DOCKER_TAG 40 | 41 | # get pods, services and the container images 42 | kubectl get pods -n flaskapp 43 | kubectl get svc -n flaskapp 44 | 45 | - name: Get short commit SHA and display tunnel URL 46 | id: get-subdomain 47 | run: | 48 | subdomain="todo-flaskapp-k3s-$(git rev-parse --short HEAD)" 49 | echo "URL for tunnelling: http://$subdomain.loca.lt" 50 | echo "::set-output name=subdomain::$subdomain" 51 | 52 | - name: Start tunnel 53 | env: 54 | SUBDOMAIN: ${{ steps.get-subdomain.outputs.subdomain }} 55 | run: | 56 | npm install -g localtunnel 57 | host=$(kubectl get svc -n flaskapp | grep flaskapp | tr -s ' ' | cut -d" " -f4) 58 | port=$(kubectl get svc -n flaskapp | grep flaskapp | tr -s ' ' | cut -d" " -f5 | cut -d":" -f1) 59 | lt -p $port -l $host -s $SUBDOMAIN 60 | -------------------------------------------------------------------------------- /.github/workflows/remove-label.yaml: -------------------------------------------------------------------------------- 1 | name: remove-label 2 | 3 | on: 4 | pull_request_target: 5 | types: [synchronize] 6 | 7 | jobs: 8 | remove: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Remove label 12 | uses: buildsville/add-remove-label@v1 13 | with: 14 | label: ok-to-test 15 | type: remove 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM python:3.10-alpine3.15 4 | 5 | LABEL MAINTAINER "Prashant Shahi " 6 | 7 | RUN pip install --upgrade pip 8 | 9 | RUN adduser -D appuser 10 | USER appuser 11 | WORKDIR /home/appuser 12 | 13 | ENV PATH="/home/appuser/.local/bin:${PATH}" 14 | 15 | COPY --chown=appuser:appuser requirements.txt requirements.txt 16 | RUN pip install --user -r requirements.txt 17 | 18 | COPY --chown=appuser:appuser . . 19 | 20 | CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"] 21 | -------------------------------------------------------------------------------- /Dockerfile.multistage: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM python:3.9-alpine3.15 2 | 3 | LABEL MAINTAINER "Prashant Shahi " 4 | 5 | RUN pip install --upgrade pip 6 | 7 | RUN adduser -D appuser 8 | USER appuser 9 | WORKDIR /home/appuser 10 | 11 | ENV PATH="/home/appuser/.local/bin:${PATH}" 12 | 13 | COPY --chown=appuser:appuser requirements.txt requirements.txt 14 | RUN pip install --user -r requirements.txt 15 | 16 | COPY --chown=appuser:appuser . . 17 | 18 | ENTRYPOINT ["python"] 19 | 20 | CMD ["app.py"] 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # To-Do-List 2 | 3 | To-Do-List is mini-project made with Flask and MongoDB. Dockerfile is also available to make docker image and docker containers. 4 | 5 | ## Built using : 6 | ```sh 7 | Flask : Python Based mini-Webframework 8 | MongoDB : Database Server 9 | Pymongo : Database Connector ( For creating connectiong between MongoDB and Flask ) 10 | HTML5 (jinja2) : For Form and Table 11 | ``` 12 | 13 | ## Set up environment for using this repo: 14 | ``` 15 | Install Python ( If you don't have already ) 16 | $ sudo apt-get install python 17 | 18 | Install MongoDB ( Make sure you install it properly ) 19 | $ sudo apt install -y mongodb 20 | 21 | 22 | Install Dependencies of the application (Flask, Bson and PyMongo) 23 | $ pip install -r requirements.txt 24 | ``` 25 | 26 | ## Run the application 27 | ``` 28 | Run MongoDB 29 | 1) Start MongoDB 30 | $ sudo service mongod start 31 | 2) Stop MongoDB 32 | $ sudo service mongod stop 33 | 34 | Run the Flask file(app.py) 35 | $ FLASK_ENV=development python app.py 36 | 37 | Go to http://localhost:5000 with any of browsers and DONE !! 38 | $ open http://localhost:5000 39 | 40 | To exit press Ctrl+C 41 | ``` 42 | 43 | ## Using [Docker](https://www.docker.com) [Docker-Compose](https://docs.docker.com/compose) 44 | 45 | Make sure that you are inside the project directory, where `docker-compose.yaml` file is present. Now, building and running the application server container and mongodb container using `docker-compose` : 46 | ``` 47 | Building or fetching the necessary images and later, creating and starting containers for the application 48 | $ docker-compose up -d 49 | 50 | Go to http://localhost:5000 with any of browsers and DONE !! 51 | $ open http://localhost:5000 52 | ``` 53 | 54 | ### Running, Debugging and Stopping the application under the hood 55 | ``` 56 | For almost all of the `docker-compose` commands, make sure that you are inside the project directory, where `docker-compose.yaml` file is present. 57 | 58 | Passing `-d` flag along with docker-compose, runs the application as daemon 59 | $ docker-compose up -d 60 | 61 | Seeing all of the logs from the application deployed. 62 | $ docker-compose logs 63 | 64 | Stopping the application 65 | $ docker-compose down 66 | ``` 67 | 68 | ## Screenshot : 69 | 70 | ![Screenshot of the Output](https://github.com/CoolBoi567/ToDo-List-using-Flask-and-MongoDB/blob/master/static/images/screenshot.jpg?raw=true "Screenshot of Output") 71 | 72 | Thanks to Twitter for emoji support with [Twemoji](https://github.com/twitter/twemoji). 73 | 74 | Made with ❤️ from Nepal 🇳🇵 75 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template,request,redirect,url_for # For flask implementation 2 | from pymongo import MongoClient # Database connector 3 | from bson.objectid import ObjectId # For ObjectId to work 4 | from bson.errors import InvalidId # For catching InvalidId exception for ObjectId 5 | import os 6 | 7 | mongodb_host = os.environ.get('MONGO_HOST', 'localhost') 8 | mongodb_port = int(os.environ.get('MONGO_PORT', '27017')) 9 | client = MongoClient(mongodb_host, mongodb_port) #Configure the connection to the database 10 | db = client.camp2016 #Select the database 11 | todos = db.todo #Select the collection 12 | 13 | app = Flask(__name__) 14 | title = "TODO with Flask" 15 | heading = "ToDo Reminder" 16 | #modify=ObjectId() 17 | 18 | def redirect_url(): 19 | return request.args.get('next') or \ 20 | request.referrer or \ 21 | url_for('index') 22 | 23 | @app.route("/list") 24 | def lists (): 25 | #Display the all Tasks 26 | todos_l = todos.find() 27 | a1="active" 28 | return render_template('index.html',a1=a1,todos=todos_l,t=title,h=heading) 29 | 30 | @app.route("/") 31 | @app.route("/uncompleted") 32 | def tasks (): 33 | #Display the Uncompleted Tasks 34 | todos_l = todos.find({"done":"no"}) 35 | a2="active" 36 | return render_template('index.html',a2=a2,todos=todos_l,t=title,h=heading) 37 | 38 | 39 | @app.route("/completed") 40 | def completed (): 41 | #Display the Completed Tasks 42 | todos_l = todos.find({"done":"yes"}) 43 | a3="active" 44 | return render_template('index.html',a3=a3,todos=todos_l,t=title,h=heading) 45 | 46 | @app.route("/done") 47 | def done (): 48 | #Done-or-not ICON 49 | id=request.values.get("_id") 50 | task=todos.find({"_id":ObjectId(id)}) 51 | if(task[0]["done"]=="yes"): 52 | todos.update_one({"_id":ObjectId(id)}, {"$set": {"done":"no"}}) 53 | else: 54 | todos.update_one({"_id":ObjectId(id)}, {"$set": {"done":"yes"}}) 55 | redir=redirect_url() # Re-directed URL i.e. PREVIOUS URL from where it came into this one 56 | 57 | # if(str(redir)=="http://localhost:5000/search"): 58 | # redir+="?key="+id+"&refer="+refer 59 | 60 | return redirect(redir) 61 | 62 | #@app.route("/add") 63 | #def add(): 64 | # return render_template('add.html',h=heading,t=title) 65 | 66 | @app.route("/action", methods=['POST']) 67 | def action (): 68 | #Adding a Task 69 | name=request.values.get("name") 70 | desc=request.values.get("desc") 71 | date=request.values.get("date") 72 | pr=request.values.get("pr") 73 | todos.insert_one({ "name":name, "desc":desc, "date":date, "pr":pr, "done":"no"}) 74 | return redirect("/list") 75 | 76 | @app.route("/remove") 77 | def remove (): 78 | #Deleting a Task with various references 79 | key=request.values.get("_id") 80 | todos.delete_one({"_id":ObjectId(key)}) 81 | return redirect("/") 82 | 83 | @app.route("/update") 84 | def update (): 85 | id=request.values.get("_id") 86 | task=todos.find({"_id":ObjectId(id)}) 87 | return render_template('update.html',tasks=task,h=heading,t=title) 88 | 89 | @app.route("/action3", methods=['POST']) 90 | def action3 (): 91 | #Updating a Task with various references 92 | name=request.values.get("name") 93 | desc=request.values.get("desc") 94 | date=request.values.get("date") 95 | pr=request.values.get("pr") 96 | id=request.values.get("_id") 97 | todos.update_one({"_id":ObjectId(id)}, {'$set':{ "name":name, "desc":desc, "date":date, "pr":pr }}) 98 | return redirect("/") 99 | 100 | @app.route("/search", methods=['GET']) 101 | def search(): 102 | #Searching a Task with various references 103 | 104 | key=request.values.get("key") 105 | refer=request.values.get("refer") 106 | if(refer=="id"): 107 | try: 108 | todos_l = todos.find({refer:ObjectId(key)}) 109 | if not todos_l: 110 | return render_template('index.html',a2=a2,todos=todos_l,t=title,h=heading,error="No such ObjectId is present") 111 | except InvalidId as err: 112 | pass 113 | return render_template('index.html',a2=a2,todos=todos_l,t=title,h=heading,error="Invalid ObjectId format given") 114 | else: 115 | todos_l = todos.find({refer:key}) 116 | return render_template('searchlist.html',todos=todos_l,t=title,h=heading) 117 | 118 | @app.route("/about") 119 | def about(): 120 | return render_template('credits.html',t=title,h=heading) 121 | 122 | if __name__ == "__main__": 123 | env = os.environ.get('FLASK_ENV', 'development') 124 | port = int(os.environ.get('PORT', 5000)) 125 | debug = False if env == 'production' else True 126 | app.run(debug=True) 127 | app.run(port=port, debug=debug) 128 | # Careful with the debug mode.. -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | todo-flaskapp: 5 | image: coolboi567/todo-flaskapp:${DOCKER_TAG:-latest} 6 | ports: 7 | - 5000:5000 8 | container_name: flask-app 9 | environment: 10 | - FLASK_ENV=development 11 | - PORT=5000 12 | - MONGO_HOST=mongo 13 | - MONGO_PORT=27017 14 | depends_on: 15 | - mongo 16 | mongo: 17 | image: mvertes/alpine-mongo:latest 18 | volumes: 19 | - flaskapp:/data/db/ 20 | ports: 21 | - 27017:27017 22 | 23 | volumes: 24 | flaskapp: -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: todo-flaskapp 2 | description: Helm Chart for ToDo FlaskApp with MongoDB 3 | version: 0.0.1 4 | apiVersion: v1 5 | keywords: 6 | - flaskapp 7 | sources: 8 | - https://github.com/prashant-shahi/ToDo-List-using-Flask-and-MongoDB 9 | - https://prashantshahi.dev 10 | home: https://github.com/prashant-shahi/ToDo-List-using-Flask-and-MongoDB 11 | -------------------------------------------------------------------------------- /helm/README.md: -------------------------------------------------------------------------------- 1 | # Helm Chart for ToDo-FlaskApp 2 | 3 | This directory contains Helm chart for the ToDo-FlaskApp. 4 | -------------------------------------------------------------------------------- /helm/templates/_helper.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{/* 4 | Expand the name of the chart. 5 | */}} 6 | {{- define "flaskapp.name" -}} 7 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 8 | {{- end -}} 9 | 10 | {{/* 11 | Return the proper flaskapp image name 12 | */}} 13 | {{- define "flaskapp.image" -}} 14 | {{- $registryName := .Values.flaskapp.image.registry -}} 15 | {{- $repositoryName := .Values.flaskapp.image.repository -}} 16 | {{- $tag := .Values.flaskapp.image.tag | toString -}} 17 | {{- if $registryName -}} 18 | {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} 19 | {{- else -}} 20 | {{- printf "%s:%s" $repositoryName $tag -}} 21 | {{- end -}} 22 | {{- end -}} 23 | 24 | {{/* 25 | Return the proper mongo image name 26 | */}} 27 | {{- define "mongo.image" -}} 28 | {{- $registryName := .Values.mongo.image.registry -}} 29 | {{- $repositoryName := .Values.mongo.image.repository -}} 30 | {{- $tag := .Values.mongo.image.tag | toString -}} 31 | {{- if $registryName -}} 32 | {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} 33 | {{- else -}} 34 | {{- printf "%s:%s" $repositoryName $tag -}} 35 | {{- end -}} 36 | {{- end -}} -------------------------------------------------------------------------------- /helm/templates/flaskapp-persistentvolumeclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: {{ .Values.flaskapp.name }} 6 | name: {{ .Values.flaskapp.name }} 7 | spec: 8 | accessModes: 9 | - ReadWriteOnce 10 | resources: 11 | requests: 12 | storage: 100Mi 13 | status: {} 14 | -------------------------------------------------------------------------------- /helm/templates/mongo-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: {{ .Values.mongo.name }} 6 | name: {{ .Values.mongo.name }} 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/component: {{ .Values.mongo.name }} 12 | strategy: 13 | type: Recreate 14 | template: 15 | metadata: 16 | labels: 17 | app.kubernetes.io/component: {{ .Values.mongo.name }} 18 | spec: 19 | containers: 20 | - image: {{ template "mongo.image" . }} 21 | name: {{ .Values.mongo.name }} 22 | ports: 23 | - containerPort: {{ .Values.mongo.service.port }} 24 | resources: {} 25 | volumeMounts: 26 | - mountPath: /data/db/ 27 | name: {{ .Values.flaskapp.name }} 28 | restartPolicy: Always 29 | volumes: 30 | - name: {{ .Values.flaskapp.name }} 31 | persistentVolumeClaim: 32 | claimName: {{ .Values.flaskapp.name }} 33 | status: {} 34 | -------------------------------------------------------------------------------- /helm/templates/mongo-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: {{ .Values.mongo.name }} 6 | name: {{ .Values.mongo.name }} 7 | spec: 8 | ports: 9 | - name: "http" 10 | port: {{ .Values.mongo.service.port }} 11 | targetPort: {{ .Values.mongo.service.port }} 12 | protocol: TCP 13 | selector: 14 | app.kubernetes.io/component: {{ .Values.mongo.name }} 15 | type: {{ .Values.mongo.service.type }} 16 | status: 17 | loadBalancer: {} 18 | -------------------------------------------------------------------------------- /helm/templates/todo-flaskapp-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: {{ .Values.flaskapp.name }} 6 | name: {{ .Values.flaskapp.name }} 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/component: {{ .Values.flaskapp.name }} 12 | strategy: {} 13 | template: 14 | metadata: 15 | labels: 16 | app.kubernetes.io/component: {{ .Values.flaskapp.name }} 17 | spec: 18 | initContainers: 19 | - name: {{ .Values.flaskapp.name }}-init 20 | image: {{ template "mongo.image" . }} 21 | imagePullPolicy: IfNotPresent 22 | env: 23 | - name: MONGO_HOST 24 | value: {{ .Values.mongo.name }} 25 | - name: MONGO_PORT 26 | value: {{ .Values.mongo.service.port | quote }} 27 | command: 28 | - sh 29 | - -c 30 | - until mongo --eval 'db.runCommand("ping").ok' ${MONGO_HOST}:${MONGO_PORT}/test --quiet; do echo -e "waiting for MongoDB"; sleep 5; done; echo -e "MongoDB ready, starting ToDo-FlaskApp now"; 31 | containers: 32 | - name: {{ .Values.flaskapp.name }} 33 | image: {{ template "flaskapp.image" . }} 34 | env: 35 | - name: FLASK_ENV 36 | value: {{ .Values.flaskapp.env }} 37 | - name: MONGO_HOST 38 | value: {{ .Values.mongo.name }} 39 | - name: MONGO_PORT 40 | value: {{ .Values.mongo.service.port | quote }} 41 | - name: PORT 42 | value: {{ .Values.flaskapp.service.port | quote }} 43 | ports: 44 | - containerPort: {{ .Values.flaskapp.service.port }} 45 | resources: {} 46 | restartPolicy: Always 47 | status: {} 48 | -------------------------------------------------------------------------------- /helm/templates/todo-flaskapp-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/component: {{ .Values.flaskapp.name }} 6 | name: todo-flaskapp 7 | spec: 8 | ports: 9 | - name: "http" 10 | port: {{ .Values.flaskapp.service.port }} 11 | targetPort: {{ .Values.flaskapp.service.port }} 12 | protocol: TCP 13 | selector: 14 | app.kubernetes.io/component: {{ .Values.flaskapp.name }} 15 | type: {{ .Values.flaskapp.service.type }} 16 | status: 17 | loadBalancer: {} 18 | -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | nameoverride: "" 2 | 3 | mongo: 4 | name: mongo 5 | image: 6 | registry: docker.io 7 | repository: mvertes/alpine-mongo 8 | tag: latest 9 | imagePullPolicy: IfNotPresent 10 | service: 11 | type: ClusterIP 12 | port: 27017 13 | 14 | flaskapp: 15 | name: todo-flaskapp 16 | env: production 17 | image: 18 | registry: docker.io 19 | repository: coolboi567/todo-flaskapp 20 | tag: latest 21 | imagePullPolicy: Always 22 | service: 23 | type: ClusterIP 24 | port: 5000 -------------------------------------------------------------------------------- /k8s/flaskapp-persistentvolumeclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | io.kompose.service: flaskapp 7 | name: flaskapp 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 100Mi 14 | status: {} 15 | -------------------------------------------------------------------------------- /k8s/mongo-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | kompose.cmd: kompose convert -o k8s/ 6 | kompose.version: 1.26.0 (40646f47) 7 | creationTimestamp: null 8 | labels: 9 | io.kompose.service: mongo 10 | name: mongo 11 | spec: 12 | replicas: 1 13 | selector: 14 | matchLabels: 15 | io.kompose.service: mongo 16 | strategy: 17 | type: Recreate 18 | template: 19 | metadata: 20 | annotations: 21 | kompose.cmd: kompose convert -o k8s/ 22 | kompose.version: 1.26.0 (40646f47) 23 | creationTimestamp: null 24 | labels: 25 | io.kompose.service: mongo 26 | spec: 27 | containers: 28 | - image: mvertes/alpine-mongo:latest 29 | name: mongo 30 | ports: 31 | - containerPort: 27017 32 | resources: {} 33 | volumeMounts: 34 | - mountPath: /data/db/ 35 | name: flaskapp 36 | restartPolicy: Always 37 | volumes: 38 | - name: flaskapp 39 | persistentVolumeClaim: 40 | claimName: flaskapp 41 | status: {} 42 | -------------------------------------------------------------------------------- /k8s/mongo-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | kompose.cmd: kompose convert -o k8s/ 6 | kompose.version: 1.26.0 (40646f47) 7 | creationTimestamp: null 8 | labels: 9 | io.kompose.service: mongo 10 | name: mongo 11 | spec: 12 | ports: 13 | - name: "27017" 14 | port: 27017 15 | targetPort: 27017 16 | selector: 17 | io.kompose.service: mongo 18 | status: 19 | loadBalancer: {} 20 | -------------------------------------------------------------------------------- /k8s/todo-flaskapp-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | kompose.cmd: kompose convert -o k8s/ 6 | kompose.version: 1.26.0 (40646f47) 7 | creationTimestamp: null 8 | labels: 9 | io.kompose.service: todo-flaskapp 10 | name: todo-flaskapp 11 | spec: 12 | replicas: 1 13 | selector: 14 | matchLabels: 15 | io.kompose.service: todo-flaskapp 16 | strategy: {} 17 | template: 18 | metadata: 19 | annotations: 20 | kompose.cmd: kompose convert -o k8s/ 21 | kompose.version: 1.26.0 (40646f47) 22 | creationTimestamp: null 23 | labels: 24 | io.kompose.service: todo-flaskapp 25 | spec: 26 | initContainers: 27 | - name: flaskapp-init 28 | image: mvertes/alpine-mongo:latest 29 | imagePullPolicy: IfNotPresent 30 | env: 31 | - name: MONGO_HOST 32 | value: mongo 33 | - name: MONGO_PORT 34 | value: "27017" 35 | command: 36 | - sh 37 | - -c 38 | - until mongo --eval 'db.runCommand("ping").ok' ${MONGO_HOST}:${MONGO_PORT}/test --quiet; do echo -e "waiting for MongoDB"; sleep 5; done; echo -e "MongoDB ready, starting ToDo-FlaskApp now"; 39 | containers: 40 | - env: 41 | - name: FLASK_ENV 42 | value: development 43 | - name: MONGO_HOST 44 | value: mongo 45 | - name: MONGO_PORT 46 | value: "27017" 47 | - name: PORT 48 | value: "5000" 49 | image: coolboi567/todo-flaskapp:latest 50 | imagePullPolicy: Always 51 | name: flask-app 52 | ports: 53 | - containerPort: 5000 54 | resources: {} 55 | restartPolicy: Always 56 | status: {} 57 | -------------------------------------------------------------------------------- /k8s/todo-flaskapp-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | kompose.cmd: kompose convert -o k8s/ 6 | kompose.version: 1.26.0 (40646f47) 7 | creationTimestamp: null 8 | labels: 9 | io.kompose.service: todo-flaskapp 10 | name: todo-flaskapp 11 | spec: 12 | ports: 13 | - name: "5000" 14 | port: 5000 15 | targetPort: 5000 16 | selector: 17 | io.kompose.service: todo-flaskapp 18 | status: 19 | loadBalancer: {} 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.3.2 2 | pymongo==4.6.3 -------------------------------------------------------------------------------- /static/assets/emoji.css: -------------------------------------------------------------------------------- 1 | img.emoji { 2 | // Override any img styles to ensure Emojis are displayed inline 3 | margin: 0px !important; 4 | display: inline !important; 5 | } 6 | -------------------------------------------------------------------------------- /static/assets/emoji.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // Set the size of the rendered Emojis 3 | // This can be set to 16x16, 36x36, or 72x72 4 | twemoji.size = '16x16'; 5 | // Parse the document body and 6 | // insert tags in place of Unicode Emojis 7 | twemoji.parse(document.body); 8 | } 9 | -------------------------------------------------------------------------------- /static/assets/style.css: -------------------------------------------------------------------------------- 1 | h1{ 2 | /* font-family:"Arial Black", Gadget, sans-serif;*/ 3 | font-family:"Times New Romans", Gadget, sans-serif; 4 | } 5 | body{ 6 | color:black; 7 | background-color:white; 8 | left-margin:10px; 9 | right-margin:10px; 10 | } 11 | table{ 12 | width:95%; 13 | border-collapse:collapse; 14 | table-layout:fixed; 15 | } 16 | td 17 | { 18 | border: rgba(224, 119, 70, 1) 1px solid; 19 | word-wrap:break-word; 20 | } 21 | table#close{ 22 | 23 | table-layout:default; 24 | border-collapse:seperate; 25 | border-spacing: 5px 5px; 26 | border:none; 27 | vertical-align:top; 28 | } 29 | table.none 30 | { 31 | border:none; 32 | vertical-align:top; 33 | } 34 | table.none td 35 | { 36 | border: none; 37 | } 38 | tr.row td{ 39 | text-decoration:italic; 40 | } 41 | th.status{ 42 | width:5%; 43 | } 44 | th.name{ 45 | width:20%; 46 | } 47 | th.des{ 48 | width:40%; 49 | padding:5px 5px 5px 5px; 50 | } 51 | th.date{ 52 | width:8%; 53 | } 54 | th.pr{ 55 | width:7%; 56 | } 57 | th.func1{ 58 | width:6%; 59 | } 60 | th.func2{ 61 | width:5%; 62 | } 63 | td.date, td.pr, td.func1, td.func2 { 64 | text-align: center; 65 | } 66 | 67 | /* Grid CSS */ 68 | .row::after { 69 | content: ""; 70 | clear: both; 71 | display: table; 72 | margin: 1.2em; 73 | } 74 | .col-1 { width: 8.33%; } 75 | .col-2 { width: 16.66%; } 76 | .col-3 { width: 25%; } 77 | .col-4 { width: 33.33%; } 78 | .col-5 { width: 41.66%; } 79 | .col-6 { width: 50%; } 80 | .col-7 { width: 58.33%; } 81 | .col-8 { width: 66.66%; } 82 | .col-9 { width: 75%; } 83 | .col-10 { width: 83.33%; } 84 | .col-11 { width: 91.66%; } 85 | .col-12 { width: 100%; } 86 | 87 | button[type=submit] { 88 | background-color: #428bca; 89 | border: none; 90 | color: white; 91 | border-radius: 15px 15px; 92 | padding: 10px 20px; 93 | text-decoration: none; 94 | margin: 4px 2px; 95 | cursor: pointer; 96 | } 97 | 98 | .success { color: #5cb85c; } 99 | .danger { color: #d9534f; } 100 | .warning { color: #ff9800; } 101 | .info { color: #2196F3; } 102 | 103 | ul { 104 | list-style-type: none; 105 | margin: 0; 106 | padding: 0; 107 | overflow: hidden; 108 | background-color: #333; 109 | } 110 | li { 111 | float: left; 112 | } 113 | li a { 114 | display: block; 115 | color: white; 116 | text-align: center; 117 | padding: 14px 16px; 118 | text-decoration: none; 119 | } 120 | a.active { 121 | background-color: #4CAF50; 122 | } 123 | /* Change the link color to #111 (black) on hover */ 124 | li a:hover { 125 | background-color: #111; 126 | } 127 | 128 | -------------------------------------------------------------------------------- /static/assets/twemoji.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, browser: true, bitwise: true, plusplus: true */ 2 | var twemoji = (function ( 3 | /*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//* 4 | https://github.com/twitter/twemoji/blob/gh-pages/LICENSE 5 | */ 6 | 7 | // WARNING: this file is generated automatically via 8 | // `node twemoji-generator.js` 9 | // please update its `createTwemoji` function 10 | // at the bottom of the same file instead. 11 | 12 | ) { 13 | 'use strict'; 14 | 15 | /*jshint maxparams:4 */ 16 | 17 | var 18 | // the exported module object 19 | twemoji = { 20 | 21 | 22 | ///////////////////////// 23 | // properties // 24 | ///////////////////////// 25 | 26 | // default assets url, by default will be Twitter Inc. CDN 27 | base: (location.protocol === 'https:' ? 'https:' : 'http:') + 28 | '//twemoji.maxcdn.com/', 29 | 30 | // default assets file extensions, by default '.png' 31 | ext: '.png', 32 | 33 | // default assets/folder size, by default "36x36" 34 | // available via Twitter CDN: 16, 36, 72 35 | size: '36x36', 36 | 37 | // default class name, by default 'emoji' 38 | className: 'emoji', 39 | 40 | // basic utilities / helpers to convert code points 41 | // to JavaScript surrogates and vice versa 42 | convert: { 43 | 44 | /** 45 | * Given an HEX codepoint, returns UTF16 surrogate pairs. 46 | * 47 | * @param string generic codepoint, i.e. '1F4A9' 48 | * @return string codepoint transformed into utf16 surrogates pair, 49 | * i.e. \uD83D\uDCA9 50 | * 51 | * @example 52 | * twemoji.convert.fromCodePoint('1f1e8'); 53 | * // "\ud83c\udde8" 54 | * 55 | * '1f1e8-1f1f3'.split('-').map(twemoji.convert.fromCodePoint).join('') 56 | * // "\ud83c\udde8\ud83c\uddf3" 57 | */ 58 | fromCodePoint: fromCodePoint, 59 | 60 | /** 61 | * Given UTF16 surrogate pairs, returns the equivalent HEX codepoint. 62 | * 63 | * @param string generic utf16 surrogates pair, i.e. \uD83D\uDCA9 64 | * @param string optional separator for double code points, default='-' 65 | * @return string utf16 transformed into codepoint, i.e. '1F4A9' 66 | * 67 | * @example 68 | * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3'); 69 | * // "1f1e8-1f1f3" 70 | * 71 | * twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3', '~'); 72 | * // "1f1e8~1f1f3" 73 | */ 74 | toCodePoint: toCodePoint 75 | }, 76 | 77 | 78 | ///////////////////////// 79 | // methods // 80 | ///////////////////////// 81 | 82 | /** 83 | * User first: used to remove missing images 84 | * preserving the original text intent when 85 | * a fallback for network problems is desired. 86 | * Automatically added to Image nodes via DOM 87 | * It could be recycled for string operations via: 88 | * $('img.emoji').on('error', twemoji.onerror) 89 | */ 90 | onerror: function onerror() { 91 | if (this.parentNode) { 92 | this.parentNode.replaceChild(createText(this.alt), this); 93 | } 94 | }, 95 | 96 | /** 97 | * Main method/logic to generate either tags or HTMLImage nodes. 98 | * "emojify" a generic text or DOM Element. 99 | * 100 | * @overloads 101 | * 102 | * String replacement for `innerHTML` or server side operations 103 | * twemoji.parse(string); 104 | * twemoji.parse(string, Function); 105 | * twemoji.parse(string, Object); 106 | * 107 | * HTMLElement tree parsing for safer operations over existing DOM 108 | * twemoji.parse(HTMLElement); 109 | * twemoji.parse(HTMLElement, Function); 110 | * twemoji.parse(HTMLElement, Object); 111 | * 112 | * @param string|HTMLElement the source to parse and enrich with emoji. 113 | * 114 | * string replace emoji matches with tags. 115 | * Mainly used to inject emoji via `innerHTML` 116 | * It does **not** parse the string or validate it, 117 | * it simply replaces found emoji with a tag. 118 | * NOTE: be sure this won't affect security. 119 | * 120 | * HTMLElement walk through the DOM tree and find emoji 121 | * that are inside **text node only** (nodeType === 3) 122 | * Mainly used to put emoji in already generated DOM 123 | * without compromising surrounding nodes and 124 | * **avoiding** the usage of `innerHTML`. 125 | * NOTE: Using DOM elements instead of strings should 126 | * improve security without compromising too much 127 | * performance compared with a less safe `innerHTML`. 128 | * 129 | * @param Function|Object [optional] 130 | * either the callback that will be invoked or an object 131 | * with all properties to use per each found emoji. 132 | * 133 | * Function if specified, this will be invoked per each emoji 134 | * that has been found through the RegExp except 135 | * those follwed by the invariant \uFE0E ("as text"). 136 | * Once invoked, parameters will be: 137 | * 138 | * codePoint:string the lower case HEX code point 139 | * i.e. "1f4a9" 140 | * 141 | * options:Object all info for this parsing operation 142 | * 143 | * variant:char the optional \uFE0F ("as image") 144 | * variant, in case this info 145 | * is anyhow meaningful. 146 | * By default this is ignored. 147 | * 148 | * If such callback will return a falsy value instead 149 | * of a valid `src` to use for the image, nothing will 150 | * actually change for that specific emoji. 151 | * 152 | * 153 | * Object if specified, an object containing the following properties 154 | * 155 | * callback Function the callback to invoke per each found emoji. 156 | * base string the base url, by default twemoji.base 157 | * ext string the image extension, by default twemoji.ext 158 | * size string the assets size, by default twemoji.size 159 | * 160 | * @example 161 | * 162 | * twemoji.parse("I \u2764\uFE0F emoji!"); 163 | * // I ❤️ emoji! 164 | * 165 | * 166 | * twemoji.parse("I \u2764\uFE0F emoji!", function(icon, options, variant) { 167 | * return '/assets/' + icon + '.gif'; 168 | * }); 169 | * // I ❤️ emoji! 170 | * 171 | * 172 | * twemoji.parse("I \u2764\uFE0F emoji!", { 173 | * size: 72, 174 | * callback: function(icon, options, variant) { 175 | * return '/assets/' + options.size + '/' + icon + options.ext; 176 | * } 177 | * }); 178 | * // I ❤️ emoji! 179 | * 180 | */ 181 | parse: parse, 182 | 183 | /** 184 | * Given a string, invokes the callback argument 185 | * per each emoji found in such string. 186 | * This is the most raw version used by 187 | * the .parse(string) method itself. 188 | * 189 | * @param string generic string to parse 190 | * @param Function a generic callback that will be 191 | * invoked to replace the content. 192 | * This calback wil receive standard 193 | * String.prototype.replace(str, callback) 194 | * arguments such: 195 | * callback( 196 | * match, // the emoji match 197 | * icon, // the emoji text (same as text) 198 | * variant // either '\uFE0E' or '\uFE0F', if present 199 | * ); 200 | * 201 | * and others commonly received via replace. 202 | * 203 | * NOTE: When the variant \uFE0E is found, remember this is an explicit intent 204 | * from the user: the emoji should **not** be replaced with an image. 205 | * In \uFE0F case one, it's the opposite, it should be graphic. 206 | * This utility convetion is that only \uFE0E are not translated into images. 207 | */ 208 | replace: replace, 209 | 210 | /** 211 | * Simplify string tests against emoji. 212 | * 213 | * @param string some text that might contain emoji 214 | * @return boolean true if any emoji was found, false otherwise. 215 | * 216 | * @example 217 | * 218 | * if (twemoji.test(someContent)) { 219 | * console.log("emoji All The Things!"); 220 | * } 221 | */ 222 | test: test 223 | }, 224 | 225 | // used to escape HTML special chars in attributes 226 | escaper = { 227 | '&': '&', 228 | '<': '<', 229 | '>': '>', 230 | "'": ''', 231 | '"': '"' 232 | }, 233 | 234 | // RegExp based on emoji's official Unicode standards 235 | // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt 236 | re = /((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd7f|\ud83c\ude02|\ud83c\ude1a|\ud83c\ude2f|\ud83c\ude37|\u3299|\u303d|\u3030|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2122|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g, 237 | 238 | // used to find HTML special chars in attributes 239 | rescaper = /[&<>'"]/g, 240 | 241 | // nodes with type 1 which should **not** be parsed (including lower case svg) 242 | shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/, 243 | 244 | // just a private shortcut 245 | fromCharCode = String.fromCharCode; 246 | 247 | return twemoji; 248 | 249 | 250 | ///////////////////////// 251 | // private functions // 252 | // declaration // 253 | ///////////////////////// 254 | 255 | /** 256 | * Shortcut to create text nodes 257 | * @param string text used to create DOM text node 258 | * @return Node a DOM node with that text 259 | */ 260 | function createText(text) { 261 | return document.createTextNode(text); 262 | } 263 | 264 | /** 265 | * Utility function to escape html attribute text 266 | * @param string text use in HTML attribute 267 | * @return string text encoded to use in HTML attribute 268 | */ 269 | function escapeHTML(s) { 270 | return s.replace(rescaper, replacer); 271 | } 272 | 273 | /** 274 | * Default callback used to generate emoji src 275 | * based on Twitter CDN 276 | * @param string the emoji codepoint string 277 | * @param string the default size to use, i.e. "36x36" 278 | * @param string optional "\uFE0F" variant char, ignored by default 279 | * @return string the image source to use 280 | */ 281 | function defaultImageSrcGenerator(icon, options) { 282 | return ''.concat(options.base, options.size, '/', icon, options.ext); 283 | } 284 | 285 | /** 286 | * Given a generic DOM nodeType 1, walk through all children 287 | * and store every nodeType 3 (#text) found in the tree. 288 | * @param Element a DOM Element with probably some text in it 289 | * @param Array the list of previously discovered text nodes 290 | * @return Array same list with new discovered nodes, if any 291 | */ 292 | function grabAllTextNodes(node, allText) { 293 | var 294 | childNodes = node.childNodes, 295 | length = childNodes.length, 296 | subnode, 297 | nodeType; 298 | while (length--) { 299 | subnode = childNodes[length]; 300 | nodeType = subnode.nodeType; 301 | // parse emoji only in text nodes 302 | if (nodeType === 3) { 303 | // collect them to process emoji later 304 | allText.push(subnode); 305 | } 306 | // ignore all nodes that are not type 1 or that 307 | // should not be parsed as script, style, and others 308 | else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) { 309 | grabAllTextNodes(subnode, allText); 310 | } 311 | } 312 | return allText; 313 | } 314 | 315 | /** 316 | * Used to both remove the possible variant 317 | * and to convert utf16 into code points 318 | * @param string the emoji surrogate pair 319 | * @param string the optional variant char, if any 320 | */ 321 | function grabTheRightIcon(icon, variant) { 322 | // if variant is present as \uFE0F 323 | return toCodePoint( 324 | variant === '\uFE0F' ? 325 | // the icon should not contain it 326 | icon.slice(0, -1) : 327 | // fix non standard OSX behavior 328 | (icon.length === 3 && icon.charAt(1) === '\uFE0F' ? 329 | icon.charAt(0) + icon.charAt(2) : icon) 330 | ); 331 | } 332 | 333 | /** 334 | * DOM version of the same logic / parser: 335 | * emojify all found sub-text nodes placing images node instead. 336 | * @param Element generic DOM node with some text in some child node 337 | * @param Object options containing info about how to parse 338 | * 339 | * .callback Function the callback to invoke per each found emoji. 340 | * .base string the base url, by default twemoji.base 341 | * .ext string the image extension, by default twemoji.ext 342 | * .size string the assets size, by default twemoji.size 343 | * 344 | * @return Element same generic node with emoji in place, if any. 345 | */ 346 | function parseNode(node, options) { 347 | var 348 | allText = grabAllTextNodes(node, []), 349 | length = allText.length, 350 | attrib, 351 | attrname, 352 | modified, 353 | fragment, 354 | subnode, 355 | text, 356 | match, 357 | i, 358 | index, 359 | img, 360 | alt, 361 | icon, 362 | variant, 363 | src; 364 | while (length--) { 365 | modified = false; 366 | fragment = document.createDocumentFragment(); 367 | subnode = allText[length]; 368 | text = subnode.nodeValue; 369 | i = 0; 370 | while ((match = re.exec(text))) { 371 | index = match.index; 372 | if (index !== i) { 373 | fragment.appendChild( 374 | createText(text.slice(i, index)) 375 | ); 376 | } 377 | alt = match[0]; 378 | icon = match[1]; 379 | variant = match[2]; 380 | i = index + alt.length; 381 | if (variant !== '\uFE0E') { 382 | src = options.callback( 383 | grabTheRightIcon(icon, variant), 384 | options, 385 | variant 386 | ); 387 | if (src) { 388 | img = new Image(); 389 | img.onerror = options.onerror; 390 | img.setAttribute('draggable', 'false'); 391 | attrib = options.attributes(icon, variant); 392 | for (attrname in attrib) { 393 | if ( 394 | attrib.hasOwnProperty(attrname) && 395 | // don't allow any handlers to be set + don't allow overrides 396 | attrname.indexOf('on') !== 0 && 397 | !img.hasAttribute(attrname) 398 | ) { 399 | img.setAttribute(attrname, attrib[attrname]); 400 | } 401 | } 402 | img.className = options.className; 403 | img.alt = alt; 404 | img.src = src; 405 | modified = true; 406 | fragment.appendChild(img); 407 | } 408 | } 409 | if (!img) fragment.appendChild(createText(alt)); 410 | img = null; 411 | } 412 | // is there actually anything to replace in here ? 413 | if (modified) { 414 | // any text left to be added ? 415 | if (i < text.length) { 416 | fragment.appendChild( 417 | createText(text.slice(i)) 418 | ); 419 | } 420 | // replace the text node only, leave intact 421 | // anything else surrounding such text 422 | subnode.parentNode.replaceChild(fragment, subnode); 423 | } 424 | } 425 | return node; 426 | } 427 | 428 | /** 429 | * String/HTML version of the same logic / parser: 430 | * emojify a generic text placing images tags instead of surrogates pair. 431 | * @param string generic string with possibly some emoji in it 432 | * @param Object options containing info about how to parse 433 | * 434 | * .callback Function the callback to invoke per each found emoji. 435 | * .base string the base url, by default twemoji.base 436 | * .ext string the image extension, by default twemoji.ext 437 | * .size string the assets size, by default twemoji.size 438 | * 439 | * @return the string with replacing all found and parsed emoji 440 | */ 441 | function parseString(str, options) { 442 | return replace(str, function (match, icon, variant) { 443 | var 444 | ret = match, 445 | attrib, 446 | attrname, 447 | src; 448 | // verify the variant is not the FE0E one 449 | // this variant means "emoji as text" and should not 450 | // require any action/replacement 451 | // http://unicode.org/Public/UNIDATA/StandardizedVariants.html 452 | if (variant !== '\uFE0E') { 453 | src = options.callback( 454 | grabTheRightIcon(icon, variant), 455 | options, 456 | variant 457 | ); 458 | if (src) { 459 | // recycle the match string replacing the emoji 460 | // with its image counter part 461 | ret = ''); 485 | } 486 | } 487 | return ret; 488 | }); 489 | } 490 | 491 | /** 492 | * Function used to actually replace HTML special chars 493 | * @param string HTML special char 494 | * @return string encoded HTML special char 495 | */ 496 | function replacer(m) { 497 | return escaper[m]; 498 | } 499 | 500 | /** 501 | * Default options.attribute callback 502 | * @return null 503 | */ 504 | function returnNull() { 505 | return null; 506 | } 507 | 508 | /** 509 | * Given a generic value, creates its squared counterpart if it's a number. 510 | * As example, number 36 will return '36x36'. 511 | * @param any a generic value. 512 | * @return any a string representing asset size, i.e. "36x36" 513 | * only in case the value was a number. 514 | * Returns initial value otherwise. 515 | */ 516 | function toSizeSquaredAsset(value) { 517 | return typeof value === 'number' ? 518 | value + 'x' + value : 519 | value; 520 | } 521 | 522 | 523 | ///////////////////////// 524 | // exported functions // 525 | // declaration // 526 | ///////////////////////// 527 | 528 | function fromCodePoint(codepoint) { 529 | var code = typeof codepoint === 'string' ? 530 | parseInt(codepoint, 16) : codepoint; 531 | if (code < 0x10000) { 532 | return fromCharCode(code); 533 | } 534 | code -= 0x10000; 535 | return fromCharCode( 536 | 0xD800 + (code >> 10), 537 | 0xDC00 + (code & 0x3FF) 538 | ); 539 | } 540 | 541 | function parse(what, how) { 542 | if (!how || typeof how === 'function') { 543 | how = {callback: how}; 544 | } 545 | // if first argument is string, inject html tags 546 | // otherwise use the DOM tree and parse text nodes only 547 | return (typeof what === 'string' ? parseString : parseNode)(what, { 548 | callback: how.callback || defaultImageSrcGenerator, 549 | attributes: typeof how.attributes === 'function' ? how.attributes : returnNull, 550 | base: typeof how.base === 'string' ? how.base : twemoji.base, 551 | ext: how.ext || twemoji.ext, 552 | size: how.folder || toSizeSquaredAsset(how.size || twemoji.size), 553 | className: how.className || twemoji.className, 554 | onerror: how.onerror || twemoji.onerror 555 | }); 556 | } 557 | 558 | function replace(text, callback) { 559 | return String(text).replace(re, callback); 560 | } 561 | 562 | function test(text) { 563 | // IE6 needs a reset before too 564 | re.lastIndex = 0; 565 | var result = re.test(text); 566 | re.lastIndex = 0; 567 | return result; 568 | } 569 | 570 | function toCodePoint(unicodeSurrogates, sep) { 571 | var 572 | r = [], 573 | c = 0, 574 | p = 0, 575 | i = 0; 576 | while (i < unicodeSurrogates.length) { 577 | c = unicodeSurrogates.charCodeAt(i++); 578 | if (p) { 579 | r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16)); 580 | p = 0; 581 | } else if (0xD800 <= c && c <= 0xDBFF) { 582 | p = c; 583 | } else { 584 | r.push(c.toString(16)); 585 | } 586 | } 587 | return r.join(sep || '-'); 588 | } 589 | 590 | }()); -------------------------------------------------------------------------------- /static/assets/twemoji.min.js: -------------------------------------------------------------------------------- 1 | /*! Copyright Twitter Inc. and other contributors. Licensed under MIT */ 2 | var twemoji=function(){"use strict";var twemoji={base:(location.protocol==="https:"?"https:":"http:")+"//twemoji.maxcdn.com/",ext:".png",size:"36x36",className:"emoji",convert:{fromCodePoint:fromCodePoint,toCodePoint:toCodePoint},onerror:function onerror(){if(this.parentNode){this.parentNode.replaceChild(createText(this.alt),this)}},parse:parse,replace:replace,test:test},escaper={"&":"&","<":"<",">":">","'":"'",'"':"""},re=/((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd7f|\ud83c\ude02|\ud83c\ude1a|\ud83c\ude2f|\ud83c\ude37|\u3299|\u303d|\u3030|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2122|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g,rescaper=/[&<>'"]/g,shouldntBeParsed=/IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,fromCharCode=String.fromCharCode;return twemoji;function createText(text){return document.createTextNode(text)}function escapeHTML(s){return s.replace(rescaper,replacer)}function defaultImageSrcGenerator(icon,options){return"".concat(options.base,options.size,"/",icon,options.ext)}function grabAllTextNodes(node,allText){var childNodes=node.childNodes,length=childNodes.length,subnode,nodeType;while(length--){subnode=childNodes[length];nodeType=subnode.nodeType;if(nodeType===3){allText.push(subnode)}else if(nodeType===1&&!shouldntBeParsed.test(subnode.nodeName)){grabAllTextNodes(subnode,allText)}}return allText}function grabTheRightIcon(icon,variant){return toCodePoint(variant==="️"?icon.slice(0,-1):icon.length===3&&icon.charAt(1)==="️"?icon.charAt(0)+icon.charAt(2):icon)}function parseNode(node,options){var allText=grabAllTextNodes(node,[]),length=allText.length,attrib,attrname,modified,fragment,subnode,text,match,i,index,img,alt,icon,variant,src;while(length--){modified=false;fragment=document.createDocumentFragment();subnode=allText[length];text=subnode.nodeValue;i=0;while(match=re.exec(text)){index=match.index;if(index!==i){fragment.appendChild(createText(text.slice(i,index)))}alt=match[0];icon=match[1];variant=match[2];i=index+alt.length;if(variant!=="︎"){src=options.callback(grabTheRightIcon(icon,variant),options,variant);if(src){img=new Image;img.onerror=options.onerror;img.setAttribute("draggable","false");attrib=options.attributes(icon,variant);for(attrname in attrib){if(attrib.hasOwnProperty(attrname)&&attrname.indexOf("on")!==0&&!img.hasAttribute(attrname)){img.setAttribute(attrname,attrib[attrname])}}img.className=options.className;img.alt=alt;img.src=src;modified=true;fragment.appendChild(img)}}if(!img)fragment.appendChild(createText(alt));img=null}if(modified){if(i")}}return ret})}function replacer(m){return escaper[m]}function returnNull(){return null}function toSizeSquaredAsset(value){return typeof value==="number"?value+"x"+value:value}function fromCodePoint(codepoint){var code=typeof codepoint==="string"?parseInt(codepoint,16):codepoint;if(code<65536){return fromCharCode(code)}code-=65536;return fromCharCode(55296+(code>>10),56320+(code&1023))}function parse(what,how){if(!how||typeof how==="function"){how={callback:how}}return(typeof what==="string"?parseString:parseNode)(what,{callback:how.callback||defaultImageSrcGenerator,attributes:typeof how.attributes==="function"?how.attributes:returnNull,base:typeof how.base==="string"?how.base:twemoji.base,ext:how.ext||twemoji.ext,size:how.folder||toSizeSquaredAsset(how.size||twemoji.size),className:how.className||twemoji.className,onerror:how.onerror||twemoji.onerror})}function replace(text,callback){return String(text).replace(re,callback)}function test(text){re.lastIndex=0;var result=re.test(text);re.lastIndex=0;return result}function toCodePoint(unicodeSurrogates,sep){var r=[],c=0,p=0,i=0;while(i 2 | 3 | {{t}} 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 |

{{ h }}

19 | 25 |
26 |

Credits :

27 |
28 | Prashant Shahi 29 |

30 | Email: 31 | me@prashantshahi.dev 32 |

33 |
34 |
35 |

Made with ❤️ from Karnali, Nepal ☕

36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{t}} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

{{ h }}

12 | 18 | {% if error %} 19 |
20 | Error! {{ error }} 21 |
22 | {% endif %} 23 |
24 | {% if todos[0] %} 25 |
26 |
27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 41 |
Search Reference:
42 |
43 |
44 | To-Do LIST : 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {% for todo in todos %} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {% endfor %} 66 |
StatusTask NameDescription NameDatePriorityRemoveModify
{{ todo["name"] }}{{ todo["desc"] }}{{ todo["date"] }}{{ todo["pr"] }}🗑️📝
67 | {% else %} 68 |
69 |

No Tasks in the List !!

70 |
71 | {% endif %} 72 |
73 |
74 | 75 | 76 | 79 | 80 | 81 | 84 | 85 | 86 | 89 | 90 | 91 | 94 | 95 | 96 | 104 | 105 | 106 | 109 | 110 |
77 |

Add a Task

78 |
82 | 83 |
87 | 88 |
92 | 93 |
97 | 103 |
107 | 108 |
111 |
112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /templates/searchlist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{t}} 4 | 5 | 6 | 7 | 8 | 9 | 10 |

{{ h }}

11 | 17 |
18 | {% if todos[0] %} 19 |

Result of the Search : ToDO List

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for todo in todos %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% endfor %} 41 | {% else %} 42 |

No Result Found !!

43 | {% endif %} 44 |
StatusTask NameTask DescriptionDateProjectDeleteModify
{{ todo["name"] }}{{ todo["desc"] }}{{ todo["date"] }}{{ todo["pr"] }}
45 | 46 | 47 | -------------------------------------------------------------------------------- /templates/update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{t}} 4 | 5 | 6 | 7 | 8 | 19 | 20 | 21 |

{{ h }}

22 | 28 |
29 |

Update tasks with a reference

30 |
31 | {% for task in tasks %} 32 |

Unique ID: {{ task['_id'] }}

33 | 34 | 35 |

36 | Task Name: 37 | 38 |

39 |

40 | Description: 41 | 42 |

43 |

44 | Date: 45 | 46 |

47 |

48 | Priority: 49 | 55 |

56 | 57 | {% endfor %} 58 | 59 |
60 |
61 | 62 | 63 | --------------------------------------------------------------------------------