diff --git a/code/Dockerfile b/code/Dockerfile new file mode 100644 index 0000000..2fe02ae --- /dev/null +++ b/code/Dockerfile @@ -0,0 +1,39 @@ +# For more information, please refer to https://aka.ms/vscode-docker-python +FROM python:3.9-alpine as compile-image +# Install pip requirements +COPY requirements.txt . +RUN apk add --virtual .build-deps gcc libffi-dev musl-dev +RUN pip install --user -r requirements.txt + + + +FROM python:3.9-alpine + +# add nginx +EXPOSE 80 +RUN apk add --no-cache nginx +RUN adduser -D -g 'www' www +RUN mkdir /www +RUN chown -R www:www /var/lib/nginx +RUN chown -R www:www /www +RUN mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig +COPY settings/nginx.conf /etc/nginx/nginx.conf + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED=1 + +COPY --from=compile-image /root/.local /root/.local +ENV PATH=/root/.local/bin:$PATH +WORKDIR /app +COPY . /app + +# Creates a non-root user with an explicit UID and adds permission to access the /app folder +# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers +#RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app +#USER appuser + +# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug +RUN chmod +x /app/runserver.sh +CMD ["/bin/sh", "/app/runserver.sh"] \ No newline at end of file diff --git a/code/joplin_utils.nim b/code/joplin_utils.nim index 26deea1..468afc4 100644 --- a/code/joplin_utils.nim +++ b/code/joplin_utils.nim @@ -6,6 +6,7 @@ # joplin_utils : procedure related joplin application # # --==--==--==--==--==--==--==--==--==--==--==--==--==--==--==-- # +import os import net import times import osproc @@ -15,8 +16,11 @@ import httpcore import strutils import strformat import std/options +import std/httpclient +import std/asyncdispatch + from os import sleep -import std/[asyncdispatch, httpclient] +from posix import read, write, fdatasync, close # --==--==--==--==--==--==--==--==--==--==-- # # TYPE : Setup joplin_ping data @@ -56,6 +60,15 @@ type latitude*, longitude*, altitude*, order*:seq[float] req*: Request +# --==--==--==--==--==--==--==--==--==--==-- # +# TYPE : Setup process data structure +# --==--==--==--==--==--==--==--==--==--==-- # +type + program* = object + pid*: int + stdout*: string + stderr*: string + # --==--==--==--==--==--==--==--==--==--==-- # # DURATION : duration variables # --==--==--==--==--==--==--==--==--==--==-- # @@ -281,20 +294,35 @@ proc get_joplin_cli_token*(): string = return flagValue +# --==--==--==--==--==--==--==--==--==--==-- # +# PROC : launch any program and get PID +# --==--==--==--==--==--==--==--==--==--==-- # +proc launchProgram(app:string = "", workingPath:string = "", arguments:array[2, string]):int {.thread.} = + + var p = startProcess(joinPath(workingPath,app), workingPath, arguments) + let pid = p.processID() + var outhdl = outputHandle(p) + var inputhdl = inputHandle(p) + var errhdl = errorHandle(p) + return pid # --==--==--==--==--==--==--==--==--==--==-- # -# PROC : start Joplin Terminal +# PROC : Start joplin cli # --==--==--==--==--==--==--==--==--==--==-- # -proc joplin_cli_start*(): bool = - var rc = false - var result = execCmdEx("joplin server start &") +proc joplin_cli_start*():int {.thread.} = + + let joplinProcessId = launchProgram("joplin","/usr/bin/",["server","start"]) + return joplinProcessId + + +# --==--==--==--==--==--==--==--==--==--==-- # +# PROC : stop Joplin Terminal +# --==--==--==--==--==--==--==--==--==--==-- # +proc joplin_cli_stop*():int {.thread.} = + let joplinProcessId = launchProgram("joplin","/usr/bin/",["server","stop"]) + return joplinProcessId + - if result.exitCode == 0: - # echo result.output - rc = true - else: - rc = false - return rc # --==--==--==--==--==--==--==--==--==--==-- # @@ -305,26 +333,49 @@ proc joplin_cli_status*(): bool = var result = execCmdEx("joplin server status") if result.exitCode == 0: - # echo result.output - rc = true + if "Server is not running" in result.output : + echo "Joplin Terminal cli status is down : ", result.output + rc = false + else: + echo "Joplin Terminal cli status is up : ", result.output + rc = true else: - rc = false + echo "Error validate joplin terminal status : ", result.output return rc +# --==--==--==--==--==--==--==--==--==--==-- # +# PROC : start Joplin Terminal +# --==--==--==--==--==--==--==--==--==--==-- # +# proc joplin_cli_start*(): bool = +# var rc = false +# startProcess("joplin","/usr/bin", @["server", "start"]) +# #var result = execCmdEx("ls -l /usr/bin/joplin") + +# if joplin_cli_status() == true: +# echo "Joplin Terminal started successfully : ",result.output +# rc = true +# else: +# echo "Joplin Terminal didn't start : ",result.output +# rc = false +# return rc + + + + # --==--==--==--==--==--==--==--==--==--==-- # # PROC : stop Joplin Terminal # --==--==--==--==--==--==--==--==--==--==-- # -proc joplin_cli_stop*(): bool = - var rc = false - var result = execCmdEx("joplin server stop") +# proc joplin_cli_stop*(): bool = +# var rc = false +# var result = execCmdEx("joplin server stop") - if result.exitCode == 0: - # echo result.output - rc = true - else: - rc = false - return rc +# if result.exitCode == 0: +# # echo result.output +# rc = true +# else: +# rc = false +# return rc # --==--==--==--==--==--==--==--==--==--==-- # diff --git a/main.nim b/main.nim index 4e8ad8c..11bdedb 100644 --- a/main.nim +++ b/main.nim @@ -73,6 +73,7 @@ type TData* = ref object of RootObj loggedIn*: bool userid, username*, userpass*, email*: string + notification*: int req*: Request # --==--==--==--==--==--==--==--==--==--==-- # @@ -84,7 +85,9 @@ proc init(c: var TData, cld: var ColomnLeftData) = c.username = "" c.userid = "" c.loggedIn = false + c.notification = 0 + ## default option # cld.option = notes @@ -274,6 +277,18 @@ routes: echo @"msg" if c.loggedIn: + # Start joplin terminal cli if stropped + if @"msg" == "startStopJoplin": + if joplin_cli_status() == false: + var isStart = joplin_cli_start() + echo "Joplin client Terminal started: ",isStart + # echo joplin_cli_status() + if joplin_cli_status() == true: + var isStart = joplin_cli_stop() + echo "Joplin client Terminal stopped: ",isStart + # echo joplin_cli_status() + redirect("/secret") + # if Joplin application work var checkJoplin = waitFor ping_joplin(joplin_token) if checkJoplin.ping_status: @@ -342,6 +357,15 @@ routes: logout(c) redirect("/") + # start_joplin route + # --==--==--==--==--==--==--==--==--==--==-- # + # post "/start_joplin": + # if joplin_cli_status() == false: + # joplin_cli_start() + + + + # --==--==--==--==--==--==--==--==--==--==-- # # # ROUTES TESTS SECTION ## # --==--==--==--==--==--==--==--==--==--==-- # diff --git a/public/css/alertify-notif.css b/public/css/alertify-notif.css new file mode 100755 index 0000000..039c1a5 --- /dev/null +++ b/public/css/alertify-notif.css @@ -0,0 +1,263 @@ +.alertify, +.alertify-show, +.alertify-log { + -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + transition: all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + /* easeOutBack */ +} + +.alertify-hide { + -webkit-transition: all 250ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + -moz-transition: all 250ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + -ms-transition: all 250ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + -o-transition: all 250ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + transition: all 250ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + /* easeInBack */ +} + +.alertify-log-hide { + -webkit-transition: all 500ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + -moz-transition: all 500ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + -ms-transition: all 500ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + -o-transition: all 500ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + transition: all 500ms cubic-bezier(0.6, -0.28, 0.735, 0.045); + /* easeInBack */ +} + +.alertify-cover { + position: fixed; + z-index: 99999; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + filter: alpha(opacity=0); + opacity: 0; +} + +.alertify-cover-hidden { + display: none; +} + +.alertify { + position: fixed; + z-index: 99999; + top: 50px; + left: 50%; + width: 550px; + margin-left: -275px; + opacity: 1; +} + +.alertify-hidden { + -webkit-transform: translate(0, -150px); + -moz-transform: translate(0, -150px); + -ms-transform: translate(0, -150px); + -o-transform: translate(0, -150px); + transform: translate(0, -150px); + opacity: 0; + display: none; +} + +/* overwrite display: none; for everything except IE6-8 */ +:root * > .alertify-hidden { + display: block; + visibility: hidden; +} + +.alertify-logs { + position: fixed; + z-index: 5000; + bottom: 10px; + right: 10px; + width: 300px; +} + +.alertify-logs-hidden { + display: none; +} + +.alertify-log { + display: block; + margin-top: 10px; + position: relative; + right: -300px; + opacity: 0; +} + +.alertify-log-show { + right: 0; + opacity: 1; +} + +.alertify-log-hide { + -webkit-transform: translate(300px, 0); + -moz-transform: translate(300px, 0); + -ms-transform: translate(300px, 0); + -o-transform: translate(300px, 0); + transform: translate(300px, 0); + opacity: 0; +} + +.alertify-dialog { + padding: 25px; +} + +.alertify-resetFocus { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.alertify-inner { + text-align: center; +} + +.alertify-text { + margin-bottom: 15px; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 100%; +} + +.alertify-button, +.alertify-button:hover, +.alertify-button:active, +.alertify-button:visited { + background: none; + text-decoration: none; + border: none; + /* line-height and font-size for input button */ + line-height: 1.5; + font-size: 100%; + display: inline-block; + cursor: pointer; + margin-left: 5px; +} + +@media only screen and (max-width: 680px) { + .alertify, + .alertify-logs { + width: 90%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + .alertify { + left: 5%; + margin: 0; + } +} +/** + * Default Look and Feel + */ +.alertify, +.alertify-log { + font-family: sans-serif; +} + +.alertify { + background: #FFF; + border: 10px solid #333; + /* browsers that don't support rgba */ + border: 10px solid rgba(0, 0, 0, 0.7); + border-radius: 8px; + box-shadow: 0 3px 3px rgba(0, 0, 0, 0.3); + -webkit-background-clip: padding; + /* Safari 4? Chrome 6? */ + -moz-background-clip: padding; + /* Firefox 3.6 */ + background-clip: padding-box; + /* Firefox 4, Safari 5, Opera 10, IE 9 */ +} + +.alertify-text { + border: 1px solid #CCC; + padding: 10px; + border-radius: 4px; +} + +.alertify-button { + border-radius: 4px; + color: #FFF; + font-weight: bold; + padding: 6px 15px; + text-decoration: none; + text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5); + box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.5); + background-image: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)); + background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)); + background-image: -ms-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)); + background-image: -o-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)); + background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)); +} + +.alertify-button:hover, +.alertify-button:focus { + outline: none; + background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.1), transparent); + background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0.1), transparent); + background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0.1), transparent); + background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0.1), transparent); + background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0)); +} + +.alertify-button:focus { + box-shadow: 0 0 15px #2B72D5; +} + +.alertify-button:active { + position: relative; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.alertify-button-cancel, +.alertify-button-cancel:hover, +.alertify-button-cancel:focus { + background-color: #FE1A00; + border: 1px solid #D83526; +} + +.alertify-button-ok, +.alertify-button-ok:hover, +.alertify-button-ok:focus { + background-color: #5CB811; + border: 1px solid #3B7808; +} + +.alertify-log { + background: #1F1F1F; + background: rgba(0, 0, 0, 0.9); + padding: 15px; + border-radius: 4px; + color: #FFF; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5); +} + +.alertify-log-error { + background: #FE1A00; + background: rgba(254, 26, 0, 0.9); +} + +.alertify-log-success { + background: #5CB811; + background: rgba(92, 184, 17, 0.9); +} + +/* button { + min-width: 300px; + outline: none; +} */ + diff --git a/public/css/notif-bell.css b/public/css/notif-bell.css new file mode 100755 index 0000000..ed00dfc --- /dev/null +++ b/public/css/notif-bell.css @@ -0,0 +1,182 @@ +/* * { + margin: 0; + padding: 0; +} +html { + line-height: 1.2; + box-sizing: border-box; +} +*, +*::after, +*::before { + box-sizing: inherit; +} +html, +body { + height: 100%; +} +body { + position: relative; + z-index: 0; + font-family: monospace, arial, sans-serif; + font-size: 16px; + background: linear-gradient(#160731 0%,#1B293C 100%); + color: #333; + overflow: hidden; +} */ +/* .my-moon { + width: 120px; + height: 120px; + background-color: #fff; + border-radius: 50%; + box-shadow: 0 0 0 10px rgba(255,255,255,.04), + 0 0 0 20px rgba(255,255,255,.04), + 0 0 0 30px rgba(255,255,255,.04), + 0 0 50px 50px rgba(255,255,255,.02), + 0 0 100px 100px rgba(255,255,255,.02); + -webkit-animation: moon-moving 30s both infinite; + animation: moon-moving 30s both infinite; +} */ +.notification-box { + position: fixed; + z-index: 99; + top: 15px; + right: 30px; + width: 40px; + height: 40px; + text-align: center; +} +.notification-bell { + -webkit-animation: bell 1s 1s both infinite; + animation: bell 1s 1s both infinite; +} +.notification-bell * { + display: block; + margin: 0 auto; + /* background-color: #fff; + box-shadow: 0px 0px 15px #fff; */ + background-color: rgb(9, 9, 9); + box-shadow: 0px 0px 15px rgb(9, 9, 9); +} +.bell-top { + width: 6px; + height: 6px; + border-radius: 3px 3px 0 0; +} +.bell-middle { + width: 25px; + height: 25px; + margin-top: -1px; + border-radius: 12.5px 12.5px 0 0; +} +.bell-bottom { + position: relative; + z-index: 0; + width: 32px; + height: 2px; +} +.bell-bottom::before, +.bell-bottom::after { + content: ''; + position: absolute; + top: -4px; +} +.bell-bottom::before { + left: 1px; + /* border-bottom: 4px solid #fff; */ + border-bottom: 4px solid rgb(9, 9, 9); + border-right: 0 solid transparent; + border-left: 4px solid transparent; +} +.bell-bottom::after { + right: 1px; + /* border-bottom: 4px solid #fff; */ + border-bottom: 4px solid rgb(9, 9, 9); + border-right: 4px solid transparent; + border-left: 0 solid transparent; +} +.bell-rad { + width: 8px; + height: 4px; + margin-top: 2px; + border-radius: 0 0 4px 4px; + -webkit-animation: rad 1s 2s both infinite; + animation: rad 1s 2s both infinite; +} +.notification-count { + position: absolute; + z-index: 1; + top: -6px; + right: -6px; + width: 25px; + height: 25px; + line-height: 25px; + font-size: 15px; + border-radius: 50%; + background-color: #ff4927; + color: #fff; + -webkit-animation: zoom 3s 3s both infinite; + animation: zoom 3s 3s both infinite; +} +@-webkit-keyframes bell { + 0% { transform: rotate(0); } + 10% { transform: rotate(30deg); } + 20% { transform: rotate(0); } + 80% { transform: rotate(0); } + 90% { transform: rotate(-30deg); } + 100% { transform: rotate(0); } +} +@keyframes bell { + 0% { transform: rotate(0); } + 10% { transform: rotate(30deg); } + 20% { transform: rotate(0); } + 80% { transform: rotate(0); } + 90% { transform: rotate(-30deg); } + 100% { transform: rotate(0); } +} +@-webkit-keyframes rad { + 0% { transform: translateX(0); } + 10% { transform: translateX(6px); } + 20% { transform: translateX(0); } + 80% { transform: translateX(0); } + 90% { transform: translateX(-6px); } + 100% { transform: translateX(0); } +} +@keyframes rad { + 0% { transform: translateX(0); } + 10% { transform: translateX(6px); } + 20% { transform: translateX(0); } + 80% { transform: translateX(0); } + 90% { transform: translateX(-6px); } + 100% { transform: translateX(0); } +} +@-webkit-keyframes zoom { + 0% { opacity: 0; transform: scale(0); } + 10% { opacity: 1; transform: scale(1); } + 50% { opacity: 1; } + 51% { opacity: 0; } + 100% { opacity: 0; } +} +@keyframes zoom { + 0% { opacity: 0; transform: scale(0); } + 10% { opacity: 1; transform: scale(1); } + 50% { opacity: 1; } + 51% { opacity: 0; } + 100% { opacity: 0; } +} +@-webkit-keyframes moon-moving { + 0% { + transform: translate(-200%, 600%); + } + 100% { + transform: translate(800%, -200%); + } +} +@keyframes moon-moving { + 0% { + transform: translate(-200%, 600%); + } + 100% { + transform: translate(800%, -200%); + } +} \ No newline at end of file diff --git a/public/css/popupnotif.css b/public/css/popupnotif.css new file mode 100644 index 0000000..a0c56f8 --- /dev/null +++ b/public/css/popupnotif.css @@ -0,0 +1,207 @@ +.alert>.start-icon { + margin-right: 0; + min-width: 20px; + text-align: center; +} + +.alert>.start-icon { + margin-right: 5px; +} + +.greencross +{ + font-size:18px; + color: #25ff0b; + text-shadow: none; +} + +.alert-simple.alert-success +{ + border: 1px solid rgba(36, 241, 6, 0.46); + background-color: rgba(7, 149, 66, 0.12156862745098039); + box-shadow: 0px 0px 2px #259c08; + color: #0ad406; + text-shadow: 2px 1px #00040a; + transition:0.5s; + cursor:pointer; +} +.alert-success:hover{ + background-color: rgba(7, 149, 66, 0.35); + transition:0.5s; +} +.alert-simple.alert-info +{ + border: 1px solid rgba(6, 44, 241, 0.46); + background-color: rgba(7, 73, 149, 0.12156862745098039); + box-shadow: 0px 0px 2px #0396ff; + color: #0396ff; + text-shadow: 2px 1px #00040a; + transition:0.5s; + cursor:pointer; +} + +.alert-info:hover +{ + background-color: rgba(7, 73, 149, 0.35); + transition:0.5s; +} + +.blue-cross +{ + font-size: 18px; + color: #0bd2ff; + text-shadow: none; +} + +.alert-simple.alert-warning +{ + border: 1px solid rgba(241, 142, 6, 0.81); + background-color: rgba(220, 128, 1, 0.16); + box-shadow: 0px 0px 2px #ffb103; + color: #ffb103; + text-shadow: 2px 1px #00040a; + transition:0.5s; + cursor:pointer; +} + +.alert-warning:hover{ + background-color: rgba(220, 128, 1, 0.33); + transition:0.5s; +} + +.warning +{ + font-size: 18px; + color: #ffb40b; + text-shadow: none; +} + +.alert-simple.alert-danger +{ + border: 1px solid rgba(241, 6, 6, 0.81); + background-color: rgba(220, 17, 1, 0.16); + box-shadow: 0px 0px 2px #ff0303; + color: #ff0303; + text-shadow: 2px 1px #00040a; + transition:0.5s; + cursor:pointer; +} + +.alert-danger:hover +{ + background-color: rgba(220, 17, 1, 0.33); + transition:0.5s; +} + +.danger +{ + font-size: 18px; + color: #ff0303; + text-shadow: none; +} + +.alert-simple.alert-primary +{ + border: 1px solid rgba(6, 241, 226, 0.81); + background-color: rgba(1, 204, 220, 0.16); + box-shadow: 0px 0px 2px #03fff5; + color: #03d0ff; + text-shadow: 2px 1px #00040a; + transition:0.5s; + cursor:pointer; +} + +.alert-primary:hover{ + background-color: rgba(1, 204, 220, 0.33); + transition:0.5s; +} + +.alertprimary +{ + font-size: 18px; + color: #03d0ff; + text-shadow: none; +} + +.square_box { + position: absolute; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + border-top-left-radius: 45px; + opacity: 0.302; +} + +.square_box.box_three { + background-image: -moz-linear-gradient(-90deg, #290a59 0%, #3d57f4 100%); + background-image: -webkit-linear-gradient(-90deg, #290a59 0%, #3d57f4 100%); + background-image: -ms-linear-gradient(-90deg, #290a59 0%, #3d57f4 100%); + opacity: 0.059; + left: -80px; + top: -60px; + width: 500px; + height: 500px; + border-radius: 45px; +} + +.square_box.box_four { + background-image: -moz-linear-gradient(-90deg, #290a59 0%, #3d57f4 100%); + background-image: -webkit-linear-gradient(-90deg, #290a59 0%, #3d57f4 100%); + background-image: -ms-linear-gradient(-90deg, #290a59 0%, #3d57f4 100%); + opacity: 0.059; + left: 150px; + top: -25px; + width: 550px; + height: 550px; + border-radius: 45px; +} + +.alert:before { + content: ''; + position: absolute; + width: 0; + height: calc(100% - 44px); + border-left: 1px solid; + border-right: 2px solid; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; + left: 0; + top: 50%; + transform: translate(0,-50%); + height: 20px; +} + +.fa-times +{ +-webkit-animation: blink-1 2s infinite both; + animation: blink-1 2s infinite both; +} + + +/** + * ---------------------------------------- + * animation blink-1 + * ---------------------------------------- + */ +@-webkit-keyframes blink-1 { + 0%, + 50%, + 100% { + opacity: 1; + } + 25%, + 75% { + opacity: 0; + } +} +@keyframes blink-1 { + 0%, + 50%, + 100% { + opacity: 1; + } + 25%, + 75% { + opacity: 0; + } +} diff --git a/public/css/styles.css b/public/css/styles.css index e41d00a..bd7dcd5 100755 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -58,7 +58,7 @@ a { --animation-timing-curve: ease-in-out; --blue-joplin-color: #0053b8; --light-blue-joplin-color: rgb(237, 241, 243); - --header-height: 50px; + --header-height: 55px; } .header { @@ -129,6 +129,12 @@ a { padding: 0; } +.notifBell-btn { + background: none; + border: none; + padding: 0; +} + .menu-icon { width: 25px; height: 25px; diff --git a/public/js/alertify_notif.js b/public/js/alertify_notif.js new file mode 100755 index 0000000..f6b09ca --- /dev/null +++ b/public/js/alertify_notif.js @@ -0,0 +1,639 @@ +$(document).foundation(); + +/*global define*/ +(function (global, undefined) { + "use strict"; + + var document = global.document, + Alertify; + + Alertify = function () { + + var _alertify = {}, + dialogs = {}, + isopen = false, + keys = { ENTER: 13, ESC: 27, SPACE: 32 }, + queue = [], + $, btnCancel, btnOK, btnReset, btnResetBack, btnFocus, elCallee, elCover, elDialog, elLog, form, input, getTransitionEvent; + + /** + * Markup pieces + * @type {Object} + */ + dialogs = { + buttons : { + holder : "", + submit : "", + ok : "", + cancel : "" + }, + input : "
", + message : "

