dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit a236b8d07abf80abca8a4ff61d4aba09def5d7f2
parent 9c0c3cc3a0c714b1812d086fe2829d238af45fe0
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Fri, 21 Feb 2025 20:25:57 -0800

top-bar stream for quban

Diffstat:
Acmd/dkf/migrations/164.sql | 4++++
Mpkg/database/tableUsers.go | 2++
Apkg/web/handlers/api/v1/stream-top-bar.qtpl | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/web/handlers/api/v1/stream-top-bar.qtpl.go | 312+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/web/handlers/api/v1/topBarHandler.go | 13+++++++++++++
Mpkg/web/handlers/data.go | 1+
Mpkg/web/handlers/settings.go | 3+++
Mpkg/web/middlewares/middlewares.go | 1+
Mpkg/web/public/views/pages/settings/chat.gohtml | 7+++++++
9 files changed, 463 insertions(+), 0 deletions(-)

diff --git a/cmd/dkf/migrations/164.sql b/cmd/dkf/migrations/164.sql @@ -0,0 +1,4 @@ +-- +migrate Up +ALTER TABLE users ADD COLUMN use_stream_top_bar TINYINT(1) NOT NULL DEFAULT 0; + +-- +migrate Down diff --git a/pkg/database/tableUsers.go b/pkg/database/tableUsers.go @@ -159,6 +159,7 @@ type User struct { AFK bool UseStream bool UseStreamMenu bool + UseStreamTopBar bool SyntaxHighlightCode string ConfirmExternalLinks bool ChessSoundsEnabled bool @@ -826,6 +827,7 @@ func (d *DkfDB) createUser(usernameStr, password, repassword, gpgPublicKey strin newUser.RegistrationDuration = registrationDuration newUser.UseStream = true newUser.UseStreamMenu = true + newUser.UseStreamTopBar = true newUser.DisplayAliveIndicator = true newUser.CodeBlockHeight = 300 newUser.HellbanOpacity = 30 diff --git a/pkg/web/handlers/api/v1/stream-top-bar.qtpl b/pkg/web/handlers/api/v1/stream-top-bar.qtpl @@ -0,0 +1,120 @@ +{% import "dkforest/pkg/database" %} + +{% func RenderTopBar(CSRF string, Data chatTopBarData, AuthUser *database.User) -%} +<html lang="en"> + <head> + <title></title> + <style> + html, body { + margin: 0; + padding: 0; + } + body { font-family: Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; } + a { color: #00bc8c; text-decoration: none; font-size: 14px; } + a:hover { color: #007053; text-decoration: underline; } + .wrapper { + position: relative; + width: 500px; + } + .wrapper input, .wrapper textarea { + box-sizing: border-box; + border-radius: 10px 0px 0px 10px; + border: 1px solid rgba(10,10,10,255); + background: rgba(76, 76, 76, 255); + margin: 0; + float: left; + padding-right: 10px; + padding-left: 10px; + width: 421px; + height: 22px; + color: #fff; + box-shadow:0 0 0 black; + outline: none; + } + .wrapper input:focus { + outline: none; + } + .wrapper button { + border-radius: 0 10px 10px 0; + position: absolute; + top: 0; + right: 0; + width: 80px; + height: 22px; + color: white; + border: 1px solid rgba(10,10,10,255); + background: rgba(65, 65, 65, 255); + } + .wrapper button:hover { + background: rgba(57, 57, 57, 255); + } + {%- if Data.Multiline -%} + .wrapper textarea, .wrapper button { + height: 122px; + } + {%- endif -%} + * { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + [list]::-webkit-calendar-picker-indicator { + display: none !important; + } + + #top-bar-form { margin: 0; padding: 5px 0 0 0; } + #refresh-btn { line-height: 20px; } + #file-upload-btn { height: 16px; font-size: 9px; margin-top: 2px; margin-left: 5px; position: absolute; } + #err-lbl { color: #e74c3c; font-size: 80%; } + #success-lbl { color: #198754; font-size: 80%; } + #username-td { padding-right: 5px; } + #links-td { color: #aaa; } + </style> + </head> + <body> + <form method="post" id="top-bar-form" enctype="multipart/form-data"> + <input type="hidden" name="csrf" value="{%s CSRF %}" /> + <table> + <tr> + <td id="username-td"><span {%= unesc(AuthUser.GenerateChatStyle()) -%}>{%s string(AuthUser.Username) -%}</span></td> + <td> + <div class="wrapper"> + {%- if Data.Multiline -%} + <textarea name="message" maxlength="10000" autocomplete="off"{%- if AuthUser.SpellcheckEnabled -%} spellcheck="true"{%- endif -%} autofocus>{%s Data.Message %}</textarea> + {%- else -%} + <input value="{%s Data.Message -%}" type="text" name="message" maxlength="10000" autocomplete="off"{%- if AuthUser.SpellcheckEnabled -%} spellcheck="true"{%- endif -%} autofocus{%- if AuthUser.AutocompleteCommandsEnabled -%} list="commands"{%- endif -%} /> + {%- if AuthUser.AutocompleteCommandsEnabled -%} + <datalist id="commands"> + {%- for _, el := range Data.CommandsList -%}<option value="{%s el %}">{%- endfor -%} + </datalist> + {%- endif -%} + {%- endif -%} + <button type="submit" value="send_message" name="btn_submit">send</button> + </div> + </td> + </tr> + <tr> + <td> + {%- if Data.Error != "" || Data.Success != "" -%} + <a href="/api/v1/chat/top-bar/{%s Data.RoomName -%}?{%- if Data.Multiline -%}{%s Data.QueryParamsMl -%}{%- else -%}{%s Data.QueryParamsNml -%}{%- endif -%}" title="Refresh" id="refresh-btn">&#8635;</a> + {%- endif -%} + </td> + <td id="links-td"> + {%- if Data.Error != "" -%} + <span id="err-lbl">{%s Data.Error -%}</span> + {%- elseif Data.Success != "" -%} + <span id="success-lbl">{%s Data.Success -%}</span> + {%- else -%} + <a href="/settings/chat" target="_top">Settings</a> | + <a href="/chat/help" target="_top">"Available commands</a> | + <a href="/chat/create-room" target="_top">Create room</a> + {%- if AuthUser.CanUseMultiline -%} | <a href="/chat/{%s Data.RoomName -%}?{%- if Data.Multiline -%}{%s Data.QueryParamsNml -%}{%- else -%}{%s Data.QueryParamsMl -%}{%- endif -%}" target="_top">ml</a>{%- endif -%} + {%- if AuthUser.CanUpload() -%} | <input name="file" type="file" id="file-upload-btn" />{%- endif -%} + {%- endif -%} + </td> + <td></td> + </tr> + </table> + </form> + </body> +</html> +{%- endfunc -%} diff --git a/pkg/web/handlers/api/v1/stream-top-bar.qtpl.go b/pkg/web/handlers/api/v1/stream-top-bar.qtpl.go @@ -0,0 +1,312 @@ +// Code generated by qtc from "stream-top-bar.qtpl". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +//line stream-top-bar.qtpl:1 +package v1 + +//line stream-top-bar.qtpl:1 +import "dkforest/pkg/database" + +//line stream-top-bar.qtpl:3 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line stream-top-bar.qtpl:3 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line stream-top-bar.qtpl:3 +func StreamRenderTopBar(qw422016 *qt422016.Writer, CSRF string, Data chatTopBarData, AuthUser *database.User) { +//line stream-top-bar.qtpl:3 + qw422016.N().S(`<html lang="en"> + <head> + <title></title> + <style> + html, body { + margin: 0; + padding: 0; + } + body { font-family: Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; } + a { color: #00bc8c; text-decoration: none; font-size: 14px; } + a:hover { color: #007053; text-decoration: underline; } + .wrapper { + position: relative; + width: 500px; + } + .wrapper input, .wrapper textarea { + box-sizing: border-box; + border-radius: 10px 0px 0px 10px; + border: 1px solid rgba(10,10,10,255); + background: rgba(76, 76, 76, 255); + margin: 0; + float: left; + padding-right: 10px; + padding-left: 10px; + width: 421px; + height: 22px; + color: #fff; + box-shadow:0 0 0 black; + outline: none; + } + .wrapper input:focus { + outline: none; + } + .wrapper button { + border-radius: 0 10px 10px 0; + position: absolute; + top: 0; + right: 0; + width: 80px; + height: 22px; + color: white; + border: 1px solid rgba(10,10,10,255); + background: rgba(65, 65, 65, 255); + } + .wrapper button:hover { + background: rgba(57, 57, 57, 255); + } +`) +//line stream-top-bar.qtpl:51 + if Data.Multiline { +//line stream-top-bar.qtpl:51 + qw422016.N().S(` .wrapper textarea, .wrapper button { + height: 122px; + } +`) +//line stream-top-bar.qtpl:55 + } +//line stream-top-bar.qtpl:55 + qw422016.N().S(` * { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + [list]::-webkit-calendar-picker-indicator { + display: none !important; + } + + #top-bar-form { margin: 0; padding: 5px 0 0 0; } + #refresh-btn { line-height: 20px; } + #file-upload-btn { height: 16px; font-size: 9px; margin-top: 2px; margin-left: 5px; position: absolute; } + #err-lbl { color: #e74c3c; font-size: 80%; } + #success-lbl { color: #198754; font-size: 80%; } + #username-td { padding-right: 5px; } + #links-td { color: #aaa; } + </style> + </head> + <body> + <form method="post" id="top-bar-form" enctype="multipart/form-data"> + <input type="hidden" name="csrf" value="`) +//line stream-top-bar.qtpl:75 + qw422016.E().S(CSRF) +//line stream-top-bar.qtpl:75 + qw422016.N().S(`" /> + <table> + <tr> + <td id="username-td"><span `) +//line stream-top-bar.qtpl:78 + streamunesc(qw422016, AuthUser.GenerateChatStyle()) +//line stream-top-bar.qtpl:78 + qw422016.N().S(`>`) +//line stream-top-bar.qtpl:78 + qw422016.E().S(string(AuthUser.Username)) +//line stream-top-bar.qtpl:78 + qw422016.N().S(`</span></td> + <td> + <div class="wrapper"> +`) +//line stream-top-bar.qtpl:81 + if Data.Multiline { +//line stream-top-bar.qtpl:81 + qw422016.N().S(` <textarea name="message" maxlength="10000" autocomplete="off"`) +//line stream-top-bar.qtpl:82 + if AuthUser.SpellcheckEnabled { +//line stream-top-bar.qtpl:82 + qw422016.N().S(`spellcheck="true"`) +//line stream-top-bar.qtpl:82 + } +//line stream-top-bar.qtpl:82 + qw422016.N().S(`autofocus>`) +//line stream-top-bar.qtpl:82 + qw422016.E().S(Data.Message) +//line stream-top-bar.qtpl:82 + qw422016.N().S(`</textarea> +`) +//line stream-top-bar.qtpl:83 + } else { +//line stream-top-bar.qtpl:83 + qw422016.N().S(` <input value="`) +//line stream-top-bar.qtpl:84 + qw422016.E().S(Data.Message) +//line stream-top-bar.qtpl:84 + qw422016.N().S(`" type="text" name="message" maxlength="10000" autocomplete="off"`) +//line stream-top-bar.qtpl:84 + if AuthUser.SpellcheckEnabled { +//line stream-top-bar.qtpl:84 + qw422016.N().S(`spellcheck="true"`) +//line stream-top-bar.qtpl:84 + } +//line stream-top-bar.qtpl:84 + qw422016.N().S(`autofocus`) +//line stream-top-bar.qtpl:84 + if AuthUser.AutocompleteCommandsEnabled { +//line stream-top-bar.qtpl:84 + qw422016.N().S(`list="commands"`) +//line stream-top-bar.qtpl:84 + } +//line stream-top-bar.qtpl:84 + qw422016.N().S(`/> +`) +//line stream-top-bar.qtpl:85 + if AuthUser.AutocompleteCommandsEnabled { +//line stream-top-bar.qtpl:85 + qw422016.N().S(` <datalist id="commands"> +`) +//line stream-top-bar.qtpl:87 + for _, el := range Data.CommandsList { +//line stream-top-bar.qtpl:87 + qw422016.N().S(`<option value="`) +//line stream-top-bar.qtpl:87 + qw422016.E().S(el) +//line stream-top-bar.qtpl:87 + qw422016.N().S(`">`) +//line stream-top-bar.qtpl:87 + } +//line stream-top-bar.qtpl:87 + qw422016.N().S(` </datalist> +`) +//line stream-top-bar.qtpl:89 + } +//line stream-top-bar.qtpl:90 + } +//line stream-top-bar.qtpl:90 + qw422016.N().S(` <button type="submit" value="send_message" name="btn_submit">send</button> + </div> + </td> + </tr> + <tr> + <td> +`) +//line stream-top-bar.qtpl:97 + if Data.Error != "" || Data.Success != "" { +//line stream-top-bar.qtpl:97 + qw422016.N().S(` <a href="/api/v1/chat/top-bar/`) +//line stream-top-bar.qtpl:98 + qw422016.E().S(Data.RoomName) +//line stream-top-bar.qtpl:98 + qw422016.N().S(`?`) +//line stream-top-bar.qtpl:98 + if Data.Multiline { +//line stream-top-bar.qtpl:98 + qw422016.E().S(Data.QueryParamsMl) +//line stream-top-bar.qtpl:98 + } else { +//line stream-top-bar.qtpl:98 + qw422016.E().S(Data.QueryParamsNml) +//line stream-top-bar.qtpl:98 + } +//line stream-top-bar.qtpl:98 + qw422016.N().S(`" title="Refresh" id="refresh-btn">&#8635;</a> +`) +//line stream-top-bar.qtpl:99 + } +//line stream-top-bar.qtpl:99 + qw422016.N().S(` </td> + <td id="links-td"> +`) +//line stream-top-bar.qtpl:102 + if Data.Error != "" { +//line stream-top-bar.qtpl:102 + qw422016.N().S(` <span id="err-lbl">`) +//line stream-top-bar.qtpl:103 + qw422016.E().S(Data.Error) +//line stream-top-bar.qtpl:103 + qw422016.N().S(`</span> +`) +//line stream-top-bar.qtpl:104 + } else if Data.Success != "" { +//line stream-top-bar.qtpl:104 + qw422016.N().S(` <span id="success-lbl">`) +//line stream-top-bar.qtpl:105 + qw422016.E().S(Data.Success) +//line stream-top-bar.qtpl:105 + qw422016.N().S(`</span> +`) +//line stream-top-bar.qtpl:106 + } else { +//line stream-top-bar.qtpl:106 + qw422016.N().S(` <a href="/settings/chat" target="_top">Settings</a> | + <a href="/chat/help" target="_top">"Available commands</a> | + <a href="/chat/create-room" target="_top">Create room</a> +`) +//line stream-top-bar.qtpl:110 + if AuthUser.CanUseMultiline { +//line stream-top-bar.qtpl:110 + qw422016.N().S(`| <a href="/chat/`) +//line stream-top-bar.qtpl:110 + qw422016.E().S(Data.RoomName) +//line stream-top-bar.qtpl:110 + qw422016.N().S(`?`) +//line stream-top-bar.qtpl:110 + if Data.Multiline { +//line stream-top-bar.qtpl:110 + qw422016.E().S(Data.QueryParamsNml) +//line stream-top-bar.qtpl:110 + } else { +//line stream-top-bar.qtpl:110 + qw422016.E().S(Data.QueryParamsMl) +//line stream-top-bar.qtpl:110 + } +//line stream-top-bar.qtpl:110 + qw422016.N().S(`" target="_top">ml</a>`) +//line stream-top-bar.qtpl:110 + } +//line stream-top-bar.qtpl:111 + if AuthUser.CanUpload() { +//line stream-top-bar.qtpl:111 + qw422016.N().S(`| <input name="file" type="file" id="file-upload-btn" />`) +//line stream-top-bar.qtpl:111 + } +//line stream-top-bar.qtpl:112 + } +//line stream-top-bar.qtpl:112 + qw422016.N().S(` </td> + <td></td> + </tr> + </table> + </form> + </body> +</html> +`) +//line stream-top-bar.qtpl:120 +} + +//line stream-top-bar.qtpl:120 +func WriteRenderTopBar(qq422016 qtio422016.Writer, CSRF string, Data chatTopBarData, AuthUser *database.User) { +//line stream-top-bar.qtpl:120 + qw422016 := qt422016.AcquireWriter(qq422016) +//line stream-top-bar.qtpl:120 + StreamRenderTopBar(qw422016, CSRF, Data, AuthUser) +//line stream-top-bar.qtpl:120 + qt422016.ReleaseWriter(qw422016) +//line stream-top-bar.qtpl:120 +} + +//line stream-top-bar.qtpl:120 +func RenderTopBar(CSRF string, Data chatTopBarData, AuthUser *database.User) string { +//line stream-top-bar.qtpl:120 + qb422016 := qt422016.AcquireByteBuffer() +//line stream-top-bar.qtpl:120 + WriteRenderTopBar(qb422016, CSRF, Data, AuthUser) +//line stream-top-bar.qtpl:120 + qs422016 := string(qb422016.B) +//line stream-top-bar.qtpl:120 + qt422016.ReleaseByteBuffer(qb422016) +//line stream-top-bar.qtpl:120 + return qs422016 +//line stream-top-bar.qtpl:120 +} diff --git a/pkg/web/handlers/api/v1/topBarHandler.go b/pkg/web/handlers/api/v1/topBarHandler.go @@ -9,6 +9,7 @@ import ( "dkforest/pkg/web/handlers/interceptors" "dkforest/pkg/web/handlers/interceptors/command" "dkforest/pkg/web/handlers/streamModals" + "dkforest/pkg/web/handlers/utils/stream" "fmt" "github.com/dustin/go-humanize" "github.com/labstack/echo" @@ -130,6 +131,7 @@ func buildCommandsList(authUser *database.User, room database.ChatRoom) (command func ChatTopBarHandler(c echo.Context) error { authUser := c.Get("authUser").(*database.User) db := c.Get("database").(*database.DkfDB) + csrf, _ := c.Get("csrf").(string) var data chatTopBarData data.RoomName = c.Param("roomName") @@ -174,6 +176,17 @@ func ChatTopBarHandler(c echo.Context) error { // GET requests stops here if c.Request().Method != http.MethodPost { + if authUser.UseStreamTopBar { + streamItem, err := stream.SetStreaming(c, authUser.ID, "") + if err != nil { + return c.Redirect(http.StatusFound, "/") + } + defer streamItem.Cleanup() + _, _ = c.Response().Write([]byte(RenderTopBar(csrf, data, authUser))) + c.Response().Flush() + <-streamItem.Quit + return c.NoContent(http.StatusOK) + } return c.Render(http.StatusOK, "chat-top-bar", data) } diff --git a/pkg/web/handlers/data.go b/pkg/web/handlers/data.go @@ -678,6 +678,7 @@ type settingsChatData struct { DisplayHellbanButton bool UseStream bool UseStreamMenu bool + UseStreamTopBar bool DisplayAliveIndicator bool ManualMultiline bool ConfirmExternalLinks bool diff --git a/pkg/web/handlers/settings.go b/pkg/web/handlers/settings.go @@ -66,6 +66,7 @@ func SettingsChatHandler(c echo.Context) error { data.NotifyChessMove = authUser.NotifyChessMove data.UseStream = authUser.UseStream data.UseStreamMenu = authUser.UseStreamMenu + data.UseStreamTopBar = authUser.UseStreamTopBar data.DisplayAliveIndicator = authUser.DisplayAliveIndicator data.ConfirmExternalLinks = authUser.ConfirmExternalLinks data.ChessSoundsEnabled = authUser.ChessSoundsEnabled @@ -117,6 +118,7 @@ func changeSettingsForm(c echo.Context, data settingsChatData) error { data.NotifyChessMove = utils.DoParseBool(c.Request().PostFormValue("notify_chess_move")) data.UseStream = utils.DoParseBool(c.Request().PostFormValue("use_stream")) data.UseStreamMenu = utils.DoParseBool(c.Request().PostFormValue("use_stream_menu")) + data.UseStreamTopBar = utils.DoParseBool(c.Request().PostFormValue("use_stream_top_bar")) data.DisplayAliveIndicator = utils.DoParseBool(c.Request().PostFormValue("display_alive_indicator")) data.ConfirmExternalLinks = utils.DoParseBool(c.Request().PostFormValue("confirm_external_links")) data.ChessSoundsEnabled = utils.DoParseBool(c.Request().PostFormValue("chess_sounds_enabled")) @@ -166,6 +168,7 @@ func changeSettingsForm(c echo.Context, data settingsChatData) error { authUser.NotifyChessMove = data.NotifyChessMove authUser.UseStream = data.UseStream authUser.UseStreamMenu = data.UseStreamMenu + authUser.UseStreamTopBar = data.UseStreamTopBar authUser.DisplayAliveIndicator = data.DisplayAliveIndicator authUser.ConfirmExternalLinks = data.ConfirmExternalLinks authUser.ChessSoundsEnabled = data.ChessSoundsEnabled diff --git a/pkg/web/middlewares/middlewares.go b/pkg/web/middlewares/middlewares.go @@ -36,6 +36,7 @@ var GzipMiddleware = middleware.GzipWithConfig( c.Path() == "/poker/:roomID/bet" || c.Path() == "/api/v1/chat/messages/:roomName/stream" || c.Path() == "/api/v1/chat/messages/:roomName/stream/menu" || + c.Path() == "/api/v1/chat/top-bar/:roomName" || c.Path() == "/uploads/:filename" || c.Path() == "/" { return true diff --git a/pkg/web/public/views/pages/settings/chat.gohtml b/pkg/web/public/views/pages/settings/chat.gohtml @@ -212,6 +212,13 @@ </div> <div class="form-check form-check-1"> <div class="checkbox-wrapper form-check-input"> + <input class="my-cbx" type="checkbox" name="use_stream_top_bar" id="use_stream_top_bar" value="1"{{ if .Data.UseStreamTopBar }} checked{{ end }} /> + <label for="use_stream_top_bar" class="toggle"><span></span></label> + </div> + <label class="form-check-label" for="use_stream_top_bar">{{ t "Use chat top-bar stream version" . }}</label> + </div> + <div class="form-check form-check-1"> + <div class="checkbox-wrapper form-check-input"> <input class="my-cbx" type="checkbox" name="display_alive_indicator" id="display_alive_indicator" value="1"{{ if .Data.DisplayAliveIndicator }} checked{{ end }} /> <label for="display_alive_indicator" class="toggle"><span></span></label> </div>