mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 04:22:24 -05:00 
			
		
		
		
	[chore/frontend] Tweak threading a bit, inform about hidden replies (#3097)
* [chore/frontend] Tweak threading a bit, inform about hidden replies * whoops * round off bottom of replies col-header if no replies visible
This commit is contained in:
		
					parent
					
						
							
								bbbdf01213
							
						
					
				
			
			
				commit
				
					
						c83e96b8a7
					
				
			
		
					 6 changed files with 90 additions and 102 deletions
				
			
		|  | @ -126,8 +126,17 @@ type WebStatus struct { | |||
| 	// display this status in the web view. | ||||
| 	Indent int | ||||
| 
 | ||||
| 	// This status is the first status after | ||||
| 	// the "main" thread, so it and everything | ||||
| 	// This status is the last visible status | ||||
| 	// in the main thread, so everything below | ||||
| 	// can be considered "replies". | ||||
| 	ThreadLastMain bool | ||||
| 
 | ||||
| 	// This status is the one around which | ||||
| 	// the thread context was constructed. | ||||
| 	ThreadContextStatus bool | ||||
| 
 | ||||
| 	// This status is the first visibile status | ||||
| 	// after the "main" thread, so it and everything | ||||
| 	// below it can be considered "replies". | ||||
| 	ThreadFirstReply bool | ||||
| } | ||||
|  |  | |||
|  | @ -29,15 +29,16 @@ type ThreadContext struct { | |||
| } | ||||
| 
 | ||||
| type WebThreadContext struct { | ||||
| 	// Parents in the thread. | ||||
| 	Ancestors []*WebStatus `json:"ancestors"` | ||||
| 	// Status around which this | ||||
| 	// thread ctx was constructed. | ||||
| 	Status *WebStatus | ||||
| 
 | ||||
| 	// Children in the thread. | ||||
| 	Descendants []*WebStatus `json:"descendants"` | ||||
| 
 | ||||
| 	// The status around which the ancestors | ||||
| 	// + descendants context was constructed. | ||||
| 	Status *WebStatus `json:"-"` | ||||
| 	// Ordered slice of statuses | ||||
| 	// for rendering in template. | ||||
| 	// | ||||
| 	// Includes ancestors, target | ||||
| 	// status, and descendants. | ||||
| 	Statuses []*WebStatus | ||||
| 
 | ||||
| 	// Total length of | ||||
| 	// the main thread. | ||||
|  |  | |||
|  | @ -400,8 +400,7 @@ func (p *Processor) WebContextGet( | |||
| 
 | ||||
| 	// Start preparing web context. | ||||
| 	wCtx := &apimodel.WebThreadContext{ | ||||
| 		Ancestors:   make([]*apimodel.WebStatus, 0, len(iCtx.ancestors)), | ||||
| 		Descendants: make([]*apimodel.WebStatus, 0, len(iCtx.descendants)), | ||||
| 		Statuses: make([]*apimodel.WebStatus, 0, len(wholeThread)), | ||||
| 	} | ||||
| 
 | ||||
| 	var ( | ||||
|  | @ -415,72 +414,70 @@ func (p *Processor) WebContextGet( | |||
| 		// ie., who created first post in the thread. | ||||
| 		contextAcctID = wholeThread[0].AccountID | ||||
| 
 | ||||
| 		// Position of target status in wholeThread, | ||||
| 		// we put it on top of ancestors. | ||||
| 		targetStatusIdx = len(iCtx.ancestors) | ||||
| 
 | ||||
| 		// Position from which we should add | ||||
| 		// to descendants and not to ancestors. | ||||
| 		descendantsIdx = targetStatusIdx + 1 | ||||
| 
 | ||||
| 		// Whether we've reached end of "main" | ||||
| 		// thread and are now looking at replies. | ||||
| 		inReplies bool | ||||
| 
 | ||||
| 		// Index in wholeThread where | ||||
| 		// the "main" thread ends. | ||||
| 		// Index in wholeThread | ||||
| 		// where replies begin. | ||||
| 		firstReplyIdx int | ||||
| 
 | ||||
| 		// We should mark the next **VISIBLE** | ||||
| 		// reply as the first reply. | ||||
| 		markNextVisibleAsReply bool | ||||
| 		markNextVisibleAsFirstReply bool | ||||
| 	) | ||||
| 
 | ||||
| 	for idx, status := range wholeThread { | ||||
| 		if !inReplies { | ||||
| 			// Haven't reached end | ||||
| 			// of "main" thread yet. | ||||
| 			// | ||||
| 			// First post in wholeThread can't | ||||
| 			// be a self reply, so ignore it. | ||||
| 			// | ||||
| 			// That aside, first non-self-reply | ||||
| 			// in wholeThread means the "main" | ||||
| 			// thread is now over. | ||||
| 			if idx != 0 && !isSelfReply(status, contextAcctID) { | ||||
| 				// Jot some stuff down. | ||||
| 				firstReplyIdx = idx | ||||
| 			// Check if we've reached replies | ||||
| 			// by looking for the first status | ||||
| 			// that's not a self-reply, ie., | ||||
| 			// not a post in the "main" thread. | ||||
| 			switch { | ||||
| 			case idx == 0: | ||||
| 				// First post in wholeThread can't | ||||
| 				// be a self reply anyway because | ||||
| 				// it (very likely) doesn't reply | ||||
| 				// to anything, so ignore it. | ||||
| 
 | ||||
| 			case !isSelfReply(status, contextAcctID): | ||||
| 				// This is not a self-reply, which | ||||
| 				// means it's a reply from another | ||||
| 				// account. So, replies start here. | ||||
| 				inReplies = true | ||||
| 				markNextVisibleAsReply = true | ||||
| 				firstReplyIdx = idx | ||||
| 				markNextVisibleAsFirstReply = true | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// Ensure status is actually | ||||
| 		// visible to just anyone. | ||||
| 		// visible to just anyone, and | ||||
| 		// hide / don't include it if not. | ||||
| 		v, err := p.filter.StatusVisible(ctx, nil, status) | ||||
| 		if err != nil || !v { | ||||
| 			// Skip this one. | ||||
| 			if !inReplies { | ||||
| 				// Main thread entry hidden. | ||||
| 				wCtx.ThreadHidden++ | ||||
| 			} else { | ||||
| 				// Reply hidden. | ||||
| 				wCtx.ThreadRepliesHidden++ | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Prepare status to add to thread context. | ||||
| 		apiStatus, err := p.converter.StatusToWebStatus(ctx, status) | ||||
| 		// Prepare visible status to add to thread context. | ||||
| 		webStatus, err := p.converter.StatusToWebStatus(ctx, status) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if markNextVisibleAsReply { | ||||
| 		if markNextVisibleAsFirstReply { | ||||
| 			// This is the first visible | ||||
| 			// "reply / comment", so the | ||||
| 			// little "x amount of replies" | ||||
| 			// header should go above this. | ||||
| 			apiStatus.ThreadFirstReply = true | ||||
| 			markNextVisibleAsReply = false | ||||
| 			webStatus.ThreadFirstReply = true | ||||
| 			markNextVisibleAsFirstReply = false | ||||
| 		} | ||||
| 
 | ||||
| 		// If this is a reply, work out the indent of | ||||
|  | @ -491,59 +488,47 @@ func (p *Processor) WebContextGet( | |||
| 			case !ok: | ||||
| 				// No parent with | ||||
| 				// indent, start at 0. | ||||
| 				apiStatus.Indent = 0 | ||||
| 				webStatus.Indent = 0 | ||||
| 
 | ||||
| 			case isSelfReply(status, status.AccountID): | ||||
| 				// Self reply, so indent at same | ||||
| 				// level as own replied-to status. | ||||
| 				apiStatus.Indent = parentIndent | ||||
| 				webStatus.Indent = parentIndent | ||||
| 
 | ||||
| 			case parentIndent == 5: | ||||
| 				// Already indented as far as we | ||||
| 				// can go to keep things readable | ||||
| 				// on thin screens, so just keep | ||||
| 				// parent's indent. | ||||
| 				apiStatus.Indent = parentIndent | ||||
| 				webStatus.Indent = parentIndent | ||||
| 
 | ||||
| 			default: | ||||
| 				// Reply to someone else who's | ||||
| 				// indented, but not to TO THE MAX. | ||||
| 				// Indent by another one. | ||||
| 				apiStatus.Indent = parentIndent + 1 | ||||
| 				webStatus.Indent = parentIndent + 1 | ||||
| 			} | ||||
| 
 | ||||
| 			// Store the indent for this status. | ||||
| 			statusIndents[status.ID] = apiStatus.Indent | ||||
| 			statusIndents[status.ID] = webStatus.Indent | ||||
| 		} | ||||
| 
 | ||||
| 		switch { | ||||
| 		case idx == targetStatusIdx: | ||||
| 			// This is the target status itself. | ||||
| 			wCtx.Status = apiStatus | ||||
| 
 | ||||
| 		case idx < descendantsIdx: | ||||
| 			// Haven't reached descendants yet, | ||||
| 			// so this must be an ancestor. | ||||
| 			wCtx.Ancestors = append( | ||||
| 				wCtx.Ancestors, | ||||
| 				apiStatus, | ||||
| 			) | ||||
| 
 | ||||
| 		default: | ||||
| 			// We're in descendants town now. | ||||
| 			wCtx.Descendants = append( | ||||
| 				wCtx.Descendants, | ||||
| 				apiStatus, | ||||
| 			) | ||||
| 		if webStatus.ID == targetStatusID { | ||||
| 			// This is the og | ||||
| 			// thread context status. | ||||
| 			webStatus.ThreadContextStatus = true | ||||
| 			wCtx.Status = webStatus | ||||
| 		} | ||||
| 
 | ||||
| 		wCtx.Statuses = append(wCtx.Statuses, webStatus) | ||||
| 	} | ||||
| 
 | ||||
| 	// Now we've gone through the whole | ||||
| 	// thread, we can add some additional info. | ||||
| 
 | ||||
| 	// Length of the "main" thread. If there are | ||||
| 	// replies then it's up to where the replies | ||||
| 	// start, otherwise it's the whole thing. | ||||
| 	// visible replies then it's up to where the | ||||
| 	// replies start, else it's the whole thing. | ||||
| 	if inReplies { | ||||
| 		wCtx.ThreadLength = firstReplyIdx | ||||
| 	} else { | ||||
|  | @ -553,6 +538,9 @@ func (p *Processor) WebContextGet( | |||
| 	// Jot down number of hidden posts so template doesn't have to do it. | ||||
| 	wCtx.ThreadShown = wCtx.ThreadLength - wCtx.ThreadHidden | ||||
| 
 | ||||
| 	// Mark the last "main" visible status. | ||||
| 	wCtx.Statuses[wCtx.ThreadShown-1].ThreadLastMain = true | ||||
| 
 | ||||
| 	// Number of replies is equal to number | ||||
| 	// of statuses in the thread that aren't | ||||
| 	// part of the "main" thread. | ||||
|  |  | |||
|  | @ -1014,6 +1014,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() { | |||
|   "PollOptions": null, | ||||
|   "Local": false, | ||||
|   "Indent": 0, | ||||
|   "ThreadLastMain": false, | ||||
|   "ThreadContextStatus": false, | ||||
|   "ThreadFirstReply": false | ||||
| }`, string(b)) | ||||
| } | ||||
|  |  | |||
|  | @ -42,6 +42,15 @@ | |||
| 		h2 { | ||||
| 			margin-right: auto; | ||||
| 		} | ||||
| 
 | ||||
| 		&.replies.hidden-only { | ||||
| 			/* | ||||
| 				No visible replies below this column | ||||
| 				header, so round off the bottom. | ||||
| 			*/ | ||||
| 			border-bottom-left-radius: $br; | ||||
| 			border-bottom-right-radius: $br; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.status { | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
| {{- define "repliesSummary" -}} | ||||
|     {{- if .context.ThreadRepliesShown -}} | ||||
|         {{- if .context.ThreadRepliesHidden -}} | ||||
|             {{- if eq .context.ThreadReplies 1 -}} | ||||
|             {{- if eq .context.ThreadRepliesShown 1 -}} | ||||
|                 {{- /* Some replies are hidden. */ -}} | ||||
|                 {{ .context.ThreadRepliesShown }} visible reply | ||||
|             {{- else if gt .context.ThreadRepliesShown 1 -}} | ||||
|  | @ -35,6 +35,8 @@ | |||
|                 {{ .context.ThreadReplies }} replies | ||||
|             {{- end -}} | ||||
|         {{- end -}} | ||||
|     {{- else -}} | ||||
|         {{- .context.ThreadRepliesHidden }} {{ if eq .context.ThreadRepliesHidden 1 }}reply{{ else }}replies{{ end }} hidden or not public | ||||
|     {{- end -}} | ||||
| {{- end -}} | ||||
| 
 | ||||
|  | @ -60,7 +62,7 @@ | |||
| {{- with . }} | ||||
| </section> | ||||
| <section class="thread thread-replies" aria-labelledby="replies" open> | ||||
|     <div class="col-header replies"> | ||||
|     <div class="col-header replies{{- if not .context.ThreadRepliesShown }} hidden-only{{- end -}}"> | ||||
|         <h2 id="replies">{{- template "repliesSummary" . -}}</h2> | ||||
|         <a href="#thread-summary">back to top</a> | ||||
|     </div> | ||||
|  | @ -77,41 +79,18 @@ | |||
|             {{- end }} | ||||
|         </div> | ||||
| 
 | ||||
|         {{- range $thisStatus := .context.Ancestors }} | ||||
|         {{- if $thisStatus.ThreadFirstReply }} | ||||
|         {{- range $status := .context.Statuses }} | ||||
|         <article | ||||
|             class="status{{- if $status.ThreadContextStatus }} expanded{{- end -}}{{- if $status.Indent }} indent-{{ $status.Indent }}{{- end -}}" | ||||
|             {{- includeAttr "status_attributes.tmpl" $status | indentAttr 3 }} | ||||
|         > | ||||
|             {{- include "status.tmpl" $status | indent 3 }} | ||||
|         </article> | ||||
|         {{- if and $status.ThreadLastMain $.context.ThreadReplies }} | ||||
|         {{- include "repliesStart" $ | indent 1 }} | ||||
|         {{- end }} | ||||
|         <article | ||||
|             class="status{{- if $thisStatus.Indent }} indent-{{ $thisStatus.Indent }}{{- end -}}" | ||||
|             {{- includeAttr "status_attributes.tmpl" $thisStatus | indentAttr 3 }} | ||||
|         > | ||||
|             {{- include "status.tmpl" $thisStatus | indent 3 }} | ||||
|         </article> | ||||
|         {{- end }} | ||||
| 
 | ||||
|         {{- with $thisStatus := .context.Status }} | ||||
|         {{- if $thisStatus.ThreadFirstReply }} | ||||
|         {{- include "repliesStart" $ | indent 1 }} | ||||
|         {{- end }} | ||||
|         <article | ||||
|             class="status expanded{{- if $thisStatus.Indent }} indent-{{ $thisStatus.Indent }}{{- end -}}" | ||||
|             {{- includeAttr "status_attributes.tmpl" $thisStatus | indentAttr 3  }} | ||||
|         > | ||||
|             {{- include "status.tmpl" $thisStatus | indent 3 }} | ||||
|         </article> | ||||
|         {{- end }} | ||||
| 
 | ||||
|         {{- range $thisStatus := .context.Descendants }} | ||||
|         {{- if $thisStatus.ThreadFirstReply }} | ||||
|         {{- include "repliesStart" $ | indent 1 }} | ||||
|         {{- end }} | ||||
|         <article | ||||
|             class="status{{- if $thisStatus.Indent }} indent-{{ $thisStatus.Indent }}{{- end -}}" | ||||
|             {{- includeAttr "status_attributes.tmpl" $thisStatus | indentAttr 3 }} | ||||
|         > | ||||
|             {{- include "status.tmpl" $thisStatus | indent 3 }} | ||||
|         </article> | ||||
|         {{- end }} | ||||
|     {{- if .context.ThreadReplies }} | ||||
|     </section> | ||||
|     {{- end }} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue