Skip to content

Commit 5141a02

Browse files
committed
Show chat purpose and the change dialog, if clicking title in topbar
The chat in-page title can be far far away, if the chat is long (many messages), don't want to scroll all the way up to the top. Also, fix a highlight-sidebar bug: calling `undefined.fn()` if not already open.
1 parent bc698d7 commit 5141a02

15 files changed

Lines changed: 186 additions & 14 deletions

File tree

client/app-more/page-dialogs/ChangePageModal.more.ts

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ interface ChangePageDiagState extends ChangePageDiagParams {
3131
store: Store
3232
isOpen: Bo
3333
atRect: Rect
34+
pullLeft?: Bo
35+
windowWidth?: Nr
3436
}
3537

3638

@@ -69,6 +71,9 @@ const ChangePageDialog = createComponent({
6971
...props,
7072
isOpen: true,
7173
atRect,
74+
pullLeft: props.pullLeft ||
75+
props.showViewAnswerButton, // (hack) then it's the icon to the left of the title
76+
windowWidth: window.innerWidth,
7277
} satisfies Partial<ChangePageDiagState>);
7378
},
7479

@@ -90,6 +95,10 @@ const ChangePageDialog = createComponent({
9095
const isOwnOrCore = isOwnOrStaff || user_isTrustMinNotThreat(me, TrustLevel.CoreMember);
9196
const isStaffOrTrusted = isStaff(me) || user_isTrustMinNotThreat(me, TrustLevel.Trusted);
9297

98+
let anyChatPurpose: RElm | U;
99+
let anyViewMembersBtn: RElm | U;
100+
let anyEditPurposeBtn: RElm | U;
101+
let anyLeaveButton: RElm | U;
93102
let anyViewAnswerButton;
94103
let changeStatusTitle;
95104
let setNewListItem;
@@ -138,6 +147,59 @@ const ChangePageDialog = createComponent({
138147

139148
const origPost = page.postsByNr[BodyNr];
140149

150+
const isChat = page_isChat(page.pageRole);
151+
if (isChat) {
152+
anyChatPurpose = rFr({},
153+
// Maybe it's obvious that this text is the chat purpose? It'd typically start with
154+
// "Here you can ...". Could skip? Or, show the title again, with an edit button
155+
// just after? And an edit button after the purpose, too?
156+
r.div({ className: 's_ExplDrp_Ttl n_Purp' }, t.c.Purpose), // [chat_purpose_header]
157+
r.div({ className: 's_ExplDrp_DescIt' },
158+
debiki2.page.PostBody({ store, post: origPost })));
159+
160+
// A list chat members button, unless it's a joinless chat (which one doesn't need
161+
// to join to post messages, doesn't have members (unless type changed to joinless,
162+
// when there already were members, hmm)). [0_joinless_chat_members]
163+
anyViewMembersBtn = page.pageRole === PageRole.JoinlessChat ? null :
164+
r.div({ className: 's_ExplDrp_ActIt' },
165+
Button({
166+
onClick: () => {
167+
// REFACTOR: Break out fn! [break_out_view_chat_membs_fn]
168+
ReactActions.setPagebarOpen(true);
169+
sidebar.contextBar.showUsers();
170+
setTimeout(() => { // [highl_contextbar]
171+
sidebar.contextBar.highligtDuringMillis(700);
172+
})
173+
this.close();
174+
}},
175+
// Elsewhere, id: 'e2eWB_ViewPeopleB'
176+
r.span({ className: 'e_ChPgD_ViewMembB' }, t.wb.ViewChatMembers),
177+
));
178+
179+
const isChatMember = me_isPageMember(me, page);
180+
181+
// UX BUG, harmles: Can leave chat page, when editor open & typing a message already.
182+
// Then, when submitting, there's this error: [leave_chat_ux_bug]
183+
// "not a member of this chat channel [EsE4UGY7]".
184+
anyLeaveButton = !isChatMember ? null : r.div({ className: 's_ExplDrp_ActIt' },
185+
Button({
186+
onClick: () => {
187+
Server.leavePage();
188+
this.close();
189+
}},
190+
// Elsewhere, id: 'e2eWB_LeaveB'
191+
r.span({ className: 'e_ChPgD_LeaveB' }, t.wb.LeaveThisChat),
192+
));
193+
194+
anyEditPurposeBtn = !isOwnOrStaff ? null : r.div({ className: 's_ExplDrp_ActIt' },
195+
Button({ className: 'e_ChPgD_EdPurpB',
196+
onClick: () => {
197+
editor.openToEditChatTitleAndPurpose();
198+
this.close();
199+
}},
200+
t.wb.EditChat));
201+
}
202+
141203
// Ideas and Problems can be solved [tpc_typ_solv], and then
142204
// pat cannot change their doing status, unless un-selecting
143205
// the solution post.
@@ -309,11 +371,46 @@ const ChangePageDialog = createComponent({
309371
}
310372
}
311373

374+
// If ever opening, although empty, that'd be a bug. [empty_change_page_dlg]
375+
// (It's safe to forget to update this list — there'll be a debug build assertion failure.)
376+
// @ifdef DEBUG
377+
dieIf(state.isOpen && !(
378+
anyChatPurpose
379+
|| anyViewMembersBtn
380+
|| anyLeaveButton
381+
|| anyEditPurposeBtn
382+
|| anyViewAnswerButton
383+
|| changeStatusTitle
384+
|| setNewListItem
385+
|| setPlannedListItem
386+
|| setStartedListItem
387+
|| setDoneListItem
388+
|| assignBtn
389+
|| changeCategoryListItem
390+
|| changeTopicTypeListItem
391+
|| reopenListItem
392+
|| closeListItem
393+
|| deletePageListItem
394+
|| undeletePageListItem
395+
|| changeComtOrderListItem
396+
|| changeComtNestingListItem), 'TyEEMPYTCHAPAD');
397+
// @endif
398+
312399
return (
313400
DropdownModal({ show: state.isOpen, onHide: this.close, atRect: state.atRect,
314-
pullLeft: state.showViewAnswerButton, // (hack) then it's the icon to the left of the title
315-
showCloseButton: true, dialogClassName2: 's_ChPgD' },
401+
pullLeft: state.pullLeft, windowWidth: state.windowWidth,
402+
showCloseButton: true, dialogClassName2: 's_ChPgD',
403+
allowWide: !!anyChatPurpose,
404+
},
405+
anyChatPurpose,
406+
anyViewMembersBtn,
407+
anyLeaveButton,
408+
anyEditPurposeBtn,
316409
anyViewAnswerButton,
410+
// If is a chat, and pat is a mod or the page owner, then, there's chat related
411+
// buttons above, and alter page buttons below — then, nice with a separator line
412+
// in between.
413+
isOwnOrStaff && anyChatPurpose ? r.hr() : null,
317414
changeStatusTitle,
318415
setNewListItem,
319416
setPlannedListItem,

client/app-slim/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,6 +2693,7 @@ interface ShowNewPageParams {
26932693
interface ChangePageDiagParams {
26942694
page: Page,
26952695
showViewAnswerButton?: true,
2696+
pullLeft?: Bo
26962697
}
26972698

26982699

@@ -2991,6 +2992,7 @@ interface DropdownProps extends SharedDiagParams {
29912992
interface SharedDiagParams {
29922993
atRect?: Rect;
29932994
pullLeft?: Bo;
2995+
allowWide?: Bo // see [dropdown_width]
29942996
allowFullWidth?: Bo;
29952997
closeOnClickOutside?: false; // default true
29962998
onHide?: () => V;

client/app-slim/oop-methods.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ export function me_uiPrefs(me: Myself): UiPrefs {
657657

658658
/// Oops should use at more places. [me_isPageMember]
659659
///
660-
function me_isPageMember(me: Me, page: Page): Bo {
660+
export function me_isPageMember(me: Me, page: Page): Bo {
661661
if (page.pageMemberIds.indexOf(me.id) >= 0)
662662
return true;
663663

client/app-slim/page/chat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ const TitleAndLastChatMessages = createComponent({
365365
title,
366366
r.div({ className: 'esChatChnl_about'},
367367
thisIsTheWhat,
368-
r.div({}, t.c.Purpose),
368+
r.div({}, t.c.Purpose), // [chat_purpose_header]
369369
origPostBody),
370370
scrollUpTips,
371371
messages,

client/app-slim/sidebar/sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export var Sidebar = createComponent({ // RENAME to ContextBar
194194
},
195195

196196
highligtDuringMillis: function(millis: number) {
197+
// But `fadingBackdrop` is undef, if called too soon. [highl_contextbar]
197198
this.refs.fadingBackdrop.showForMillis(millis);
198199
},
199200

client/app-slim/slim-bundle.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ declare namespace debiki2 {
256256
closeSidebar: () => void;
257257
openSidebar: () => void;
258258
showAdminGuide: () => void;
259+
showUsers: () => V;
260+
highligtDuringMillis: (ms: Nr) => V;
259261
}
260262
}
261263

@@ -394,6 +396,7 @@ declare namespace debiki2 {
394396
function pat_name(pat: Me | Pat | LazyCreatedAnon | NewAnon): St;
395397
function pat_isMe(pat: UserInclDetails | Me | Pat | PatId): pat is Me;
396398
function pat_isMember(pat: UserInclDetails | Me | Pat | PatId): Bo;
399+
function me_isPageMember(me: Me, page: Page): Bo; // change to `pat_isPageMember`?
397400
var isGuest;
398401
function pat_isGuest(pat: UserInclDetails | Me | Pat): Bo;
399402
function user_isGuest(pat: UserInclDetails | Me | Pat): Bo;
@@ -406,6 +409,8 @@ declare namespace debiki2 {
406409
function anonStatus_toStr(anonStatus: AnonStatus, verb?: Verbosity): St;
407410
var page_isGroupTalk;
408411

412+
function pat_isAuthorOf(pat: Me | Pat, post: Post, patsById: PpsById): Bo;
413+
409414
function store_getAuthorOrMissing(store: DiscStore, post: Post): Pat;
410415
function store_getUserOrMissing(store: DiscStore, userId: PatId, errorCode2?: St): Pat;
411416
function store_thisIsMyPage(store: DiscStore): Bo;
@@ -558,7 +563,8 @@ declare namespace debiki2 {
558563
}
559564

560565
namespace page {
561-
var Post;
566+
function Post(props: PostProps): RElm;
567+
function PostBody(props: PostProps): RElm;
562568
function CatsOrHomeLink(ps: { page: PageTypeAncestors,
563569
store: Store, forTopbar?: Bo, skipHome?: true }): RElm | N;
564570
namespace Hacks {

client/app-slim/topbar/topbar.styl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,12 +782,20 @@ html.esSidebarsOverlayPage
782782
padding: 2px $paddingLeftRight 2px;
783783
position: relative;
784784
top: -4px;
785+
// Or:
786+
padding: 3px $paddingLeftRight 4px; // simpler to click, if mobile?
787+
top: -3px;
788+
785789
pointer-events: auto;
786790
font-weight: bold;
787791
color: hsl(0 0% 25%);
788792
white-space: nowrap;
789793
text-overflow: ellipsis;
790794
overflow: hidden;
795+
border: none; // removes .btn border (it's a dropdown button)
796+
.caret
797+
margin-left: 0.6ex;
798+
791799
@media (max-height: $tallHeight - 1px)
792800
// Smaller screen, so smaller font — more space for the actual content.
793801
top: -2px;

client/app-slim/topbar/topbar.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,36 @@ export const TopBar = createComponent({
297297
const showTitle = isBitDown && anyUnsafeTitleSource;
298298

299299
if (catsOrHomeLink || showTitle) {
300-
const anyTitle = !showTitle ? null :
301-
r.div({ className: 's_Tb_Pg_Ttl' }, // [title_plain_txt]
302-
anyUnsafeTitleSource);
300+
// The title, if clicked, shows the Change Page dialog, since when we've scrolled down
301+
// on a page, the in-page title isn't visible any longer (especially if it's a long chat)
302+
// but it's still nice to have access to the Change dialog.
303+
// For chat pages, the Change dialog also shows the chat purpose — the orig post (with
304+
// the purpose text) is typically is far, far away, at the beginning of the chat history.
305+
306+
// But if it's not a chat, and we're not the author or a mod, then, there'd be
307+
// nothing in the Change dialog, so don't show it.
308+
// But how do we know if it *would* be empty, if opened? The correct approach might be
309+
// to break out a fn from ChangePageDialog that (depending on a param) either
310+
// creates the dialog content, or just returns true iff there's sth to render,
311+
// and call that fn from here. [empty_change_page_dlg] But for now:
312+
const isOwnPage = store_thisIsMyPage(store);
313+
const isChat = page_isChat(page.pageRole);
314+
// Sometimes, should show dialog, if is core member. Oh well, the *important* thing
315+
// is to always show, if is a *chat* (so can see the chat purpose).
316+
const showDiagOnClick = isOwnPage || isStaff(me) || isChat;
317+
318+
const className = 's_Tb_Pg_Ttl';
319+
const anyTitle = !showTitle ? null : (
320+
!showDiagOnClick
321+
? r.div({ className }, anyUnsafeTitleSource) // [title_plain_txt]
322+
: Button({ className,
323+
onClick: (event: MouseEvent) => {
324+
const rect = cloneEventTargetRect(event);
325+
morebundle.openChangePageDialog(rect, { page, showViewAnswerButton: true });
326+
}},
327+
anyUnsafeTitleSource, r.span({ className: 'caret' }))); // [title_plain_txt]
328+
329+
303330
noCatsMaybeTitle = isSectionPage;
304331
CatsAndTitle = () =>
305332
r.div({ className: 's_Tb_Pg' },

client/app-slim/util/ExplainingDropdown.styl

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11

22
$sideSpace = 20px;
33

4-
.s_ExplDrp_Ttl
5-
margin: 6px 50px 7px 12px; // 50px = space for a close [X] button
6-
font-size: 17px;
7-
font-style: italic;
4+
.s_ExplDrp_Ttl,
5+
.s_ExplDrp_DescIt
6+
margin: 9px 50px 3px 20px; // 50px = space for a close [X] button
87

8+
.s_ExplDrp_Ttl
9+
// If it's a "Purpose:" header, then, text follows below, so, add a bit more space.
10+
&.n_Purp
11+
margin-bottom: 0.6ex;
12+
font-size: 15px;
13+
font-weight: bold;
14+
color: hsl(0 0% 33%); // otherwise bold is a bit too strong
915

1016
// If the action-item (e.g. a button) is at the top of the dialog, add some padding.
1117
.esCloseCross + .s_ExplDrp_ActIt
1218
padding-top: 7px;
1319

1420
.s_ExplDrp_ActIt + .s_NotfPrefDD_Ttl,
21+
.s_ExplDrp_ActIt + .s_ExplDrp_ActIt,
1522
.esExplDrp_entry + .s_NotfPrefDD_Ttl
1623
margin-top: 14px;
1724

@@ -25,6 +32,8 @@ $sideSpace = 20px;
2532
.esExplDrp_ActIt_Expl
2633
margin: 0 0 7px $sideSpace;
2734

35+
.e_AsgB
36+
margin-left: 9px; // $sideSpace = 20px is a bit much
2837
// No hover. The button instead has some :hover style.
2938

3039

client/app-slim/utils/DropdownModal.styl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
width: 100%;
77
height: 0;
88

9+
.esDropModal-NotSuperWide,
910
.esDropModal-NotTooWide
1011
.esDropModal_content
1112
max-width: 89%; // so always some emptiness outside that one can click to close. [4YK8ST2]
@@ -16,6 +17,11 @@
1617
.esDropModal_content
1718
max-width: 560px; // or annoyingly wide. [dropdown_width]
1819

20+
@media (min-width: 899px) // (800px / 89% * 100% = 899px)
21+
.esDropModal-NotSuperWide
22+
.esDropModal_content
23+
max-width: 800px; // [dropdown_width]
24+
1925
.esDropModal_CloseB
2026
// This doesn't work well: sometimes overlaps long contents.
2127
// Wouldn't float: right be better? But need one extra <div>? [close_cross_css]
@@ -34,6 +40,9 @@
3440
background: white;
3541
min-width: 150px;
3642
margin-right: 6px;
43+
& > hr
44+
margin: 5px 0 10px;
45+
border-top: 1px solid hsl(0 0% 87%);
3746

3847
.esDropModal_header
3948
font-style: italic;

0 commit comments

Comments
 (0)