From 6bde90360288bfd848b3f569dae440e101205c10 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Wed, 29 Dec 2021 12:12:33 -0700 Subject: [PATCH 1/6] allow extensions to use `!` and `^` as special characters --- src/inlines.c | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/inlines.c b/src/inlines.c index 8020a73e2..ec238e4be 100644 --- a/src/inlines.c +++ b/src/inlines.c @@ -1516,6 +1516,7 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent, cmark_chunk contents; unsigned char c; bufsize_t startpos, endpos; + bufsize_t initpos = subj->pos; c = peek_char(subj); if (c == 0) { return 0; @@ -1563,43 +1564,49 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent, new_inl = handle_close_bracket(parser, subj); break; case '!': - advance(subj); - if (peek_char(subj) == '[' && peek_char_n(subj, 1) != '^') { + if (peek_char_n(subj, 1) == '[' && peek_char_n(subj, 2) != '^') { + advance(subj); advance(subj); new_inl = make_str(subj, subj->pos - 2, subj->pos - 1, cmark_chunk_literal("![")); push_bracket(subj, IMAGE, new_inl); - } else { - new_inl = make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("!")); } break; case '^': - advance(subj); // TODO: Support a name between ^ and [ - if (peek_char(subj) == '[') { + if (peek_char_n(subj, 1) == '[') { + advance(subj); advance(subj); new_inl = make_str(subj, subj->pos - 2, subj->pos - 1, cmark_chunk_literal("^[")); push_bracket(subj, ATTRIBUTE, new_inl); - } else { - new_inl = make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("^")); } break; - default: - new_inl = try_extensions(parser, parent, c, subj); - if (new_inl != NULL) - break; + } - endpos = subject_find_special_char(parser, subj, options); - contents = cmark_chunk_dup(&subj->input, subj->pos, endpos - subj->pos); - startpos = subj->pos; - subj->pos = endpos; + if (subj->pos == initpos) { + if (!new_inl) + new_inl = try_extensions(parser, parent, c, subj); - // if we're at a newline, strip trailing spaces. - if ((options & CMARK_OPT_PRESERVE_WHITESPACE) == 0 && S_is_line_end_char(peek_char(subj))) { - cmark_chunk_rtrim(&contents); - } + if (!new_inl) { + if (c == '^' || c == '!') { + advance(subj); + new_inl = make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_dup(&subj->input, subj->pos - 1, 1)); + } else { + endpos = subject_find_special_char(parser, subj, options); + contents = cmark_chunk_dup(&subj->input, subj->pos, endpos - subj->pos); + startpos = subj->pos; + subj->pos = endpos; + + // if we're at a newline, strip trailing spaces. + if ((options & CMARK_OPT_PRESERVE_WHITESPACE) == 0 && S_is_line_end_char(peek_char(subj))) { + cmark_chunk_rtrim(&contents); + } - new_inl = make_str(subj, startpos, endpos - 1, contents); + if (endpos > startpos) + new_inl = make_str(subj, startpos, endpos - 1, contents); + } + } } + if (new_inl != NULL) { cmark_node_append_child(parent, new_inl); } From 1e705e9e8718a080a5e358157d51d4a34d5b8751 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Thu, 30 Dec 2021 08:36:28 -0700 Subject: [PATCH 2/6] update Makefile with new include dir --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a05ba97a6..b84e9e741 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ mingw: cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw32.cmake -DCMAKE_INSTALL_PREFIX=$(MINGW_INSTALLDIR) ;\ $(MAKE) && $(MAKE) install -man/man3/cmark-gfm.3: src/cmark-gfm.h | $(CMARK) +man/man3/cmark-gfm.3: src/include/cmark-gfm.h | $(CMARK) python man/make_man_page.py $< > $@ \ archive: From f678bae879337d6c82e0f2e676da9069b8412d3a Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 25 Dec 2021 13:11:00 -0700 Subject: [PATCH 3/6] add new spoiler extension --- extensions/CMakeLists.txt | 1 + extensions/core-extensions.c | 2 + extensions/spoiler.c | 139 +++++++++++++++++++++++++++++++++++ extensions/spoiler.h | 9 +++ 4 files changed, 151 insertions(+) create mode 100644 extensions/spoiler.c create mode 100644 extensions/spoiler.h diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 4977c98c3..62984af5c 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -11,6 +11,7 @@ set(LIBRARY_SOURCES ext_scanners.re ext_scanners.h tasklist.c + spoiler.c ) include_directories( diff --git a/extensions/core-extensions.c b/extensions/core-extensions.c index 131cdf402..c9bff9471 100644 --- a/extensions/core-extensions.c +++ b/extensions/core-extensions.c @@ -2,6 +2,7 @@ #include "autolink.h" #include "mutex.h" #include "strikethrough.h" +#include "spoiler.h" #include "table.h" #include "tagfilter.h" #include "tasklist.h" @@ -15,6 +16,7 @@ static int core_extensions_registration(cmark_plugin *plugin) { cmark_plugin_register_syntax_extension(plugin, create_autolink_extension()); cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension()); cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension()); + cmark_plugin_register_syntax_extension(plugin, create_spoiler_extension()); return 1; } diff --git a/extensions/spoiler.c b/extensions/spoiler.c new file mode 100644 index 000000000..786b5219e --- /dev/null +++ b/extensions/spoiler.c @@ -0,0 +1,139 @@ +#include "spoiler.h" +#include +#include + +cmark_node_type CMARK_NODE_SPOILER; + +static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser, + cmark_node *parent, unsigned char character, + cmark_inline_parser *inline_parser) { + cmark_node *res = NULL; + int left_flanking, right_flanking, punct_before, punct_after, delims; + char buffer[101]; + + if (character != '|') + return NULL; + + delims = cmark_inline_parser_scan_delimiters( + inline_parser, sizeof(buffer) - 1, '|', + &left_flanking, + &right_flanking, &punct_before, &punct_after); + + memset(buffer, '|', delims); + buffer[delims] = 0; + + res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); + cmark_node_set_literal(res, buffer); + res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser); + res->start_column = cmark_inline_parser_get_column(inline_parser) - delims; + + if ((left_flanking || right_flanking) && (delims == 2)) { + cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking, + right_flanking, res); + } + + return res; +} + +static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser, + cmark_inline_parser *inline_parser, delimiter *opener, + delimiter *closer) { + cmark_node *spoiler; + cmark_node *tmp, *next; + delimiter *delim, *tmp_delim; + delimiter *res = closer->next; + + spoiler = opener->inl_text; + + if (opener->inl_text->as.literal.len != closer->inl_text->as.literal.len) + goto done; + + if (!cmark_node_set_type(spoiler, CMARK_NODE_SPOILER)) + goto done; + + cmark_node_set_syntax_extension(spoiler, self); + + tmp = cmark_node_next(opener->inl_text); + + while (tmp) { + if (tmp == closer->inl_text) + break; + next = cmark_node_next(tmp); + cmark_node_append_child(spoiler, tmp); + tmp = next; + } + + spoiler->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1; + cmark_node_free(closer->inl_text); + + delim = closer; + while (delim != NULL && delim != opener) { + tmp_delim = delim->previous; + cmark_inline_parser_remove_delimiter(inline_parser, delim); + delim = tmp_delim; + } + + cmark_inline_parser_remove_delimiter(inline_parser, opener); + +done: + return res; +} + +static const char *get_type_string(cmark_syntax_extension *extension, + cmark_node *node) { + return node->type == CMARK_NODE_SPOILER ? "spoiler" : ""; +} + +static int can_contain(cmark_syntax_extension *extension, cmark_node *node, + cmark_node_type child_type) { + if (node->type != CMARK_NODE_SPOILER) + return false; + + return CMARK_NODE_TYPE_INLINE_P(child_type); +} + +static void commonmark_render(cmark_syntax_extension *extension, + cmark_renderer *renderer, cmark_node *node, + cmark_event_type ev_type, int options) { + renderer->out(renderer, node, "||", false, LITERAL); +} + +static void html_render(cmark_syntax_extension *extension, + cmark_html_renderer *renderer, cmark_node *node, + cmark_event_type ev_type, int options) { + bool entering = (ev_type == CMARK_EVENT_ENTER); + if (entering) { + cmark_strbuf_puts(renderer->html, ""); + } else { + cmark_strbuf_puts(renderer->html, ""); + } +} + +static void plaintext_render(cmark_syntax_extension *extension, + cmark_renderer *renderer, cmark_node *node, + cmark_event_type ev_type, int options) { + renderer->out(renderer, node, "~", false, LITERAL); +} + +cmark_syntax_extension *create_spoiler_extension(void) { + cmark_syntax_extension *ext = cmark_syntax_extension_new("spoiler"); + cmark_llist *special_chars = NULL; + + cmark_syntax_extension_set_get_type_string_func(ext, get_type_string); + cmark_syntax_extension_set_can_contain_func(ext, can_contain); + cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render); + cmark_syntax_extension_set_html_render_func(ext, html_render); + cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render); + CMARK_NODE_SPOILER = cmark_syntax_extension_add_node(1); + + cmark_syntax_extension_set_match_inline_func(ext, match); + cmark_syntax_extension_set_inline_from_delim_func(ext, insert); + + cmark_mem *mem = cmark_get_default_mem_allocator(); + special_chars = cmark_llist_append(mem, special_chars, (void *)'|'); + cmark_syntax_extension_set_special_inline_chars(ext, special_chars); + + cmark_syntax_extension_set_emphasis(ext, 1); + + return ext; +} diff --git a/extensions/spoiler.h b/extensions/spoiler.h new file mode 100644 index 000000000..a86bbbf39 --- /dev/null +++ b/extensions/spoiler.h @@ -0,0 +1,9 @@ +#ifndef CMARK_GFM_SPOILER_H +#define CMARK_GFM_SPOILER_H + +#include "cmark-gfm-core-extensions.h" + +extern cmark_node_type CMARK_NODE_SPOILER; +cmark_syntax_extension *create_spoiler_extension(void); + +#endif /* CMARK_GFM_SPOILER_H */ From e70f2255cc9305c1fa3da3ba2f073abb9c424b0a Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 25 Dec 2021 18:10:45 -0700 Subject: [PATCH 4/6] add tests for spoiler extension --- api_test/main.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/api_test/main.c b/api_test/main.c index 1051c2904..9324eb499 100644 --- a/api_test/main.c +++ b/api_test/main.c @@ -1417,6 +1417,46 @@ static void parser_interrupt(test_batch_runner *runner) { cmark_syntax_extension_free(cmark_get_default_mem_allocator(), my_ext); } +static void render_spoiler(test_batch_runner *runner) { + cmark_gfm_core_extensions_ensure_registered(); + + cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT); + cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("spoiler")); + + { + static const char markdown[] = "we have some ||spicy text|| here"; + cmark_parser_feed(parser, markdown, sizeof(markdown) - 1); + + cmark_node *doc = cmark_parser_finish(parser); + char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + STR_EQ(runner, xml, "\n" + "\n" + "\n" + " \n" + " we have some \n" + " \n" + " spicy text\n" + " \n" + " here\n" + " \n" + "\n", "rendering spoilers should appear correctly"); + } + { + static const char markdown[] = "we have some |non-spicy text| here"; + cmark_parser_feed(parser, markdown, sizeof(markdown) - 1); + + cmark_node *doc = cmark_parser_finish(parser); + char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + STR_EQ(runner, xml, "\n" + "\n" + "\n" + " \n" + " we have some |non-spicy text| here\n" + " \n" + "\n", "rendering spoilers without proper delimiters should appear correctly"); + } +} + int main() { int retval; test_batch_runner *runner = test_batch_runner_new(); @@ -1452,6 +1492,7 @@ int main() { verify_custom_attributes_node(runner); verify_custom_attributes_node_with_footnote(runner); parser_interrupt(runner); + render_spoiler(runner); test_print_summary(runner); retval = test_ok(runner) ? 0 : 1; From f69ae9f0d1b94c44bbb373fadf26eed75fd3c85f Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Tue, 28 Dec 2021 10:47:00 -0700 Subject: [PATCH 5/6] add spoiler extension to spec --- test/spec.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/spec.txt b/test/spec.txt index 582131d70..71e0215fa 100644 --- a/test/spec.txt +++ b/test/spec.txt @@ -7734,6 +7734,26 @@ new paragraph~~. +
+ +## Spoiler text (extension) + +Swift-cmark GFM provides the `spoiler` extension, providing an additional emphasis +type for text that should be hidden and revealed by user interaction. + +Spoiler text is wrapped in two pipes (`|`). + +As the use of spoiler text depends on user interaction, GFM only emits a generic +`` tag that must be styled by the client. + +```````````````````````````````` example spoiler +Spoiler alert: ||Hello, world!|| +. +

Spoiler alert: Hello, world!

+```````````````````````````````` + +
+ ## Links A link contains [link text] (the visible text), a [link destination] From 71a79e48f10865711710df25fd03f6d743dd8519 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Wed, 29 Dec 2021 12:27:49 -0700 Subject: [PATCH 6/6] optionally allow Reddit-style spoiler delimiters --- api_test/main.c | 68 +++++++++++++++++++++++++ bin/main.c | 5 ++ extensions/spoiler.c | 109 ++++++++++++++++++++++++++++++---------- src/include/cmark-gfm.h | 5 ++ test/spec.txt | 9 ++++ 5 files changed, 169 insertions(+), 27 deletions(-) diff --git a/api_test/main.c b/api_test/main.c index 9324eb499..bc7d39c6b 100644 --- a/api_test/main.c +++ b/api_test/main.c @@ -1455,6 +1455,74 @@ static void render_spoiler(test_batch_runner *runner) { " \n" "\n", "rendering spoilers without proper delimiters should appear correctly"); } + { + static const char markdown[] = "we have some >!incorrectly spicy text!< here"; + cmark_parser_feed(parser, markdown, sizeof(markdown) - 1); + + cmark_node *doc = cmark_parser_finish(parser); + char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + STR_EQ(runner, xml, "\n" + "\n" + "\n" + " \n" + " we have some >!incorrectly spicy text!< here\n" + " \n" + "\n", "rendering spoilers without proper delimiters should appear correctly"); + } +} + +static void render_spoiler_reddit(test_batch_runner *runner) { + cmark_gfm_core_extensions_ensure_registered(); + + cmark_parser *parser = cmark_parser_new(CMARK_OPT_SPOILER_REDDIT_STYLE); + cmark_parser_attach_syntax_extension(parser, cmark_find_syntax_extension("spoiler")); + + { + static const char markdown[] = "we have some >!spicy text!< here"; + cmark_parser_feed(parser, markdown, sizeof(markdown) - 1); + + cmark_node *doc = cmark_parser_finish(parser); + char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + STR_EQ(runner, xml, "\n" + "\n" + "\n" + " \n" + " we have some \n" + " \n" + " spicy text\n" + " \n" + " here\n" + " \n" + "\n", "rendering spoilers should appear correctly"); + } + { + static const char markdown[] = "we have some !non-spicy text! here"; + cmark_parser_feed(parser, markdown, sizeof(markdown) - 1); + + cmark_node *doc = cmark_parser_finish(parser); + char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + STR_EQ(runner, xml, "\n" + "\n" + "\n" + " \n" + " we have some !non-spicy text! here\n" + " \n" + "\n", "rendering spoilers without proper delimiters should appear correctly"); + } + { + static const char markdown[] = "we have some ||incorrectly spicy text|| here"; + cmark_parser_feed(parser, markdown, sizeof(markdown) - 1); + + cmark_node *doc = cmark_parser_finish(parser); + char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + STR_EQ(runner, xml, "\n" + "\n" + "\n" + " \n" + " we have some ||incorrectly spicy text|| here\n" + " \n" + "\n", "rendering spoilers without proper delimiters should appear correctly"); + } } int main() { diff --git a/bin/main.c b/bin/main.c index 2bab06284..87aaf619b 100644 --- a/bin/main.c +++ b/bin/main.c @@ -64,6 +64,9 @@ void print_usage() { " instead of align attributes.\n"); printf(" --full-info-string Include remainder of code block info\n" " string in a separate attribute.\n"); + printf(" --spoiler-reddit-style Use Reddit-style spoiler delimiters,\n" + " i.e. >!spoilertext!< instead of\n" + " ||spoilertext||\n"); printf(" --help, -h Print usage information\n"); printf(" --version Print version\n"); } @@ -185,6 +188,8 @@ int main(int argc, char *argv[]) { options |= CMARK_OPT_VALIDATE_UTF8; } else if (strcmp(argv[i], "--liberal-html-tag") == 0) { options |= CMARK_OPT_LIBERAL_HTML_TAG; + } else if (strcmp(argv[i], "--spoiler-reddit-style") == 0) { + options |= CMARK_OPT_SPOILER_REDDIT_STYLE; } else if ((strcmp(argv[i], "--help") == 0) || (strcmp(argv[i], "-h") == 0)) { print_usage(); diff --git a/extensions/spoiler.c b/extensions/spoiler.c index 786b5219e..532ea7df8 100644 --- a/extensions/spoiler.c +++ b/extensions/spoiler.c @@ -11,25 +11,74 @@ static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser, int left_flanking, right_flanking, punct_before, punct_after, delims; char buffer[101]; - if (character != '|') - return NULL; - - delims = cmark_inline_parser_scan_delimiters( - inline_parser, sizeof(buffer) - 1, '|', - &left_flanking, - &right_flanking, &punct_before, &punct_after); - - memset(buffer, '|', delims); - buffer[delims] = 0; - - res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); - cmark_node_set_literal(res, buffer); - res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser); - res->start_column = cmark_inline_parser_get_column(inline_parser) - delims; - - if ((left_flanking || right_flanking) && (delims == 2)) { - cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking, - right_flanking, res); + if ((parser->options & CMARK_OPT_SPOILER_REDDIT_STYLE)) { + // Reddit-style spoilers - flanked by angle brackets and exclamation marks, + // e.g. >!this is a spoiler!< + int pos = cmark_inline_parser_get_offset(inline_parser); + char *txt = NULL; + bool opener = false; + bool closer = false; + if (cmark_inline_parser_peek_at(inline_parser, pos) == '>' && + cmark_inline_parser_peek_at(inline_parser, pos + 1) == '!') { + txt = ">!"; + opener = true; + } else if (cmark_inline_parser_peek_at(inline_parser, pos) == '!' && + cmark_inline_parser_peek_at(inline_parser, pos + 1) == '<') { + txt = "!<"; + closer = true; + } + + if (opener && pos > 0 && !cmark_isspace(cmark_inline_parser_peek_at(inline_parser, pos - 1))) { + opener = false; + } + + if (closer) { + cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser); + bufsize_t len = chunk->len; + if (pos + 2 < len && !cmark_isspace(cmark_inline_parser_peek_at(inline_parser, pos + 2))) { + closer = false; + } + } + + if ((!opener && !closer) || !txt) + return NULL; + + res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); + cmark_node_set_literal(res, txt); + res->start_line = cmark_inline_parser_get_line(inline_parser); + res->start_column = cmark_inline_parser_get_column(inline_parser); + + cmark_inline_parser_set_offset(inline_parser, pos + 2); + + res->end_line = cmark_inline_parser_get_line(inline_parser); + res->end_column = cmark_inline_parser_get_column(inline_parser); + + // Set the character for this delimiter to `!`, since it's a heterogenous + // delimiter and the delimiter API assumes single repeated characters. + cmark_inline_parser_push_delimiter(inline_parser, '!', opener, closer, res); + } else { + // Discord-style spoilers - flanked on both sides by two pipes, + // e.g. ||this is a spoiler|| + if (character != '|') + return NULL; + + delims = cmark_inline_parser_scan_delimiters( + inline_parser, sizeof(buffer) - 1, '|', + &left_flanking, + &right_flanking, &punct_before, &punct_after); + + memset(buffer, '|', delims); + buffer[delims] = 0; + + res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); + cmark_node_set_literal(res, buffer); + res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser); + res->start_column = cmark_inline_parser_get_column(inline_parser) - delims; + + if ((left_flanking || right_flanking) && (delims == 2)) { + cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking, + right_flanking, res); + } } return res; @@ -95,7 +144,16 @@ static int can_contain(cmark_syntax_extension *extension, cmark_node *node, static void commonmark_render(cmark_syntax_extension *extension, cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - renderer->out(renderer, node, "||", false, LITERAL); + if (options & CMARK_OPT_SPOILER_REDDIT_STYLE) { + bool entering = (ev_type == CMARK_EVENT_ENTER); + if (entering) { + renderer->out(renderer, node, ">!", false, LITERAL); + } else { + renderer->out(renderer, node, "!<", false, LITERAL); + } + } else { + renderer->out(renderer, node, "||", false, LITERAL); + } } static void html_render(cmark_syntax_extension *extension, @@ -109,12 +167,6 @@ static void html_render(cmark_syntax_extension *extension, } } -static void plaintext_render(cmark_syntax_extension *extension, - cmark_renderer *renderer, cmark_node *node, - cmark_event_type ev_type, int options) { - renderer->out(renderer, node, "~", false, LITERAL); -} - cmark_syntax_extension *create_spoiler_extension(void) { cmark_syntax_extension *ext = cmark_syntax_extension_new("spoiler"); cmark_llist *special_chars = NULL; @@ -123,7 +175,7 @@ cmark_syntax_extension *create_spoiler_extension(void) { cmark_syntax_extension_set_can_contain_func(ext, can_contain); cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render); cmark_syntax_extension_set_html_render_func(ext, html_render); - cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render); + cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render); CMARK_NODE_SPOILER = cmark_syntax_extension_add_node(1); cmark_syntax_extension_set_match_inline_func(ext, match); @@ -131,6 +183,9 @@ cmark_syntax_extension *create_spoiler_extension(void) { cmark_mem *mem = cmark_get_default_mem_allocator(); special_chars = cmark_llist_append(mem, special_chars, (void *)'|'); + special_chars = cmark_llist_append(mem, special_chars, (void *)'>'); + special_chars = cmark_llist_append(mem, special_chars, (void *)'<'); + special_chars = cmark_llist_append(mem, special_chars, (void *)'!'); cmark_syntax_extension_set_special_inline_chars(ext, special_chars); cmark_syntax_extension_set_emphasis(ext, 1); diff --git a/src/include/cmark-gfm.h b/src/include/cmark-gfm.h index 14ab95f4a..a4ee03c42 100644 --- a/src/include/cmark-gfm.h +++ b/src/include/cmark-gfm.h @@ -780,6 +780,11 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar */ #define CMARK_OPT_PRESERVE_WHITESPACE ((1 << 19) | CMARK_OPT_INLINE_ONLY) +/** Parse spoiler text with Reddit-style delimiters (`>!this is a spoiler!<`). Without + * this option, spoilers are parsed with Discord-style delimiters (`||this is a spoiler||`). + */ +#define CMARK_OPT_SPOILER_REDDIT_STYLE (1 << 20) + /** * ## Version information */ diff --git a/test/spec.txt b/test/spec.txt index 71e0215fa..ff93f3c77 100644 --- a/test/spec.txt +++ b/test/spec.txt @@ -7752,6 +7752,15 @@ Spoiler alert: ||Hello, world!||

Spoiler alert: Hello, world!

```````````````````````````````` +The `spoiler` extension also optionally allows alternate "Reddit-style" delimiters, +which use angle brackets and exclamation marks to mark spoiler text: + +```````````````````````````````` example disabled +Spoiler alert: >!Hello, world!!< +. +

Spoiler alert: Hello, world!

+```````````````````````````````` + ## Links