From ca2f83c61c358e86a301ace9dc0ac8776826e8e0 Mon Sep 17 00:00:00 2001 From: Jonathan Trattner Date: Wed, 17 Mar 2021 10:29:50 -0400 Subject: [PATCH 1/2] Added functionality for debouncing and/or manually sending the editor's contents to Shiny. --- R/shinymarkdown.R | 37 ++++++++++++++++----- inst/assets/js/init-template.js | 58 ++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/R/shinymarkdown.R b/R/shinymarkdown.R index 35f00c8..58fc4ea 100644 --- a/R/shinymarkdown.R +++ b/R/shinymarkdown.R @@ -3,14 +3,25 @@ #' Create an instance of a Markdown editor for text that may require formatting. #' #' @param inputId The \code{input} prefix used to access the value. -#' @param min_height The editor's miniminum height value (pixels). Default = '300px'. Overwritten by the height parameter. -#' @param height The editor's height value, applied as a border-box. Example values include "300px", "100%", and "auto". Defaults to "500px". -#' @param preview_style The Markdown editor's preview style. Either "tab" or "vertical". Default is "tab". -#' @param preview_highlight Should the Markdown Editor's HTML preview have a highlighted element corresponding to the cursor position in the Markdown editor. Default is FALSE. -#' @param initial_edit_type Initial editor type: "markdown" or "wysiwyg". Default is "markdown". -#' @param hide_mode_switch Should the user be able to switch the editor mode from "wysiwyg" to "markdown" or vice versa? Default is TRUE. +#' @param refresh_rate The rate (ms) to send the editor's contents to Shiny. +#' Default is 1000ms. If \code{refresh = "manual"}, the editor will have a +#' button to manually send the editor's contents to Shiny. +#' @param min_height The editor's miniminum height value (pixels). Default = +#' '300px'. Overwritten by the height parameter. +#' @param height The editor's height value, applied as a border-box. Example +#' values include "300px", "100%", and "auto". Defaults to "500px". +#' @param preview_style The Markdown editor's preview style. Either "tab" or +#' "vertical". Default is "tab". +#' @param preview_highlight Should the Markdown Editor's HTML preview have a +#' highlighted element corresponding to the cursor position in the Markdown +#' editor. Default is FALSE. +#' @param initial_edit_type Initial editor type: "markdown" or "wysiwyg". +#' Default is "markdown". +#' @param hide_mode_switch Should the user be able to switch the editor mode +#' from "wysiwyg" to "markdown" or vice versa? Default is TRUE. #' @param language Editor language ISO code. Defaults to "en-us". -#' @param initial_value Should the editor have text already present? If so, supply a character string. Default is NULL. +#' @param initial_value Should the editor have text already present? If so, +#' supply a character string. Default is NULL. #' #' @return An instance of the markdown editor for use within a Shiny App. #' @@ -48,7 +59,12 @@ #' #' } #' -mdInput <- function(inputId, min_height = "300px", height = "500px", preview_style = "tab", preview_highlight = FALSE, initial_edit_type = "markdown", hide_mode_switch = TRUE, language = "en-us", initial_value = NULL) { +mdInput <- function(inputId, refresh_rate = 1000, min_height = "300px", height = "500px", preview_style = "tab", preview_highlight = FALSE, initial_edit_type = "markdown", hide_mode_switch = TRUE, language = "en-us", initial_value = NULL) { + + # Ensure correct spelling of manual if refresh_rate is non-numeric + if (!is.numeric(refresh_rate) && !grepl("\\bmanual\\b", refresh_rate)) { + stop("`refresh_rate` is not recognized. Please check your spelling and try again.") + } data <- list(inputId = inputId, min_height = min_height, @@ -59,7 +75,10 @@ mdInput <- function(inputId, min_height = "300px", height = "500px", preview_sty hide_mode_switch = base::tolower(hide_mode_switch), language = language, initial_value_lgl = ifelse(is.null(initial_value), FALSE, TRUE), - initial_value = initial_value + initial_value = initial_value, + manual_lgl = ifelse(refresh_rate == "manual", TRUE, FALSE), + auto_lgl = ifelse(refresh_rate != "manual", TRUE, FALSE), + refresh_rate = refresh_rate ) template <- readLines(system.file("assets/js/init-template.js", package = "shinymarkdown")) diff --git a/inst/assets/js/init-template.js b/inst/assets/js/init-template.js index 49bf47d..94ffc40 100644 --- a/inst/assets/js/init-template.js +++ b/inst/assets/js/init-template.js @@ -15,11 +15,61 @@ const {{inputId}}_editor = new Editor({ {{ /initial_value_lgl }} }); - /* When someone types in the editor, make the markdown and HTML available - as the Shiny/R inputs "shinymd_markdown" and "shinymd_html", respectively. */ - $('#' + '{{inputId}}' + '_editor').on('keyup', function() { +{{#auto_lgl}} + +// define debounce function +const debounce = function(func, delay) { + let timeout; + + return function executed(...args) { + const later = function() { + clearTimeout(timeout); + func(...args); + }; + + clearTimeout(timeout); + timeout = setTimeout(later, delay); + + }; + +}; + +// define getEditorContents as a debounced function that sets the Shiny input values appropriately +var getEditorContents = debounce(function() { Shiny.setInputValue("{{inputId}}" + "_markdown", {{inputId}}_editor.getMarkdown()); Shiny.setInputValue("{{inputId}}" + "_html", {{inputId}}_editor.getHtml()); - }) +}, {{refresh_rate}}); + +/* When someone types in the editor, make the markdown and HTML available + as the Shiny/R inputs "shinymd_markdown" and "shinymd_html", respectively. + This occurs after the debounce time to improve performance of Shiny apps.*/ +$('#' + '{{inputId}}' + '_editor').on('keyup', getEditorContents); + +}); +{{ /auto_lgl }} + +{{#manual_lgl}} + +// create var switchSection which isolates the specific editor's footer button div +switchSection = $('#' + '{{inputId}}' + '_editor .te-mode-switch-section')[0]; + +// add a new button with sendShiny as its id +$(switchSection).append(''); + +// Add the button class and change some CSS defaults +$("#sendShiny").addClass("te-switch-button").css({"height": "19px", "width": "120px"}); + +// when the button is pressed add the active class and send the Shiny input values +$("#sendShiny").on("mousedown", function() { + $(this).addClass("active"); + Shiny.setInputValue("{{inputId}}" + "_markdown", {{inputId}}_editor.getMarkdown()); + Shiny.setInputValue("{{inputId}}" + "_html", {{inputId}}_editor.getHtml()); }); + +// when the button is done being pressed remove the active class +$("#sendShiny").on("mouseup", function() {$(this).removeClass("active");}); + +}); + +{{ /manual_lgl }} From 0a08ba2dd486a39f6b1cde80209ef5f5e65cc70f Mon Sep 17 00:00:00 2001 From: Jonathan Trattner Date: Wed, 17 Mar 2021 10:37:41 -0400 Subject: [PATCH 2/2] Update documentation --- man/mdInput.Rd | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/man/mdInput.Rd b/man/mdInput.Rd index 2820b67..6ebb6a7 100644 --- a/man/mdInput.Rd +++ b/man/mdInput.Rd @@ -6,6 +6,7 @@ \usage{ mdInput( inputId, + refresh_rate = 1000, min_height = "300px", height = "500px", preview_style = "tab", @@ -19,21 +20,33 @@ mdInput( \arguments{ \item{inputId}{The \code{input} prefix used to access the value.} -\item{min_height}{The editor's miniminum height value (pixels). Default = '300px'. Overwritten by the height parameter.} +\item{refresh_rate}{The rate (ms) to send the editor's contents to Shiny. +Default is 1000ms. If \code{refresh = "manual"}, the editor will have a +button to manually send the editor's contents to Shiny.} -\item{height}{The editor's height value, applied as a border-box. Example values include "300px", "100\%", and "auto". Defaults to "500px".} +\item{min_height}{The editor's miniminum height value (pixels). Default = +'300px'. Overwritten by the height parameter.} -\item{preview_style}{The Markdown editor's preview style. Either "tab" or "vertical". Default is "tab".} +\item{height}{The editor's height value, applied as a border-box. Example +values include "300px", "100\%", and "auto". Defaults to "500px".} -\item{preview_highlight}{Should the Markdown Editor's HTML preview have a highlighted element corresponding to the cursor position in the Markdown editor. Default is FALSE.} +\item{preview_style}{The Markdown editor's preview style. Either "tab" or +"vertical". Default is "tab".} -\item{initial_edit_type}{Initial editor type: "markdown" or "wysiwyg". Default is "markdown".} +\item{preview_highlight}{Should the Markdown Editor's HTML preview have a +highlighted element corresponding to the cursor position in the Markdown +editor. Default is FALSE.} -\item{hide_mode_switch}{Should the user be able to switch the editor mode from "wysiwyg" to "markdown" or vice versa? Default is TRUE.} +\item{initial_edit_type}{Initial editor type: "markdown" or "wysiwyg". +Default is "markdown".} + +\item{hide_mode_switch}{Should the user be able to switch the editor mode +from "wysiwyg" to "markdown" or vice versa? Default is TRUE.} \item{language}{Editor language ISO code. Defaults to "en-us".} -\item{initial_value}{Should the editor have text already present? If so, supply a character string. Default is NULL.} +\item{initial_value}{Should the editor have text already present? If so, +supply a character string. Default is NULL.} } \value{ An instance of the markdown editor for use within a Shiny App.