diff --git a/tildes/scss/_base.scss b/tildes/scss/_base.scss index 4dc080b..abf5f26 100644 --- a/tildes/scss/_base.scss +++ b/tildes/scss/_base.scss @@ -142,6 +142,7 @@ main { menu { list-style-type: none; + padding: 0; } // We'll use lists for their semantic value sometimes, so we don't want them to diff --git a/tildes/scss/modules/_tab.scss b/tildes/scss/modules/_tab.scss index 9bb1faa..3e4c59c 100644 --- a/tildes/scss/modules/_tab.scss +++ b/tildes/scss/modules/_tab.scss @@ -1,6 +1,20 @@ // Copyright (c) 2018 Tildes contributors // SPDX-License-Identifier: AGPL-3.0-or-later +.tab { + font-size: 0.6rem; +} + +.tab-item { + &.active .btn { + border-bottom: 2px solid; + } + + .btn { + font-weight: normal; + } +} + .tab-listing-order { flex-wrap: nowrap; padding-left: 0; @@ -8,8 +22,6 @@ justify-content: space-around; - font-size: 0.6rem; - // have the tabs span the full width at small sizes width: 100%; @media (min-width: $size-md) { @@ -31,3 +43,7 @@ } } } + +.tab-markdown-mode { + display: inline-flex; +} diff --git a/tildes/scss/themes/_theme_base.scss b/tildes/scss/themes/_theme_base.scss index 20b6dac..e1738c8 100644 --- a/tildes/scss/themes/_theme_base.scss +++ b/tildes/scss/themes/_theme_base.scss @@ -405,7 +405,7 @@ background-color: map-get($theme, "alert"); } - .tab-listing-order { + .tab { border-color: map-get($theme, "border"); } @@ -414,7 +414,8 @@ color: map-get($theme, "foreground-primary"); } - &.active a { + &.active a, + &.active button { color: map-get($theme, "link"); border-bottom-color: map-get($theme, "link"); } diff --git a/tildes/static/js/behaviors/comment-reply-button.js b/tildes/static/js/behaviors/comment-reply-button.js index 0fed2ca..38161e3 100644 --- a/tildes/static/js/behaviors/comment-reply-button.js +++ b/tildes/static/js/behaviors/comment-reply-button.js @@ -24,6 +24,7 @@ $.onmount('[data-js-comment-reply-button]', function() { var parentCommentAuthor = $parentComment.find('header:first .link-user').text(); var postURL = '/api/web/comments/' + parentCommentID + '/replies'; var markdownID = 'markdown-reply-' + parentCommentID; + var previewID = markdownID + '-preview'; if ($('#' + markdownID).length > 0) { $('#' + markdownID).focus(); @@ -39,6 +40,12 @@ $.onmount('[data-js-comment-reply-button]', function() { clone.querySelector('span').innerHTML = 'Replying to ' + parentCommentAuthor; clone.querySelector('textarea').setAttribute('id', markdownID); + var preview = clone.querySelector('[data-js-markdown-preview]'); + preview.setAttribute('id', previewID); + + clone.querySelector('[data-js-markdown-preview-tab] .btn') + .setAttribute('data-ic-target', '#' + previewID); + var cancelButton = clone.querySelector('[data-js-cancel-button]'); $(cancelButton).on('click', function (event) { // re-enable click/hover events on the reply button diff --git a/tildes/static/js/behaviors/markdown-edit-tab.js b/tildes/static/js/behaviors/markdown-edit-tab.js new file mode 100644 index 0000000..de5f84b --- /dev/null +++ b/tildes/static/js/behaviors/markdown-edit-tab.js @@ -0,0 +1,12 @@ +// Copyright (c) 2018 Tildes contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +$.onmount('[data-js-markdown-edit-tab]', function() { + $(this).click(function(event) { + var $editTextarea = $(this).closest('form').find('[name="markdown"]'); + var $previewDiv = $(this).closest('form').find('[data-js-markdown-preview]'); + + $editTextarea.removeClass('d-none'); + $previewDiv.addClass('d-none'); + }); +}); diff --git a/tildes/static/js/behaviors/markdown-preview-tab.js b/tildes/static/js/behaviors/markdown-preview-tab.js new file mode 100644 index 0000000..07660e6 --- /dev/null +++ b/tildes/static/js/behaviors/markdown-preview-tab.js @@ -0,0 +1,19 @@ +// Copyright (c) 2018 Tildes contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +$.onmount('[data-js-markdown-preview-tab]', function() { + $(this).click(function(event) { + var $editTextarea = $(this).closest('form').find('[name="markdown"]'); + var $previewDiv = $(this).closest('form').find('[data-js-markdown-preview]'); + + $editTextarea.addClass('d-none'); + $previewDiv.removeClass('d-none'); + }); + + $(this).on('after.success.ic success.ic', function(event) { + // Stop intercooler event from bubbling up past this button. This + // prevents behaviors on parent elements from mistaking a successful + // "preview" from a successful "submit". + event.stopPropagation(); + }); +}); diff --git a/tildes/static/js/behaviors/tab.js b/tildes/static/js/behaviors/tab.js new file mode 100644 index 0000000..c18105a --- /dev/null +++ b/tildes/static/js/behaviors/tab.js @@ -0,0 +1,9 @@ +// Copyright (c) 2018 Tildes contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +$.onmount('[data-js-tab]', function() { + $(this).click(function(event) { + $(this).siblings().removeClass('active'); + $(this).addClass('active'); + }); +}); diff --git a/tildes/tildes/routes.py b/tildes/tildes/routes.py index a805433..59948ce 100644 --- a/tildes/tildes/routes.py +++ b/tildes/tildes/routes.py @@ -154,6 +154,8 @@ def add_intercooler_routes(config: Configurator) -> None: ) add_ic_route("user_ban", "/ban", factory=user_by_username) + add_ic_route("markdown_preview", "/markdown_preview", factory=LoggedInFactory) + class LoggedInFactory: """Simple class to use as `factory` to restrict routes to logged-in users. diff --git a/tildes/tildes/templates/intercooler/markdown_preview.jinja2 b/tildes/tildes/templates/intercooler/markdown_preview.jinja2 new file mode 100644 index 0000000..cf82e5d --- /dev/null +++ b/tildes/tildes/templates/intercooler/markdown_preview.jinja2 @@ -0,0 +1,4 @@ +{# Copyright (c) 2018 Tildes contributors #} +{# SPDX-License-Identifier: AGPL-3.0-or-later #} + +{{ rendered_html|safe }} diff --git a/tildes/tildes/templates/macros/forms.jinja2 b/tildes/tildes/templates/macros/forms.jinja2 index 2448bb6..eb3308c 100644 --- a/tildes/tildes/templates/macros/forms.jinja2 +++ b/tildes/tildes/templates/macros/forms.jinja2 @@ -6,14 +6,31 @@ {{ caption }} Formatting help - +
+ +
  • + +
  • +
  • + +
  • +
    + +
    +
    {% endmacro %} {% macro topic_tagging(value=None, auto_focus=False, autocomplete_options=None) %} diff --git a/tildes/tildes/views/api/web/markdown_preview.py b/tildes/tildes/views/api/web/markdown_preview.py new file mode 100644 index 0000000..c03c2f1 --- /dev/null +++ b/tildes/tildes/views/api/web/markdown_preview.py @@ -0,0 +1,25 @@ +# Copyright (c) 2018 Tildes contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +"""Web API endpoint for previewing Markdown.""" + +from pyramid.request import Request +from webargs.pyramidparser import use_kwargs + +from tildes.lib.markdown import convert_markdown_to_safe_html +from tildes.schemas.comment import CommentSchema +from tildes.views.decorators import ic_view_config + + +@ic_view_config( + route_name="markdown_preview", + request_method="POST", + renderer="markdown_preview.jinja2", +) +@use_kwargs(CommentSchema(only=("markdown",))) +def markdown_preview(request: Request, markdown: str) -> dict: + """Render the provided text as Markdown.""" + # pylint: disable=unused-argument + + rendered_html = convert_markdown_to_safe_html(markdown) + return {"rendered_html": rendered_html}