{{message}}

", + log : "
{{message}}
" + }; + + /** + * Return the proper transitionend event + * @return {String} Transition type string + */ + getTransitionEvent = function () { + var t, + type, + supported = false, + el = document.createElement("fakeelement"), + transitions = { + "WebkitTransition" : "webkitTransitionEnd", + "MozTransition" : "transitionend", + "OTransition" : "otransitionend", + "transition" : "transitionend" + }; + + for (t in transitions) { + if (el.style[t] !== undefined) { + type = transitions[t]; + supported = true; + break; + } + } + + return { + type : type, + supported : supported + }; + }; + + /** + * Shorthand for document.getElementById() + * + * @param {String} id A specific element ID + * @return {Object} HTML element + */ + $ = function (id) { + return document.getElementById(id); + }; + + /** + * Alertify private object + * @type {Object} + */ + _alertify = { + + /** + * Labels object + * @type {Object} + */ + labels : { + ok : "OK", + cancel : "Cancel" + }, + + /** + * Delay number + * @type {Number} + */ + delay : 5000, + + /** + * Whether buttons are reversed (default is secondary/primary) + * @type {Boolean} + */ + buttonReverse : false, + + /** + * Which button should be focused by default + * @type {String} "ok" (default), "cancel", or "none" + */ + buttonFocus : "ok", + + /** + * Set the transition event on load + * @type {[type]} + */ + transition : undefined, + + /** + * Set the proper button click events + * + * @param {Function} fn [Optional] Callback function + * + * @return {undefined} + */ + addListeners : function (fn) { + var hasOK = (typeof btnOK !== "undefined"), + hasCancel = (typeof btnCancel !== "undefined"), + hasInput = (typeof input !== "undefined"), + val = "", + self = this, + ok, cancel, common, key, reset; + + // ok event handler + ok = function (event) { + if (typeof event.preventDefault !== "undefined") event.preventDefault(); + common(event); + if (typeof input !== "undefined") val = input.value; + if (typeof fn === "function") { + if (typeof input !== "undefined") { + fn(true, val); + } + else fn(true); + } + return false; + }; + + // cancel event handler + cancel = function (event) { + if (typeof event.preventDefault !== "undefined") event.preventDefault(); + common(event); + if (typeof fn === "function") fn(false); + return false; + }; + + // common event handler (keyup, ok and cancel) + common = function (event) { + self.hide(); + self.unbind(document.body, "keyup", key); + self.unbind(btnReset, "focus", reset); + if (hasOK) self.unbind(btnOK, "click", ok); + if (hasCancel) self.unbind(btnCancel, "click", cancel); + }; + + // keyup handler + key = function (event) { + var keyCode = event.keyCode; + if ((keyCode === keys.SPACE && !hasInput) || (hasInput && keyCode === keys.ENTER)) ok(event); + if (keyCode === keys.ESC && hasCancel) cancel(event); + }; + + // reset focus to first item in the dialog + reset = function (event) { + if (hasInput) input.focus(); + else if (!hasCancel || self.buttonReverse) btnOK.focus(); + else btnCancel.focus(); + }; + + // handle reset focus link + // this ensures that the keyboard focus does not + // ever leave the dialog box until an action has + // been taken + this.bind(btnReset, "focus", reset); + this.bind(btnResetBack, "focus", reset); + // handle OK click + if (hasOK) this.bind(btnOK, "click", ok); + // handle Cancel click + if (hasCancel) this.bind(btnCancel, "click", cancel); + // listen for keys, Cancel => ESC + this.bind(document.body, "keyup", key); + if (!this.transition.supported) { + this.setFocus(); + } + }, + + /** + * Bind events to elements + * + * @param {Object} el HTML Object + * @param {Event} event Event to attach to element + * @param {Function} fn Callback function + * + * @return {undefined} + */ + bind : function (el, event, fn) { + if (typeof el.addEventListener === "function") { + el.addEventListener(event, fn, false); + } else if (el.attachEvent) { + el.attachEvent("on" + event, fn); + } + }, + + /** + * Use alertify as the global error handler (using window.onerror) + * + * @return {boolean} success + */ + handleErrors : function () { + if (typeof global.onerror !== "undefined") { + var self = this; + global.onerror = function (msg, url, line) { + self.error("[" + msg + " on line " + line + " of " + url + "]", 0); + }; + return true; + } else { + return false; + } + }, + + /** + * Append button HTML strings + * + * @param {String} secondary The secondary button HTML string + * @param {String} primary The primary button HTML string + * + * @return {String} The appended button HTML strings + */ + appendButtons : function (secondary, primary) { + return this.buttonReverse ? primary + secondary : secondary + primary; + }, + + /** + * Build the proper message box + * + * @param {Object} item Current object in the queue + * + * @return {String} An HTML string of the message box + */ + build : function (item) { + var html = "", + type = item.type, + message = item.message, + css = item.cssClass || ""; + + html += "
"; + html += "Reset Focus"; + + if (_alertify.buttonFocus === "none") html += ""; + + // doens't require an actual form + if (type === "prompt") html += "
"; + + html += "
"; + html += dialogs.message.replace("{{message}}", message); + + if (type === "prompt") html += dialogs.input; + + html += dialogs.buttons.holder; + html += "
"; + + if (type === "prompt") html += "
"; + + html += "Reset Focus"; + html += "
"; + + switch (type) { + case "confirm": + html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.ok)); + html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); + break; + case "prompt": + html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.submit)); + html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel); + break; + case "alert": + html = html.replace("{{buttons}}", dialogs.buttons.ok); + html = html.replace("{{ok}}", this.labels.ok); + break; + default: + break; + } + + elDialog.className = "alertify alertify-" + type + " " + css; + elCover.className = "alertify-cover"; + return html; + }, + + /** + * Close the log messages + * + * @param {Object} elem HTML Element of log message to close + * @param {Number} wait [optional] Time (in ms) to wait before automatically hiding the message, if 0 never hide + * + * @return {undefined} + */ + close : function (elem, wait) { + // Unary Plus: +"2" === 2 + var timer = (wait && !isNaN(wait)) ? +wait : this.delay, + self = this, + hideElement, transitionDone; + + // set click event on log messages + this.bind(elem, "click", function () { + hideElement(elem); + }); + // Hide the dialog box after transition + // This ensure it doens't block any element from being clicked + transitionDone = function (event) { + event.stopPropagation(); + // unbind event so function only gets called once + self.unbind(this, self.transition.type, transitionDone); + // remove log message + elLog.removeChild(this); + if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden"; + }; + // this sets the hide class to transition out + // or removes the child if css transitions aren't supported + hideElement = function (el) { + // ensure element exists + if (typeof el !== "undefined" && el.parentNode === elLog) { + // whether CSS transition exists + if (self.transition.supported) { + self.bind(el, self.transition.type, transitionDone); + el.className += " alertify-log-hide"; + } else { + elLog.removeChild(el); + if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden"; + } + } + }; + // never close (until click) if wait is set to 0 + if (wait === 0) return; + // set timeout to auto close the log message + setTimeout(function () { hideElement(elem); }, timer); + }, + + /** + * Create a dialog box + * + * @param {String} message The message passed from the callee + * @param {String} type Type of dialog to create + * @param {Function} fn [Optional] Callback function + * @param {String} placeholder [Optional] Default value for prompt input field + * @param {String} cssClass [Optional] Class(es) to append to dialog box + * + * @return {Object} + */ + dialog : function (message, type, fn, placeholder, cssClass) { + // set the current active element + // this allows the keyboard focus to be resetted + // after the dialog box is closed + elCallee = document.activeElement; + // check to ensure the alertify dialog element + // has been successfully created + var check = function () { + if ((elLog && elLog.scrollTop !== null) && (elCover && elCover.scrollTop !== null)) return; + else check(); + }; + // error catching + if (typeof message !== "string") throw new Error("message must be a string"); + if (typeof type !== "string") throw new Error("type must be a string"); + if (typeof fn !== "undefined" && typeof fn !== "function") throw new Error("fn must be a function"); + // initialize alertify if it hasn't already been done + this.init(); + check(); + + queue.push({ type: type, message: message, callback: fn, placeholder: placeholder, cssClass: cssClass }); + if (!isopen) this.setup(); + + return this; + }, + + /** + * Extend the log method to create custom methods + * + * @param {String} type Custom method name + * + * @return {Function} + */ + extend : function (type) { + if (typeof type !== "string") throw new Error("extend method must have exactly one paramter"); + return function (message, wait) { + this.log(message, type, wait); + return this; + }; + }, + + /** + * Hide the dialog and rest to defaults + * + * @return {undefined} + */ + hide : function () { + var transitionDone, + self = this; + // remove reference from queue + queue.splice(0,1); + // if items remaining in the queue + if (queue.length > 0) this.setup(true); + else { + isopen = false; + // Hide the dialog box after transition + // This ensure it doens't block any element from being clicked + transitionDone = function (event) { + event.stopPropagation(); + // unbind event so function only gets called once + self.unbind(elDialog, self.transition.type, transitionDone); + }; + // whether CSS transition exists + if (this.transition.supported) { + this.bind(elDialog, this.transition.type, transitionDone); + elDialog.className = "alertify alertify-hide alertify-hidden"; + } else { + elDialog.className = "alertify alertify-hide alertify-hidden alertify-isHidden"; + } + elCover.className = "alertify-cover alertify-cover-hidden"; + // set focus to the last element or body + // after the dialog is closed + elCallee.focus(); + } + }, + + /** + * Initialize Alertify + * Create the 2 main elements + * + * @return {undefined} + */ + init : function () { + // ensure legacy browsers support html5 tags + document.createElement("nav"); + document.createElement("article"); + document.createElement("section"); + // cover + if ($("alertify-cover") == null) { + elCover = document.createElement("div"); + elCover.setAttribute("id", "alertify-cover"); + elCover.className = "alertify-cover alertify-cover-hidden"; + document.body.appendChild(elCover); + } + // main element + if ($("alertify") == null) { + isopen = false; + queue = []; + elDialog = document.createElement("section"); + elDialog.setAttribute("id", "alertify"); + elDialog.className = "alertify alertify-hidden"; + document.body.appendChild(elDialog); + } + // log element + if ($("alertify-logs") == null) { + elLog = document.createElement("section"); + elLog.setAttribute("id", "alertify-logs"); + elLog.className = "alertify-logs alertify-logs-hidden"; + document.body.appendChild(elLog); + } + // set tabindex attribute on body element + // this allows script to give it focus + // after the dialog is closed + document.body.setAttribute("tabindex", "0"); + // set transition type + this.transition = getTransitionEvent(); + }, + + /** + * Show a new log message box + * + * @param {String} message The message passed from the callee + * @param {String} type [Optional] Optional type of log message + * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding the log + * + * @return {Object} + */ + log : function (message, type, wait) { + // check to ensure the alertify dialog element + // has been successfully created + var check = function () { + if (elLog && elLog.scrollTop !== null) return; + else check(); + }; + // initialize alertify if it hasn't already been done + this.init(); + check(); + + elLog.className = "alertify-logs"; + this.notify(message, type, wait); + return this; + }, + + /** + * Add new log message + * If a type is passed, a class name "alertify-log-{type}" will get added. + * This allows for custom look and feel for various types of notifications. + * + * @param {String} message The message passed from the callee + * @param {String} type [Optional] Type of log message + * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding + * + * @return {undefined} + */ + notify : function (message, type, wait) { + var log = document.createElement("article"); + log.className = "alertify-log" + ((typeof type === "string" && type !== "") ? " alertify-log-" + type : ""); + log.innerHTML = message; + // append child + elLog.appendChild(log); + // triggers the CSS animation + setTimeout(function() { log.className = log.className + " alertify-log-show"; }, 50); + this.close(log, wait); + }, + + /** + * Set properties + * + * @param {Object} args Passing parameters + * + * @return {undefined} + */ + set : function (args) { + var k; + // error catching + if (typeof args !== "object" && args instanceof Array) throw new Error("args must be an object"); + // set parameters + for (k in args) { + if (args.hasOwnProperty(k)) { + this[k] = args[k]; + } + } + }, + + /** + * Common place to set focus to proper element + * + * @return {undefined} + */ + setFocus : function () { + if (input) { + input.focus(); + input.select(); + } + else btnFocus.focus(); + }, + + /** + * Initiate all the required pieces for the dialog box + * + * @return {undefined} + */ + setup : function (fromQueue) { + var item = queue[0], + self = this, + transitionDone; + + // dialog is open + isopen = true; + // Set button focus after transition + transitionDone = function (event) { + event.stopPropagation(); + self.setFocus(); + // unbind event so function only gets called once + self.unbind(elDialog, self.transition.type, transitionDone); + }; + // whether CSS transition exists + if (this.transition.supported && !fromQueue) { + this.bind(elDialog, this.transition.type, transitionDone); + } + // build the proper dialog HTML + elDialog.innerHTML = this.build(item); + // assign all the common elements + btnReset = $("alertify-resetFocus"); + btnResetBack = $("alertify-resetFocusBack"); + btnOK = $("alertify-ok") || undefined; + btnCancel = $("alertify-cancel") || undefined; + btnFocus = (_alertify.buttonFocus === "cancel") ? btnCancel : ((_alertify.buttonFocus === "none") ? $("alertify-noneFocus") : btnOK), + input = $("alertify-text") || undefined; + form = $("alertify-form") || undefined; + // add placeholder value to the input field + if (typeof item.placeholder === "string" && item.placeholder !== "") input.value = item.placeholder; + if (fromQueue) this.setFocus(); + this.addListeners(item.callback); + }, + + /** + * Unbind events to elements + * + * @param {Object} el HTML Object + * @param {Event} event Event to detach to element + * @param {Function} fn Callback function + * + * @return {undefined} + */ + unbind : function (el, event, fn) { + if (typeof el.removeEventListener === "function") { + el.removeEventListener(event, fn, false); + } else if (el.detachEvent) { + el.detachEvent("on" + event, fn); + } + } + }; + + return { + alert : function (message, fn, cssClass) { _alertify.dialog(message, "alert", fn, "", cssClass); return this; }, + confirm : function (message, fn, cssClass) { _alertify.dialog(message, "confirm", fn, "", cssClass); return this; }, + extend : _alertify.extend, + init : _alertify.init, + log : function (message, type, wait) { _alertify.log(message, type, wait); return this; }, + prompt : function (message, fn, placeholder, cssClass) { _alertify.dialog(message, "prompt", fn, placeholder, cssClass); return this; }, + success : function (message, wait) { _alertify.log(message, "success", wait); return this; }, + error : function (message, wait) { _alertify.log(message, "error", wait); return this; }, + set : function (args) { _alertify.set(args); }, + labels : _alertify.labels, + debug : _alertify.handleErrors + }; + }; + + // AMD and window support + if (typeof define === "function") { + define([], function () { return new Alertify(); }); + } else if (typeof global.alertify === "undefined") { + global.alertify = new Alertify(); + } + +}(this)); + + +$('button.success').click(function() { + alertify.set({ delay: 1700 }); + alertify.success("Success notification"); +}); + +$('button.alert').click(function() { + alertify.set({ delay: 1700 }); + alertify.error("Error notification"); +}); \ No newline at end of file diff --git a/public/js/script.js b/public/js/script.js index ec95081..8cea17d 100755 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,6 +1,16 @@ const menuIconButton = document.querySelector("[data-menu-icon-btn]") +const notifBellButton = document.querySelector("[data-notifBell-btn]") const sidebar = document.querySelector("[data-sidebar]") menuIconButton.addEventListener("click", () => { sidebar.classList.toggle("open") }) + + +notifBellButton.addEventListener( + "click", () => { + alertify.set({ delay: 1700 }); + alertify.success("Success notification"); + } + +) diff --git a/public/test_notif.html b/public/test_notif.html new file mode 100644 index 0000000..84c6d46 --- /dev/null +++ b/public/test_notif.html @@ -0,0 +1,92 @@ + + + + + + + Test Notification Popup + + + + + + + +
+
+
+
+
+ +
+
+ + + Well done! You successfullyread this important. +
+
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+
+ + + + diff --git a/tmpl/snippet_html.nim b/tmpl/snippet_html.nim index ad2b757..1edcbfc 100644 --- a/tmpl/snippet_html.nim +++ b/tmpl/snippet_html.nim @@ -8,6 +8,22 @@ proc templ_title* (title: string): string = tmpli html"""

$title

""" +# --==--==--==--==--==--==--==--==--==--==-- # +# HTML : Show notification Bell +# --==--==--==--==--==--==--==--==--==--==-- # +proc show_notification_bell* (nb_notif: int): string = tmpli html""" +
+ $nb_notif +
+ + + + +
+
+ """ + + # --==--==--==--==--==--==--==--==--==--==-- # # SVG : Status Joplin green icon # --==--==--==--==--==--==--==--==--==--==-- # @@ -49,3 +65,7 @@ proc NEED_joplinSyncro_icon* (): string = tmpli html""" proc InProgress_joplinSyncro_icon* (): string = tmpli html""" """ + + + + diff --git a/tmpl/website.tmpl b/tmpl/website.tmpl index f8edd4e..ffcb660 100644 --- a/tmpl/website.tmpl +++ b/tmpl/website.tmpl @@ -24,8 +24,9 @@ Document - + + @@ -35,7 +36,14 @@ # var icon_menu = Menu_icon() ${icon_menu} + + + +
+ + +