diff --git a/redesign/newthread.nim b/redesign/newthread.nim new file mode 100644 index 0000000..0b3751e --- /dev/null +++ b/redesign/newthread.nim @@ -0,0 +1,81 @@ +when defined(js): + import sugar, httpcore, options, json + import dom except Event + + include karax/prelude + import karax / [kajax, kdom] + + import error, replybox, threadlist, post + import karaxutils + + type + NewThreadModal* = ref object + shown: bool + loading: bool + onNewThread: proc (threadId, postId: int) + error: Option[PostError] + replyBox: ReplyBox + + proc onCreatePost(httpStatus: int, response: kstring, state: NewThreadModal) = + postFinished: + state.shown = false + state.onNewThread(0, 0) # TODO + + proc onCreateClick(ev: Event, n: VNode, state: NewThreadModal) = + state.loading = true + state.error = none[PostError]() + + let uri = makeUri("login") + # TODO: This is a hack, karax should support this. + let formData = newFormData() + #formData.append("" TODO + ajaxPost(uri, @[], cast[cstring](formData), + (s: int, r: kstring) => onCreatePost(s, r, state)) + + proc onClose(ev: Event, n: VNode, state: NewThreadModal) = + state.shown = false + ev.preventDefault() + + proc newNewThreadModal*( + onNewThread: proc (threadId, postId: int) + ): NewThreadModal = + NewThreadModal( + shown: false, + onNewThread: onNewThread, + replyBox: newReplyBox(nil) + ) + + proc show*(state: NewThreadModal) = + state.shown = true + + proc render*(state: NewThreadModal): VNode = + result = buildHtml(): + tdiv(class=class({"active": state.shown}, "modal modal-lg"), + id="new-thread-modal"): + a(href="", class="modal-overlay", "aria-label"="close", + onClick=(ev: Event, n: VNode) => onClose(ev, n, state)) + tdiv(class="modal-container"): + tdiv(class="modal-header"): + a(href="", class="btn btn-clear float-right", + "aria-label"="close", + onClick=(ev: Event, n: VNode) => onClose(ev, n, state)) + tdiv(class="modal-title h5"): + text "New Thread" + tdiv(class="modal-body"): + tdiv(class="content"): + input(class="form-input", `type`="text", name="username", + placeholder="Type the title here") + renderContent(state.replyBox, none[Thread](), none[Post]()) + + tdiv(class="modal-footer"): + button(class="btn", + onClick=(ev: Event, n: VNode) => + onClose(ev, n, state)): + text "Cancel" + button(class=class( + {"loading": state.loading}, + "btn btn-primary" + ), + onClick=(ev: Event, n: VNode) => + (onCreateClick(ev, n, state))): + text "Create thread" \ No newline at end of file diff --git a/redesign/nimforum.scss b/redesign/nimforum.scss index fff7094..b15b579 100644 --- a/redesign/nimforum.scss +++ b/redesign/nimforum.scss @@ -113,6 +113,23 @@ $logo-height: $navbar-height - 20px; } } +#new-thread-modal { + .modal-container .modal-body { + max-height: none; + } + + .form-input { + margin-bottom: $control-padding-y*2; + } + + textarea.form-input, .panel-body > div { + margin-top: $control-padding-y*2; + resize: none; + min-height: 40vh; + max-height: 45vh; + } +} + // - Thread table .thread-title { a, a:visited, a:hover { diff --git a/redesign/replybox.nim b/redesign/replybox.nim index bbc40c6..7b9d235 100644 --- a/redesign/replybox.nim +++ b/redesign/replybox.nim @@ -48,6 +48,7 @@ when defined(js): state.preview = true state.loading = true state.error = none[PostError]() + state.rendering = none[kstring]() let formData = newFormData() formData.append("msg", state.text) @@ -55,6 +56,10 @@ when defined(js): ajaxPost(uri, @[], cast[cstring](formData), (s: int, r: kstring) => onPreviewPost(s, r, state)) + proc onMessageClick(e: Event, n: VNode, state: ReplyBox) = + state.preview = false + state.error = none[PostError]() + proc onReplyPost(httpStatus: int, response: kstring, state: ReplyBox) = postFinished: state.text = "" @@ -84,6 +89,53 @@ when defined(js): # `value` on the node? We need to document this better :) state.text = cast[dom.TextAreaElement](n.dom).value + proc renderContent*(state: ReplyBox, thread: Option[Thread], + post: Option[Post]): VNode = + result = buildHtml(): + tdiv(class="panel"): + tdiv(class="panel-nav"): + ul(class="tab tab-block"): + li(class=class({"active": not state.preview}, "tab-item"), + onClick=(e: Event, n: VNode) => + onMessageClick(e, n, state)): + a(class="c-hand"): + text "Message" + li(class=class({"active": state.preview}, "tab-item"), + onClick=(e: Event, n: VNode) => + onPreviewClick(e, n, state)): + a(class="c-hand"): + text "Preview" + tdiv(class="panel-body"): + if state.preview: + if state.loading: + tdiv(class="loading") + elif state.rendering.isSome(): + verbatim(state.rendering.get()) + else: + textarea(class="form-input", rows="5", + onChange=(e: Event, n: VNode) => + onChange(e, n, state), + value=state.text) + + if state.error.isSome(): + p(class="text-error", + style=style(StyleAttr.marginTop, "0.4rem")): + text state.error.get().message + + if thread.isSome: + tdiv(class="panel-footer"): + button(class=class( + {"loading": state.loading}, + "btn btn-primary float-right" + ), + onClick=(e: Event, n: VNode) => + onReplyClick(e, n, state, thread.get(), post)): + text "Reply" + button(class="btn btn-link float-right", + onClick=(e: Event, n: VNode) => + onCancelClick(e, n, state)): + text "Cancel" + proc render*(state: ReplyBox, thread: Thread, post: Option[Post], hasMore: bool): VNode = if not state.shown: @@ -106,44 +158,4 @@ when defined(js): button(class="btn"): italic(class="fas fa-arrow-up") tdiv(class="information-content"): - tdiv(class="panel"): - tdiv(class="panel-nav"): - ul(class="tab tab-block"): - li(class=class({"active": not state.preview}, "tab-item"), - onClick=(e: Event, n: VNode) => (state.preview = false)): - a(class="c-hand"): - text "Message" - li(class=class({"active": state.preview}, "tab-item"), - onClick=(e: Event, n: VNode) => - onPreviewClick(e, n, state)): - a(class="c-hand"): - text "Preview" - tdiv(class="panel-body"): - if state.preview: - if state.loading: - tdiv(class="loading") - elif state.rendering.isSome(): - verbatim(state.rendering.get()) - else: - textarea(class="form-input", rows="5", - onChange=(e: Event, n: VNode) => - onChange(e, n, state), - value=state.text) - - if state.error.isSome(): - tdiv(class="toast toast-error", - style=style(StyleAttr.marginTop, "0.4rem")): - text state.error.get().message - - tdiv(class="panel-footer"): - button(class=class( - {"loading": state.loading}, - "btn btn-primary float-right" - ), - onClick=(e: Event, n: VNode) => - onReplyClick(e, n, state, thread, post)): - text "Reply" - button(class="btn btn-link float-right", - onClick=(e: Event, n: VNode) => - onCancelClick(e, n, state)): - text "Cancel" \ No newline at end of file + renderContent(state, some(thread), post) \ No newline at end of file diff --git a/redesign/threadlist.nim b/redesign/threadlist.nim index 7095a53..837c994 100644 --- a/redesign/threadlist.nim +++ b/redesign/threadlist.nim @@ -24,24 +24,30 @@ when defined(js): include karax/prelude import karax / [vstyles, kajax, kdom] - import karaxutils, error + import karaxutils, error, newthread type State = ref object list: Option[ThreadList] loading: bool status: HttpCode + newThread: NewThreadModal + proc onNewThread(threadId, postId: int) proc newState(): State = State( list: none[ThreadList](), loading: false, - status: Http200 + status: Http200, + newThread: newNewThreadModal(onNewThread) ) var state = newState() + proc onNewThread(threadId, postId: int) = + discard + proc genTopButtons(): VNode = result = buildHtml(): section(class="navbar container grid-xl", id="main-buttons"): @@ -57,7 +63,8 @@ when defined(js): button(class="btn btn-link"): text "Most Active" button(class="btn btn-link"): text "Categories" section(class="navbar-section"): - button(class="btn btn-secondary"): + button(class="btn btn-secondary", + onClick=(e: Event, n: VNode) => state.newThread.show()): italic(class="fas fa-plus") text " New Thread" @@ -174,4 +181,5 @@ when defined(js): proc renderThreadList*(): VNode = result = buildHtml(tdiv): genTopButtons() - genThreadList() \ No newline at end of file + genThreadList() + render(state.newThread) \ No newline at end of file