Skip to content

Commit 3f65034

Browse files
authored
Quote blocks (#1906)
1 parent f242d2b commit 3f65034

23 files changed

+1412
-256
lines changed

dev-docs/plugins/hooks/replace-rich-text-tokens-hook.md

+38-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This hook wraps the standard function that Misago uses to replace rich-text tokens in pre-rendered HTML or the next filter from another plugin.
44

5-
Tokens are pseudo-HTML elements like `<attachment="..">` that are replaced with real HTML markup instead.
5+
Tokens are pseudo-HTML elements like `<misago-attachment="..">` that are replaced with real HTML markup instead.
66

77

88
## Location
@@ -18,7 +18,11 @@ from misago.parser.hooks import replace_rich_text_tokens_hook
1818

1919
```python
2020
def custom_replace_rich_text_tokens_filter(
21-
action: ReplaceRichTextTokensHookAction, html: str, data: dict
21+
action: ReplaceRichTextTokensHookAction,
22+
html: str,
23+
context: Context,
24+
data: dict,
25+
thread: Thread | None,
2226
) -> str:
2327
...
2428
```
@@ -40,11 +44,21 @@ See the [action](#action) section for details.
4044
An HTML string in which tokens will be replaced.
4145

4246

47+
#### `context: Context`
48+
49+
Current template context.
50+
51+
4352
#### `data: dict`
4453

4554
Data that can be embedded in HTML.
4655

4756

57+
#### `thread: Thread | None`
58+
59+
Current `Thread` instance of `None`.
60+
61+
4862
### Return value
4963

5064
A `str` with HTML that has its tokens replaced.
@@ -53,7 +67,9 @@ A `str` with HTML that has its tokens replaced.
5367
## Action
5468

5569
```python
56-
def replace_rich_text_tokens_action(html: str, data: dict) -> str:
70+
def replace_rich_text_tokens_action(
71+
html: str, context: Context, data: dict, thread: Thread | None
72+
) -> str:
5773
...
5874
```
5975

@@ -67,37 +83,47 @@ A standard Misago function used to replace rich-text tokens in pre-rendered HTML
6783
An HTML string in which tokens will be replaced.
6884

6985

86+
#### `context: Context`
87+
88+
Current template context.
89+
90+
7091
#### `data: dict`
7192

7293
Data that can be embedded in HTML.
7394

7495

96+
#### `thread: Thread | None`
97+
98+
Current `Thread` instance of `None`.
99+
100+
75101
### Return value
76102

77103
A `str` with HTML that has its tokens replaced.
78104

79105

80106
## Example
81107

82-
The code below implements a custom filter function that replaces default spoiler block summary with a custom message:
108+
The code below implements a custom filter function that replaces `<you>` pseudo-HTML element with current user's username.
83109

84110
```python
85-
from misago.parser.context import ParserContext
111+
from django.template import Context
86112
from misago.parser.hooks import replace_rich_text_tokens_hook
87-
from misago.parser.html import SPOILER_SUMMARY
88113

89114

90115
@replace_rich_text_tokens_hook.append_filter
91-
def replace_rich_text_spoiler_hoom(
116+
def replace_rich_text_user_name(
92117
action,
93118
html: str,
119+
context: Context,
94120
data: dict,
121+
thread: Thread | None,
95122
) -> str:
96-
if SPOILER_SUMMARY in html:
97-
html = html.replace(
98-
SPOILER_SUMMARY, "SPOILER! Click at your own discretion!"
99-
)
123+
if "<you>" in html:
124+
username = user.username if user and user.is_authenticated else "Guest"
125+
html = html.replace("<you>", username)
100126

101127
# Call the next function in chain
102-
return action(context, html, **kwargs)
128+
return action(html, context, data, thread)
103129
```

frontend/src/scrollTo.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
document.body.addEventListener("click", (event) => {
2-
const selector = event.target.getAttribute("misago-scroll-to")
3-
if (!!selector) {
4-
event.preventDefault()
2+
const target = event.target.closest("[misago-scroll-to]")
3+
if (!!target) {
4+
const selector = target.getAttribute("misago-scroll-to")
55
const element = document.querySelector(selector)
66
if (element) {
7+
event.preventDefault()
78
element.scrollIntoView({ behavior: "instant" })
89
}
910
}

frontend/src/style/misago/rich-text.less

+175-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
ol,
4747
table,
4848
pre,
49+
.rich-text-quote,
4950
.rich-text-code,
5051
.rich-text-spoiler {
5152
margin-bottom: @line-height-computed;
@@ -60,6 +61,10 @@
6061
}
6162
}
6263

64+
blockquote {
65+
font-size: 16px;
66+
}
67+
6368
.rich-text-image {
6469
display: inline-block;
6570
position: relative;
@@ -306,6 +311,168 @@
306311
text-align: right;
307312
}
308313

314+
.rich-text-quote {
315+
overflow: hidden;
316+
317+
border: 1px solid @rich-text-quote-border;
318+
border-radius: @border-radius-base;
319+
}
320+
321+
.rich-text-quote-header {
322+
display: flex;
323+
align-items: center;
324+
justify-content: space-between;
325+
326+
padding: @rich-text-quote-padding;
327+
328+
background-color: @rich-text-quote-header-bg;
329+
}
330+
331+
.rich-text-quote-avatar {
332+
flex-shrink: 0;
333+
display: block;
334+
margin-right: @rich-text-quote-padding;
335+
overflow: hidden;
336+
337+
border-radius: @border-radius-small;
338+
line-height: 0;
339+
340+
img {
341+
width: 24px;
342+
height: 24px;
343+
}
344+
}
345+
346+
.rich-text-quote-btn {
347+
flex-shrink: 0;
348+
display: flex;
349+
align-items: center;
350+
justify-content: center;
351+
352+
width: 28px;
353+
height: 28px;
354+
margin: -2px 0 -2px @rich-text-quote-padding;
355+
padding: 0;
356+
357+
border-radius: @border-radius-base;
358+
359+
&:hover,
360+
&:focus,
361+
&:active {
362+
box-shadow: none;
363+
text-decoration: none;
364+
}
365+
366+
&,
367+
&:link,
368+
&:visited
369+
&:focus {
370+
background-color: @rich-text-quote-header-btn-bg;
371+
border: 1px solid @rich-text-quote-header-btn-border;
372+
color: @rich-text-quote-header-btn-color;
373+
}
374+
375+
&:hover {
376+
background-color: @rich-text-quote-header-btn-hover-bg;
377+
border: 1px solid @rich-text-quote-header-btn-hover-border;
378+
color: @rich-text-quote-header-btn-hover-color;
379+
}
380+
381+
&:active {
382+
background-color: @rich-text-quote-header-btn-active-bg;
383+
border: 1px solid @rich-text-quote-header-btn-active-border;
384+
color: @rich-text-quote-header-btn-active-color;
385+
}
386+
387+
.material-icon {
388+
font-size: 24px;
389+
line-height: 24px;
390+
}
391+
}
392+
393+
.rich-text-quote-info {
394+
width: 100%;
395+
overflow: hidden;
396+
397+
color: @rich-text-quote-header-color;
398+
font-weight: bold;
399+
white-space: nowrap;
400+
text-overflow: ellipsis;
401+
}
402+
403+
.rich-text-quote-header-link {
404+
color: @rich-text-quote-header-link;
405+
font-weight: bold;
406+
}
407+
408+
a.rich-text-quote-header-link {
409+
&,
410+
&:link,
411+
&:visited,
412+
&:focus {
413+
color: @rich-text-quote-header-link;
414+
}
415+
416+
&:hover,
417+
&:active {
418+
color: @rich-text-quote-header-link;
419+
text-decoration: underline;
420+
}
421+
}
422+
423+
.rich-text-quote-header-link-secondary {
424+
color: @rich-text-quote-header-link-secondary;
425+
font-weight: normal;
426+
}
427+
428+
a.rich-text-quote-header-link-secondary {
429+
&,
430+
&:link,
431+
&:visited,
432+
&:focus {
433+
color: @rich-text-quote-header-link-secondary;
434+
}
435+
436+
&:hover,
437+
&:active {
438+
color: @rich-text-quote-header-link-secondary;
439+
text-decoration: underline;
440+
}
441+
}
442+
443+
.rich-text-quote-header-timestamp {
444+
color: @rich-text-quote-header-timestamp;
445+
font-weight: normal;
446+
}
447+
448+
.rich-text-quote-header-timestamp-long {
449+
display: none;
450+
}
451+
452+
@media screen and (min-width: @screen-sm-min) {
453+
.rich-text-quote-header-timestamp-short {
454+
display: none;
455+
}
456+
457+
.rich-text-quote-header-timestamp-long {
458+
display: inline-block;
459+
}
460+
}
461+
462+
.rich-text-quote-header-separator {
463+
padding: 0 2px;
464+
465+
color: @rich-text-quote-header-separator;
466+
}
467+
468+
.rich-text-quote-body {
469+
margin: 0;
470+
padding: @rich-text-quote-padding;
471+
472+
background-color: @rich-text-quote-body-bg;
473+
border: none;
474+
}
475+
309476
.rich-text-code {
310477
overflow: hidden;
311478

@@ -375,7 +542,13 @@
375542
padding: 2px;
376543

377544
background-color: @rich-text-spoiler-bg;
378-
background: repeating-linear-gradient(45deg, @rich-text-spoiler-bg, @rich-text-spoiler-bg 4px, @rich-text-spoiler-bg-alt 4px, @rich-text-spoiler-bg-alt 8px);
545+
background: repeating-linear-gradient(
546+
45deg,
547+
@rich-text-spoiler-bg,
548+
@rich-text-spoiler-bg 4px,
549+
@rich-text-spoiler-bg-alt 4px,
550+
@rich-text-spoiler-bg-alt 8px
551+
);
379552
background-position: top left;
380553
border-radius: @border-radius-base;
381554
}
@@ -441,4 +614,4 @@
441614
color: @link-hover-color;
442615
text-decoration: none;
443616
}
444-
}
617+
}

frontend/src/style/misago/variables.less

+26
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,32 @@
439439

440440
//== RichText elements
441441

442+
@rich-text-quote-padding: 12px;
443+
444+
@rich-text-quote-border: #e3e8f0;
445+
446+
@rich-text-quote-header-bg: #f2f5f9;
447+
@rich-text-quote-header-color: #1f283b;
448+
449+
@rich-text-quote-header-link: #1f283b;
450+
@rich-text-quote-header-link-secondary: #1f283b;
451+
@rich-text-quote-header-timestamp: #48556a;
452+
@rich-text-quote-header-separator: #ccd5e1;
453+
454+
@rich-text-quote-header-btn-bg: transparent;
455+
@rich-text-quote-header-btn-border: transparent;
456+
@rich-text-quote-header-btn-color: #1f283b;
457+
458+
@rich-text-quote-header-btn-hover-bg: #ccd5e1;
459+
@rich-text-quote-header-btn-hover-border: #ccd5e1;
460+
@rich-text-quote-header-btn-hover-color: #1f283b;
461+
462+
@rich-text-quote-header-btn-active-bg: #1f283b;
463+
@rich-text-quote-header-btn-active-border: #1f283b;
464+
@rich-text-quote-header-btn-active-color: #fff;
465+
466+
@rich-text-quote-body-bg: #fff;
467+
442468
@rich-text-code-padding: 12px;
443469
@rich-text-code-bg: #ebecf0;
444470

0 commit comments

Comments
 (0)