landed AmSearchPosts and the "Find Posts" functionality on the main Find page
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PostHeader represents the "header" of a post, everything except for its text and attachment.
|
// PostHeader represents the "header" of a post, everything except for its text and attachment.
|
||||||
@@ -718,3 +719,241 @@ func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, error) {
|
|||||||
|
|
||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PostSearchResult struct {
|
||||||
|
PostLink string
|
||||||
|
Author string
|
||||||
|
PostDate time.Time
|
||||||
|
Lines int32
|
||||||
|
Excerpt string
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXCERPT_MAX = 60 // temporary implementation
|
||||||
|
|
||||||
|
// decodeSearchScope turns the scope values from the AmSearchPosts call into a set of coherent values.
|
||||||
|
func decodeSearchScope(ctx context.Context, scopeValues []any) (string, *Community, *Conference, *Topic, error) {
|
||||||
|
var myComm *Community = nil
|
||||||
|
var myConf *Conference = nil
|
||||||
|
var myTopic *Topic = nil
|
||||||
|
|
||||||
|
// Sort the items in the scopeValues array and fill them in the right slots.
|
||||||
|
for i := range scopeValues {
|
||||||
|
if scopeValues[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if thisComm, ok := scopeValues[i].(*Community); ok {
|
||||||
|
if myComm != nil {
|
||||||
|
return "error", nil, nil, nil, errors.New("cannot specify multiple communities")
|
||||||
|
}
|
||||||
|
myComm = thisComm
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if thisConf, ok := scopeValues[i].(*Conference); ok {
|
||||||
|
if myConf != nil {
|
||||||
|
return "error", nil, nil, nil, errors.New("cannot specify multiple conferences")
|
||||||
|
}
|
||||||
|
myConf = thisConf
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if thisTopic, ok := scopeValues[i].(*Topic); ok {
|
||||||
|
if myTopic != nil {
|
||||||
|
return "error", nil, nil, nil, errors.New("cannot specify multiple topics")
|
||||||
|
}
|
||||||
|
myTopic = thisTopic
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "error", nil, nil, nil, errors.New("invalid item specified in scope")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on which slots are full, determine the scope. Also error-check relations between the specified slots.
|
||||||
|
if myComm == nil {
|
||||||
|
if myConf != nil || myTopic != nil {
|
||||||
|
return "error", nil, nil, nil, errors.New("conference/topic specified without community")
|
||||||
|
}
|
||||||
|
return "global", nil, nil, nil, nil
|
||||||
|
}
|
||||||
|
if myConf == nil {
|
||||||
|
if myTopic != nil {
|
||||||
|
return "error", nil, nil, nil, errors.New("topic specified without conference")
|
||||||
|
}
|
||||||
|
return "community", myComm, nil, nil, nil
|
||||||
|
}
|
||||||
|
f, err := myConf.InCommunity(ctx, myComm)
|
||||||
|
if err != nil {
|
||||||
|
return "error", nil, nil, nil, err
|
||||||
|
}
|
||||||
|
if !f {
|
||||||
|
return "error", nil, nil, nil, errors.New("community does not contain conference")
|
||||||
|
}
|
||||||
|
if myTopic == nil {
|
||||||
|
return "conference", myComm, myConf, nil, nil
|
||||||
|
}
|
||||||
|
if myTopic.ConfId != myConf.ConfId {
|
||||||
|
return "error", nil, nil, nil, errors.New("conference does not contain topic")
|
||||||
|
}
|
||||||
|
return "topic", myComm, myConf, myTopic, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AmSearchPosts finds posts by using full text search on their contents.
|
||||||
|
* Parameters:
|
||||||
|
* ctx - Standard Go context value.
|
||||||
|
* searchTerms - The terms to search for in the text.
|
||||||
|
* u - The user performing the search.
|
||||||
|
* offset - How many posts in the results to skip.
|
||||||
|
* max - Maximum number of posts to return.
|
||||||
|
* scopeValues - Multiple object values to limit the scope. Put a Community pointer here to limit the scope to
|
||||||
|
* that community. Also add a Conference pointer (from that community) to limit the scope to that conference.
|
||||||
|
* Also add a Topic pointer (from that conference) to limit the scope to that topic.
|
||||||
|
* Returns:
|
||||||
|
* Array of PostSearchResult structures with the results.
|
||||||
|
* Total number of posts that match the search.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max int, scopeValues ...any) ([]PostSearchResult, int, error) {
|
||||||
|
// Decode the search scope.
|
||||||
|
scope, comm, conf, topic, err := decodeSearchScope(ctx, scopeValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the proper service index to match against the community services.
|
||||||
|
confService, err := AmGetServiceIndex("community", "Conference")
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the count of matching posts.
|
||||||
|
var row *sql.Row
|
||||||
|
switch scope {
|
||||||
|
case "global":
|
||||||
|
row = amdb.QueryRowContext(ctx, `SELECT COUNT(*)
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, searchTerms)
|
||||||
|
case "community":
|
||||||
|
row = amdb.QueryRowContext(ctx, `SELECT COUNT(*)
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND q.commid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, comm.Id, searchTerms)
|
||||||
|
case "conference":
|
||||||
|
row = amdb.QueryRowContext(ctx, `SELECT COUNT(*)
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND q.commid = ? AND c.confid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, comm.Id, conf.ConfId, searchTerms)
|
||||||
|
case "topic":
|
||||||
|
row = amdb.QueryRowContext(ctx, `SELECT COUNT(*)
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND q.commid = ? AND c.confid = ? AND t.topicid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`,
|
||||||
|
u.Uid, confService, comm.Id, conf.ConfId, topic.TopicId, searchTerms)
|
||||||
|
}
|
||||||
|
var count int
|
||||||
|
err = row.Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("AmSearchPosts query 1 error %v", err)
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the matching posts themselves.
|
||||||
|
var rs *sql.Rows
|
||||||
|
switch scope {
|
||||||
|
case "global":
|
||||||
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
|
LIMIT ? OFFSET ?`, u.Uid, confService, searchTerms, max, offset)
|
||||||
|
case "community":
|
||||||
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND q.commid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
|
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, searchTerms, max, offset)
|
||||||
|
case "conference":
|
||||||
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND q.commid = ? AND c.confid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
|
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, conf.ConfId, searchTerms, max, offset)
|
||||||
|
case "topic":
|
||||||
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
|
JOIN topics t ON t.confid = c.confid JOIN posts p ON p.topicid = t.topicid JOIN postdata d ON d.postid = p.postid JOIN users u2 ON u2.uid = p.creator_uid
|
||||||
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,IFNULL(x.granted_lvl,0)) >= c.read_lvl
|
||||||
|
AND q.commid = ? AND c.confid = ? AND t.topicid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
|
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, conf.ConfId, topic.TopicId, searchTerms, max, offset)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("AmSearchPosts query 2 error %v", err)
|
||||||
|
return nil, count, err
|
||||||
|
}
|
||||||
|
rc := make([]PostSearchResult, max)
|
||||||
|
i := 0
|
||||||
|
for rs.Next() {
|
||||||
|
var commid int32
|
||||||
|
var commAlias string
|
||||||
|
var confid int32
|
||||||
|
var topicid int32
|
||||||
|
var topicNum int16
|
||||||
|
var postid int64
|
||||||
|
var postnum int32
|
||||||
|
err := rs.Scan(&commid, &commAlias, &confid, &topicid, &topicNum, &postid, &postnum, &(rc[i].Author), &(rc[i].PostDate),
|
||||||
|
&(rc[i].Lines), &(rc[i].Excerpt))
|
||||||
|
if err != nil {
|
||||||
|
return nil, count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get conference so we can get aliases.
|
||||||
|
conf, err := AmGetConference(ctx, confid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, count, err
|
||||||
|
}
|
||||||
|
alias, err := conf.Aliases(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the post link.
|
||||||
|
plink := AmCreatePostLinkContext(commAlias, alias[0], topicNum)
|
||||||
|
plink.FirstPost = postnum
|
||||||
|
plink.LastPost = postnum
|
||||||
|
rc[i].PostLink = plink.AsString()
|
||||||
|
|
||||||
|
// Trim down the excerpt.
|
||||||
|
if len(rc[i].Excerpt) > EXCERPT_MAX {
|
||||||
|
choplen := min(len(rc[i].Excerpt), EXCERPT_MAX*3)
|
||||||
|
tmp := []rune(rc[i].Excerpt[:choplen])
|
||||||
|
choplen = min(len(tmp), EXCERPT_MAX)
|
||||||
|
rc[i].Excerpt = fmt.Sprintf("%s...", string(tmp[:choplen]))
|
||||||
|
}
|
||||||
|
i++ // go on to the next
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < max {
|
||||||
|
rc = rc[:i] // slice off any empty entries at the end
|
||||||
|
}
|
||||||
|
return rc, count, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ package database
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"errors"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -123,6 +124,23 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AmGetServiceIndex returns the service index for the given service by domain and identifier.
|
||||||
|
* Parameters:
|
||||||
|
* domain - The domain of the service to look for.
|
||||||
|
* id - The identifier of the service.
|
||||||
|
* Returns:
|
||||||
|
* The service index, if the service is found.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmGetServiceIndex(domain, id string) (int16, error) {
|
||||||
|
if d, ok := serviceRoot.byName[domain]; ok {
|
||||||
|
if svc, ok2 := d.byId[id]; ok2 {
|
||||||
|
return svc.Index, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, errors.New("service not found")
|
||||||
|
}
|
||||||
|
|
||||||
/* AmGetCommunityServices returns all the community service definitions for a community.
|
/* AmGetCommunityServices returns all the community service definitions for a community.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctx - Standard Go context value.
|
* ctx - Standard Go context value.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ _(italicized items can be deferred)_
|
|||||||
- ~~Topics list: Set up Conference permalink~~
|
- ~~Topics list: Set up Conference permalink~~
|
||||||
- ~~Send out E-mails to topic subscribers when a post is made~~
|
- ~~Send out E-mails to topic subscribers when a post is made~~
|
||||||
- _Error handling: shift titles and templates for different error codes_
|
- _Error handling: shift titles and templates for different error codes_
|
||||||
- Find Posts
|
- ~~Find Posts~~
|
||||||
- Services mechanism: Conference vtable
|
- Services mechanism: Conference vtable
|
||||||
- ~~User creation: copy conference hotlists ~~
|
- ~~User creation: copy conference hotlists ~~
|
||||||
- _Calendar (top menu link)_
|
- _Calendar (top menu link)_
|
||||||
@@ -18,6 +18,8 @@ _(italicized items can be deferred)_
|
|||||||
- User Account Management
|
- User Account Management
|
||||||
- System Audit Logs
|
- System Audit Logs
|
||||||
- Import User Accounts
|
- Import User Accounts
|
||||||
|
- Conferences list:
|
||||||
|
- Find
|
||||||
- Community Admin Menu:
|
- Community Admin Menu:
|
||||||
- Set Community Category
|
- Set Community Category
|
||||||
- Set Community Services
|
- Set Community Services
|
||||||
|
|||||||
@@ -279,7 +279,12 @@ func Find(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "PST":
|
case "PST":
|
||||||
// TODO
|
var postlist []database.PostSearchResult
|
||||||
|
postlist, total, err = database.AmSearchPosts(ctxt.Ctx(), term, ctxt.CurrentUser(), ofs*listMax, listMax)
|
||||||
|
if err == nil {
|
||||||
|
numResults = len(postlist)
|
||||||
|
ctxt.VarMap().Set("resultList", postlist)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
|
|||||||
+6
-14
@@ -218,22 +218,14 @@
|
|||||||
{{ range _, rx := resultList }}
|
{{ range _, rx := resultList }}
|
||||||
<tr class="hover:bg-blue-50">
|
<tr class="hover:bg-blue-50">
|
||||||
<td class="px-4 py-3 text-sm">
|
<td class="px-4 py-3 text-sm">
|
||||||
<a href="http://necrovenice:8080/venice/go/minds!Playground.2.880"
|
<a href="/go/{{ rx.PostLink }}" class="text-blue-700 hover:text-blue-900 font-mono">{{ rx.PostLink }}</a>
|
||||||
class="text-blue-700 hover:text-blue-900 font-mono">minds!Playground.2.880</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm">
|
<td class="px-4 py-3 text-sm">
|
||||||
<a href="http://necrovenice:8080/venice/user/Beenherebefo"
|
<a href="/user/{{ rx.Author }}" class="text-blue-700 hover:text-blue-900">{{ rx.Author }}</a>
|
||||||
class="text-blue-700 hover:text-blue-900">Beenherebefo</a>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm whitespace-nowrap text-gray-600">
|
|
||||||
Jan 1, 2002 10:57:06 PM
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600">
|
|
||||||
12
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 italic">
|
|
||||||
Bold, the truth is that one entity can incorporate in only...
|
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm whitespace-nowrap text-gray-600">{{ DisplayDateTime( rx.PostDate, .) }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600">{{ rx.Lines }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-600 italic">{{ rx.Excerpt }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -241,7 +233,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ if isset(resultShowPrev) || isset(resuiltShowNext) }}
|
{{ if isset(resultShowPrev) || isset(resultShowNext) }}
|
||||||
<!-- Bottom Navigation -->
|
<!-- Bottom Navigation -->
|
||||||
<div class="flex justify-end mt-6">
|
<div class="flex justify-end mt-6">
|
||||||
<form method="POST" action="/find">
|
<form method="POST" action="/find">
|
||||||
|
|||||||
Reference in New Issue
Block a user