forked from tangled.org/core
this repo has no description

appview/issues: switch to discussions-style

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 045b2783 45c81020

verified
Changed files
+145 -11
appview
+4 -3
appview/ingester.go
···
"tangled.sh/tangled.sh/core/appview/db"
"tangled.sh/tangled.sh/core/appview/pages/markup"
"tangled.sh/tangled.sh/core/appview/serververify"
+
"tangled.sh/tangled.sh/core/appview/validator"
"tangled.sh/tangled.sh/core/idresolver"
"tangled.sh/tangled.sh/core/rbac"
)
···
IdResolver *idresolver.Resolver
Config *config.Config
Logger *slog.Logger
+
Validator *validator.Validator
}
type processFunc func(ctx context.Context, e *models.Event) error
···
return fmt.Errorf("failed to parse comment from record: %w", err)
}
-
sanitizer := markup.NewSanitizer()
-
if sb := strings.TrimSpace(sanitizer.SanitizeDefault(comment.Body)); sb == "" {
-
return fmt.Errorf("body is empty after HTML sanitization")
+
if err := i.Validator.ValidateIssueComment(comment); err != nil {
+
return fmt.Errorf("failed to validate comment: %w", err)
}
_, err = db.AddIssueComment(ddb, *comment)
+1 -1
appview/issues/issues.go
···
db *db.DB,
config *config.Config,
notifier notify.Notifier,
+
validator *validator.Validator,
) *Issues {
return &Issues{
oauth: oauth,
···
Reactions: reactionCountMap,
UserReacted: userReactions,
})
-
}
func (rp *Issues) CloseIssue(w http.ResponseWriter, r *http.Request) {
+2 -2
appview/pages/templates/layouts/repobase.html
···
</section>
<section
-
class="w-full flex flex-col drop-shadow-sm"
+
class="w-full flex flex-col"
>
<nav class="w-full pl-4 overflow-auto">
<div class="flex z-60">
···
</div>
</nav>
<section
-
class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white"
+
class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white"
>
{{ block "repoContent" . }}{{ end }}
</section>
+64
appview/pages/templates/repo/issues/fragments/commentList.html
···
+
{{ define "repo/issues/fragments/commentList" }}
+
<div class="flex flex-col gap-8">
+
{{ range $item := .CommentList }}
+
{{ template "commentListing" (list $ .) }}
+
{{ end }}
+
<div>
+
{{ end }}
+
+
{{ define "commentListing" }}
+
{{ $root := index . 0 }}
+
{{ $comment := index . 1 }}
+
{{ $params :=
+
(dict
+
"RepoInfo" $root.RepoInfo
+
"LoggedInUser" $root.LoggedInUser
+
"Issue" $root.Issue
+
"Comment" $comment.Self) }}
+
+
<div class="rounded border border-gray-300 dark:border-gray-700 w-full overflow-hidden shadow-sm">
+
{{ template "topLevelComment" $params }}
+
+
<div class="relative ml-4 border-l border-gray-300 dark:border-gray-700">
+
{{ range $index, $reply := $comment.Replies }}
+
<div class="relative ">
+
<!-- Horizontal connector -->
+
<div class="absolute left-0 top-6 w-4 h-px bg-gray-300 dark:bg-gray-700"></div>
+
+
<div class="pl-2">
+
{{
+
template "replyComment"
+
(dict
+
"RepoInfo" $root.RepoInfo
+
"LoggedInUser" $root.LoggedInUser
+
"Issue" $root.Issue
+
"Comment" $reply)
+
}}
+
</div>
+
</div>
+
{{ end }}
+
</div>
+
+
{{ if $root.LoggedInUser }}
+
{{ template "repo/issues/fragments/replyIssueCommentPlaceholder" $params }}
+
{{ else }}
+
<div class="p-2 border-t border-gray-300 dark:border-gray-700 text-gray-500 dark:text-gray-400">
+
<a class="underline" href="/login">login</a> to reply to this discussion
+
</div>
+
{{ end }}
+
</div>
+
{{ end }}
+
+
{{ define "topLevelComment" }}
+
<div class="rounded px-6 py-4 bg-white dark:bg-gray-800">
+
{{ template "repo/issues/fragments/issueCommentHeader" . }}
+
{{ template "repo/issues/fragments/issueCommentBody" . }}
+
</div>
+
{{ end }}
+
+
{{ define "replyComment" }}
+
<div class="p-4 w-full mx-auto overflow-hidden">
+
{{ template "repo/issues/fragments/issueCommentHeader" . }}
+
{{ template "repo/issues/fragments/issueCommentBody" . }}
+
</div>
+
{{ end }}
+18
appview/pages/templates/repo/issues/fragments/replyIssueCommentPlaceholder.html
···
+
{{ define "repo/issues/fragments/replyIssueCommentPlaceholder" }}
+
<div class="p-2 border-t flex gap-2 items-center border-gray-300 dark:border-gray-700">
+
<img
+
src="{{ tinyAvatar .LoggedInUser.Did }}"
+
alt=""
+
class="rounded-full h-6 w-6 mr-1 border border-gray-300 dark:border-gray-700"
+
/>
+
<input
+
class="w-full py-2 border-none focus:outline-none"
+
placeholder="Leave a reply..."
+
hx-get="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment/{{ .Comment.Id }}/reply"
+
hx-trigger="focus"
+
hx-target="closest div"
+
hx-swap="outerHTML"
+
>
+
</input>
+
</div>
+
{{ end }}
+2 -2
appview/pages/templates/repo/issues/issue.html
···
{{ end }}
{{ define "repoContent" }}
-
<header class="pb-4">
+
<header class="pb-2">
<h1 class="text-2xl">
{{ .Issue.Title | description }}
<span class="text-gray-500 dark:text-gray-400">#{{ .Issue.IssueId }}</span>
···
</div>
{{ if .Issue.Body }}
-
<article id="body" class="mt-8 prose dark:prose-invert">
+
<article id="body" class="mt-4 prose dark:prose-invert">
{{ .Issue.Body | markdown }}
</article>
{{ end }}
+1 -1
appview/state/router.go
···
}
func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler {
-
issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier)
+
issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier, s.validator)
return issues.Router(mw)
}
+5 -2
appview/state/state.go
···
"tangled.sh/tangled.sh/core/appview/pages"
posthogService "tangled.sh/tangled.sh/core/appview/posthog"
"tangled.sh/tangled.sh/core/appview/reporesolver"
+
"tangled.sh/tangled.sh/core/appview/validator"
xrpcclient "tangled.sh/tangled.sh/core/appview/xrpcclient"
"tangled.sh/tangled.sh/core/eventconsumer"
"tangled.sh/tangled.sh/core/idresolver"
···
knotstream *eventconsumer.Consumer
spindlestream *eventconsumer.Consumer
logger *slog.Logger
+
validator *validator.Validator
}
func Make(ctx context.Context, config *config.Config) (*State, error) {
···
}
pgs := pages.NewPages(config, res)
-
cache := cache.New(config.Redis.Addr)
sess := session.New(cache)
-
oauth := oauth.NewOAuth(config, sess)
+
validator := validator.New(d)
posthog, err := posthog.NewWithConfig(config.Posthog.ApiKey, posthog.Config{Endpoint: config.Posthog.Endpoint})
if err != nil {
···
IdResolver: res,
Config: config,
Logger: tlog.New("ingester"),
+
Validator: validator,
}
err = jc.StartJetstream(ctx, ingester.Ingest())
if err != nil {
···
knotstream,
spindlestream,
slog.Default(),
+
validator,
}
return state, nil
+35
appview/validator/issue.go
···
+
package validator
+
+
import (
+
"fmt"
+
"strings"
+
+
"tangled.sh/tangled.sh/core/appview/db"
+
"tangled.sh/tangled.sh/core/appview/pages/markup"
+
)
+
+
func (v *Validator) ValidateIssueComment(comment *db.IssueComment) error {
+
// if comments have parents, only ingest ones that are 1 level deep
+
if comment.ReplyTo != nil {
+
parents, err := db.GetIssueComments(v.db, db.FilterEq("at_uri", *comment.ReplyTo))
+
if err != nil {
+
return fmt.Errorf("failed to fetch parent comment: %w", err)
+
}
+
if len(parents) != 1 {
+
return fmt.Errorf("incorrect number of parent comments returned: %d", len(parents))
+
}
+
+
// depth check
+
parent := parents[0]
+
if parent.ReplyTo != nil {
+
return fmt.Errorf("incorrect depth, this comment is replying at depth >1")
+
}
+
}
+
+
sanitizer := markup.NewSanitizer()
+
if sb := strings.TrimSpace(sanitizer.SanitizeDefault(comment.Body)); sb == "" {
+
return fmt.Errorf("body is empty after HTML sanitization")
+
}
+
+
return nil
+
}
+13
appview/validator/validator.go
···
+
package validator
+
+
import "tangled.sh/tangled.sh/core/appview/db"
+
+
type Validator struct {
+
db *db.DB
+
}
+
+
func New(db *db.DB) *Validator {
+
return &Validator{
+
db: db,
+
}
+
}