first draft of posting to a topic (including preview)
This commit is contained in:
+114
@@ -553,3 +553,117 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
||||
}
|
||||
return "framed_template", "posts.jet", nil
|
||||
}
|
||||
|
||||
func PostInTopic(ctxt ui.AmContext) (string, any, error) {
|
||||
// Locate community, conference, and topic.
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
var topic *database.Topic = nil
|
||||
if rawTopic, err := strconv.ParseInt(ctxt.URLParam("topic"), 10, 16); err == nil {
|
||||
topic, err = database.AmGetTopicByNumber(ctxt.Ctx(), conf, int16(rawTopic))
|
||||
}
|
||||
if topic == nil {
|
||||
ctxt.SetRC(http.StatusNotFound)
|
||||
return ui.ErrorPage(ctxt, fmt.Errorf("topic not found: %s", ctxt.URLParam("topic")))
|
||||
}
|
||||
|
||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.URLParam("confid"), topic.Number)
|
||||
if ctxt.FormFieldIsSet("cancel") {
|
||||
return "redirect", urlStem, nil
|
||||
}
|
||||
if ctxt.FormFieldIsSet("preview") {
|
||||
// Preview the post.
|
||||
checker, err := htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "escaper")
|
||||
if err != nil {
|
||||
return ui.ErrorPage(ctxt, err)
|
||||
}
|
||||
checker.Append(ctxt.FormField("pseud"))
|
||||
checker.Finish()
|
||||
v, _ := checker.Value()
|
||||
ctxt.VarMap().Set("pseud", v)
|
||||
|
||||
// escape the data
|
||||
postdata := ctxt.FormField("pb")
|
||||
checker.Reset()
|
||||
checker.Append(postdata)
|
||||
checker.Finish()
|
||||
v, _ = checker.Value()
|
||||
ctxt.VarMap().Set("pb", v)
|
||||
|
||||
// run the preview
|
||||
checker, err = htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "preview")
|
||||
if err != nil {
|
||||
return ui.ErrorPage(ctxt, err)
|
||||
}
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.URLParam("cid"), conf.TopTopic+1))
|
||||
checker.Append(postdata)
|
||||
checker.Finish()
|
||||
v, _ = checker.Value()
|
||||
ctxt.VarMap().Set("previewPb", v)
|
||||
nErr, _ := checker.Counter("spelling")
|
||||
ctxt.VarMap().Set("nError", nErr)
|
||||
|
||||
ctxt.VarMap().Set("maxPost", ctxt.FormField("xp"))
|
||||
if ctxt.FormFieldIsSet("attach") {
|
||||
ctxt.VarMap().Set("attachFile", true)
|
||||
}
|
||||
ctxt.VarMap().Set("urlStem", urlStem)
|
||||
ctxt.VarMap().Set("amsterdam_pageTitle", "Previewing Message")
|
||||
return "framed_template", "preview_post.jet", nil
|
||||
}
|
||||
// Figure out which URL to return to once this post is made.
|
||||
var returnURL string
|
||||
if ctxt.FormFieldIsSet("post") {
|
||||
returnURL = urlStem
|
||||
} else if ctxt.FormFieldIsSet("postnext") {
|
||||
returnURL = urlStem // TODO
|
||||
} else if ctxt.FormFieldIsSet("posttopics") {
|
||||
returnURL = fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.URLParam("confid"))
|
||||
} else {
|
||||
return ui.ErrorPage(ctxt, errors.New("unknown post button"))
|
||||
}
|
||||
maxPost, err := ctxt.FormFieldInt("xp")
|
||||
if err != nil {
|
||||
return ui.ErrorPage(ctxt, err)
|
||||
}
|
||||
if int32(maxPost) < topic.TopMessage {
|
||||
// Slippage detected! Display the slipped posts and another post box.
|
||||
// TODO
|
||||
return "framed_template", "???", nil
|
||||
}
|
||||
// start by checking the title and pseud
|
||||
checker, err := htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "post-pseud")
|
||||
if err != nil {
|
||||
return ui.ErrorPage(ctxt, err)
|
||||
}
|
||||
checker.Append(ctxt.FormField("pseud"))
|
||||
checker.Finish()
|
||||
postPseud, _ := checker.Value()
|
||||
|
||||
// now check the post data itself
|
||||
checker, err = htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "post-body")
|
||||
if err != nil {
|
||||
return ui.ErrorPage(ctxt, err)
|
||||
}
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.URLParam("cid"), conf.TopTopic+1))
|
||||
checker.Append(ctxt.FormField("pb"))
|
||||
checker.Finish()
|
||||
postText, _ := checker.Value()
|
||||
lines, _ := checker.Lines()
|
||||
|
||||
// Add the post!
|
||||
hdr, err := database.AmNewPost(ctxt.Ctx(), conf, topic, ctxt.CurrentUser(), postPseud, postText, int32(lines))
|
||||
if err != nil {
|
||||
return ui.ErrorPage(ctxt, err)
|
||||
}
|
||||
|
||||
if !ctxt.FormFieldIsSet("attach") {
|
||||
return "redirect", returnURL, nil // no attachment - just redisplay topic list
|
||||
}
|
||||
|
||||
// go upload the attachment
|
||||
ctxt.VarMap().Set("target", returnURL)
|
||||
ctxt.VarMap().Set("post", hdr.PostId)
|
||||
ctxt.VarMap().Set("amsterdam_pageTitle", "Upload Attachment")
|
||||
return "framed_template", "attachment_upload.jet", nil
|
||||
}
|
||||
|
||||
@@ -118,3 +118,86 @@ func AmGetPostRange(ctx context.Context, topic *Topic, first, last int32) ([]*Po
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
/* AmNewPost adds a new post to a topic.
|
||||
* Parameters:
|
||||
* ctx - Standard Go context value.
|
||||
* conf - Pointer to conference containing the topic.
|
||||
* topic - Pointer to topic.
|
||||
* user - Pointer to user posting the message.
|
||||
* pseud - Pseud for the new post.
|
||||
* post - New post text.
|
||||
* postLines - Number of lines in the post text.
|
||||
* Returns:
|
||||
* New post header pointer.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmNewPost(ctx context.Context, conf *Conference, topic *Topic, user *User, pseud string, post string, postLines int32) (*PostHeader, error) {
|
||||
success := false
|
||||
tx := amdb.MustBegin()
|
||||
defer func() {
|
||||
if !success {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
unlock := true
|
||||
tx.ExecContext(ctx, "LOCK TABLES topics WRITE, topicsettings WRITE, posts WRITE, postdata WRITE;")
|
||||
defer func() {
|
||||
if unlock {
|
||||
tx.ExecContext(ctx, "UNLOCK TABLES;")
|
||||
}
|
||||
}()
|
||||
|
||||
// Add the post header information.
|
||||
rs, err := tx.ExecContext(ctx, "INSERT INTO posts (topicid, num, linecount, creator_uid, posted, pseud) VALUES (?, ?, ?, ?, NOW(), ?)",
|
||||
topic.TopicId, topic.TopMessage+1, postLines, user.Uid, pseud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xid, err := rs.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read back the post header.
|
||||
var dbdata []PostHeader
|
||||
if err := tx.SelectContext(ctx, &dbdata, "SELECT * FROM posts WHERE postid = ?", xid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dbdata) == 0 {
|
||||
return nil, errors.New("AmNewPost: new post not found")
|
||||
}
|
||||
if len(dbdata) > 1 {
|
||||
return nil, fmt.Errorf("AmNewPost: too many entries (%d) for post ID %d", len(dbdata), xid)
|
||||
}
|
||||
hdr := &(dbdata[0])
|
||||
|
||||
// Add the post data.
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO postdata (postid, data) VALUES (?, ?)", int32(xid), hdr.PostId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update the topic.
|
||||
_, err = tx.ExecContext(ctx, "UPDATE topics SET top_message = ?, lastupdate = ? WHERE topicid = ?", hdr.Num, hdr.Posted, topic.TopicId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topic.TopMessage = hdr.Num
|
||||
topic.LastUpdate = hdr.Posted
|
||||
|
||||
tx.ExecContext(ctx, "UNLOCK TABLES;")
|
||||
unlock = false
|
||||
|
||||
// update the "last posted" date in the conference settings
|
||||
_, err = conf.TouchPost(ctx, tx, user, hdr.Posted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
success = true
|
||||
return hdr, nil
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ func setupEcho() *echo.Echo {
|
||||
confGroup.GET("/new_topic", ui.AmWrap(NewTopicForm))
|
||||
confGroup.POST("/new_topic", ui.AmWrap(NewTopic))
|
||||
confGroup.GET("/r/:topic", ui.AmWrap(ReadPosts))
|
||||
confGroup.POST("/r/:topic", ui.AmWrap(PostInTopic))
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
+3
-5
@@ -106,11 +106,8 @@
|
||||
<hr class="border-gray-400 mb-6">
|
||||
<h2 class="text-2xl font-bold text-black mb-4">Post Message in "{{ topicName }}":</h2>
|
||||
|
||||
<form method="POST" action="/TODO">
|
||||
<input type="hidden" name="sd" value="4">
|
||||
<input type="hidden" name="conf" value="2">
|
||||
<input type="hidden" name="cc" value="2">
|
||||
<input type="hidden" name="top" value="4">
|
||||
<form method="POST" action="{{ post_stem }}">
|
||||
<input type="hidden" name="xp" value="{{ post_max }}"/>
|
||||
|
||||
<div class="bg-gray-50 p-6 rounded-lg space-y-4">
|
||||
<div>
|
||||
@@ -137,6 +134,7 @@
|
||||
<div class="flex justify-center gap-4">
|
||||
<button type="submit" name="preview" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded font-medium transition-colors">Preview</button>
|
||||
<button type="submit" name="post" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded font-medium transition-colors">Post & Reload</button>
|
||||
<button type="submit" name="postnext" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors">Post & Go Next</button>
|
||||
<button type="submit" name="posttopics" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors">Post & Go Topics</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
{*
|
||||
* Amsterdam Web Communities System
|
||||
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*}
|
||||
<div class="p-4">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-blue-800 text-4xl font-bold inline">Previewing Post</h1>
|
||||
<hr class="border-2 border-gray-400 w-4/5 mt-2 mb-6">
|
||||
</div>
|
||||
|
||||
<!-- Preview Section -->
|
||||
<div class="max-w-3xl mb-8">
|
||||
<!-- Spelling Error Notice -->
|
||||
{{ if nError == 0 }}
|
||||
<div class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-6">
|
||||
<p class="text-sm font-bold text-blue-800">
|
||||
Your post did not contain any spelling errors.
|
||||
</p>
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
|
||||
<p class="text-sm font-bold text-yellow-800">
|
||||
{{ if nError == 1 }}
|
||||
There was 1 spelling error in your post.
|
||||
{{ else }}
|
||||
There were {{ nError }} spelling errors in your post.
|
||||
{{ end }}
|
||||
</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- Preview Display -->
|
||||
<div class="bg-white border-2 border-blue-300 rounded-lg p-6 mb-6">
|
||||
<pre class="amsPost font-mono text-sm whitespace-pre-wrap break-words">{{ previewPb | postRewrite | raw }}</pre>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-400 mb-6">
|
||||
</div>
|
||||
|
||||
<!-- Posting Form -->
|
||||
<div class="max-w-3xl">
|
||||
<form method="POST" action="{{ urlStem }}">
|
||||
<input type="hidden" name="xp" value="{{ maxPost }}"/>
|
||||
<div class="bg-gray-50 p-6 rounded-lg space-y-4">
|
||||
<!-- Your Name/Header and Attach File -->
|
||||
<div>
|
||||
<label for="pseud" class="block text-black text-sm font-medium mb-2">Your name/header:</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<input type="text" id="pseud" name="pseud" size="37" maxlength="255" value="{{ pseud | raw }}"
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" id="attach" name="attach" value="Y"
|
||||
{{ if isset(attachFile) }}checked{{ end }}
|
||||
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
|
||||
<label for="attach" class="text-black text-sm">Attach a file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label for="pb" class="text-black text-sm font-medium">Message:</label>
|
||||
<a href="/TODO/html-reference" target="_blank"
|
||||
class="text-blue-700 hover:text-blue-900 text-sm">HTML Guide</a>
|
||||
</div>
|
||||
<textarea id="pb" name="pb" wrap="soft" rows="7" cols="51"
|
||||
placeholder="Enter your message here. HTML tags are supported..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">{{ pb | raw }}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-center gap-4 pt-4">
|
||||
<button type="submit" name="preview" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded font-medium transition-colors">Preview Again</button>
|
||||
<button type="submit" name="post" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded font-medium transition-colors">Post & Reload</button>
|
||||
<button type="submit" name="postnext" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors">Post & Go Next</button>
|
||||
<button type="submit" name="posttopics" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded font-medium transition-colors">Post & Go Topics</button>
|
||||
<button type="submit" name="cancel" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded font-medium transition-colors">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Posting Guidelines -->
|
||||
<div class="mt-6 p-4 bg-blue-50 border-l-4 border-blue-400">
|
||||
<h3 class="text-sm font-bold text-blue-900 mb-2">Review Your Post:</h3>
|
||||
<ul class="text-xs text-blue-800 space-y-1 list-disc list-inside">
|
||||
<li>Check the preview above to see how your post will appear to others</li>
|
||||
<li>Spelling errors are highlighted in red - you can edit your message to fix them</li>
|
||||
<li>Click "Preview Again" to see updated changes before posting</li>
|
||||
<li>When satisfied, click "Add Topic" to submit your message</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user