Fossil SCM

fossil-scm / src / fossil.page.forumpost.js
Blame History Raw 136 lines
1
(function(F/*the fossil object*/){
2
"use strict";
3
/* JS code for /forumpost and friends. Requires fossil.dom
4
and can optionally use fossil.pikchr. */
5
const P = F.page, D = F.dom;
6
7
/**
8
When the page is loaded, this handler does the following:
9
10
- Installs expand/collapse UI elements on "long" posts and collapses
11
them.
12
13
- Any pikchr-generated SVGs get a source-toggle button added to them
14
which activates when the mouse is over the image or it is tapped.
15
16
This is a harmless no-op if the current page has neither forum
17
post constructs for (1) nor any pikchr images for (2), nor will
18
NOT running this code cause any breakage for clients with no JS
19
support: this is all "nice-to-have", not required functionality.
20
*/
21
F.onPageLoad(function(){
22
const scrollbarIsVisible = (e)=>e.scrollHeight > e.clientHeight;
23
/* Returns an event handler which implements the post expand/collapse toggle
24
on contentElem when the given widget is activated. */
25
const getWidgetHandler = function(widget, contentElem){
26
return function(ev){
27
if(ev) ev.preventDefault();
28
const wasExpanded = widget.classList.contains('expanded');
29
widget.classList.toggle('expanded');
30
contentElem.classList.toggle('expanded');
31
if(wasExpanded){
32
contentElem.classList.add('shrunken');
33
contentElem.parentElement.scrollIntoView({
34
/* This is non-standard, but !(MSIE, Safari) supposedly support it:
35
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#Browser_compatibility
36
*/ behavior: 'smooth'
37
});
38
}else{
39
contentElem.classList.remove('shrunken');
40
}
41
return false;
42
};
43
};
44
45
/* Adds an Expand/Collapse toggle to all div.forumPostBody
46
elements which are deemed "too large" (those for which
47
scrolling is currently activated because they are taller than
48
their max-height). */
49
document.querySelectorAll(
50
'div.forumTime, div.forumEdit'
51
).forEach(function f(forumPostWrapper){
52
const content = forumPostWrapper.querySelector('div.forumPostBody');
53
if(!content || !scrollbarIsVisible(content)) return;
54
const parent = content.parentElement,
55
widget = D.addClass(
56
D.div(),
57
'forum-post-collapser','bottom'
58
),
59
rightTapZone = D.addClass(
60
D.div(),
61
'forum-post-collapser','right'
62
);
63
/* Repopulates the rightTapZone with arrow indicators. Because
64
of the wildly varying height of these elements, This has to
65
be done dynamically at init time and upon collapse/expand. Will not
66
work until the rightTapZone has been added to the DOM. */
67
const refillTapZone = function f(){
68
if(!f.baseTapIndicatorHeight){
69
/* To figure out how often to place an arrow in the rightTapZone,
70
we simply grab the first header element from the page and use
71
its hight as our basis for calculation. */
72
const h1 = document.querySelector('h1, h2');
73
f.baseTapIndicatorHeight = h1.getBoundingClientRect().height;
74
}
75
D.clearElement(rightTapZone);
76
var rtzHeight = parseInt(window.getComputedStyle(rightTapZone).height);
77
do {
78
D.append(rightTapZone, D.span());
79
rtzHeight -= f.baseTapIndicatorHeight * 8;
80
}while(rtzHeight>0);
81
};
82
const handlerStep1 = getWidgetHandler(widget, content);
83
const widgetEventHandler = ()=>{ handlerStep1(); refillTapZone(); };
84
content.classList.add('with-expander');
85
widget.addEventListener('click', widgetEventHandler, false);
86
/** Append 3 children, which CSS will evenly space across the
87
widget. This improves visibility over having the label
88
in only the left, right, or center. */
89
var i = 0;
90
for( ; i < 3; ++i ) D.append(widget, D.span());
91
if(content.nextSibling){
92
forumPostWrapper.insertBefore(widget, content.nextSibling);
93
}else{
94
forumPostWrapper.appendChild(widget);
95
}
96
content.appendChild(rightTapZone);
97
rightTapZone.addEventListener('click', widgetEventHandler, false);
98
refillTapZone();
99
})/*F.onPageLoad()*/;
100
101
if(F.pikchr){
102
F.pikchr.addSrcView();
103
}
104
105
/* Attempt to keep stray double-clicks from double-posting. */
106
const formSubmitted = function(event){
107
const form = event.target;
108
if( form.dataset.submitted ){
109
event.preventDefault();
110
return;
111
}
112
form.dataset.submitted = '1';
113
/** If the user is left waiting "a long time," disable the
114
resubmit protection. If we don't do this and they tap the
115
browser's cancel button while waiting, they'll be stuck with
116
an unsubmittable form. */
117
setTimeout(()=>{delete form.dataset.submitted}, 7000);
118
return;
119
};
120
document.querySelectorAll("form").forEach(function(form){
121
form.addEventListener('submit',formSubmitted);
122
form
123
.querySelectorAll("input.action-close, input.action-reopen")
124
.forEach(function(e){
125
F.confirmer(e, {
126
confirmText: (e.classList.contains('action-reopen')
127
? "Confirm re-open"
128
: "Confirm close"),
129
onconfirm: ()=>form.submit()
130
});
131
});
132
});
133
134
})/*F.onPageLoad callback*/;
135
})(window.fossil);
136

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button