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