├── .Rbuildignore ├── DESCRIPTION ├── NAMESPACE ├── R ├── diff_to_markup.R ├── hello.R ├── macros.R ├── render_changes.R ├── shinytrack.R └── trackChanges.R ├── README-NOT.md ├── README.Rmd ├── README.md ├── inst ├── rstudio │ └── addins.dcf └── style │ └── diff_display.css ├── man ├── applyChanges.Rd ├── checkTmpPath.Rd ├── diff_to_markup.Rd ├── getSesValues.Rd ├── hello.Rd ├── initializeChanges.Rd ├── makeBuildScript.Rd ├── refreshDiff.Rd ├── render_changes.Rd ├── render_html_add.Rd ├── render_html_comment.Rd ├── render_html_delete.Rd ├── render_html_highlight.Rd ├── render_html_substitution.Rd ├── trackAdd.Rd ├── trackChanges.Rd ├── trackChangesViewer.Rd ├── trackComment.Rd ├── trackDelete.Rd ├── trackHighlight.Rd └── trackSubstitute.Rd └── trackchanges.gif /.Rbuildignore: -------------------------------------------------------------------------------- 1 | trackchanges.gif 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: trackmd 2 | Type: Package 3 | Title: RStudio Addin for Tracking Document Changes 4 | Version: 0.1.0 5 | Authors@R: c( 6 | person("Sam", "Tyner", email = "sctyner90@gmail.com", role = c("aut", "cre")), 7 | person("Zachary", "Foster", email = "TBA", role = "aut")) 8 | Description: More about what it does (maybe more than one line) 9 | Use four spaces when indenting paragraphs within the Description. 10 | Depends: 11 | R (>= 3.0.0) 12 | Imports: 13 | rstudioapi (>= 0.4), 14 | shiny (>= 0.13), 15 | miniUI (>= 0.1.1), 16 | shinyAce (>= 0.3.1), 17 | readr, 18 | servr 19 | License: MIT 20 | LazyData: true 21 | RoxygenNote: 6.0.1 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(trackAdd) 4 | export(trackChanges) 5 | export(trackComment) 6 | export(trackDelete) 7 | export(trackHighlight) 8 | export(trackSubstitute) 9 | import(miniUI) 10 | import(rstudioapi) 11 | import(shiny) 12 | import(shinyAce) 13 | -------------------------------------------------------------------------------- /R/diff_to_markup.R: -------------------------------------------------------------------------------- 1 | #' Create track changes diff 2 | #' 3 | #' Compares two strings and creates a diff using the markdown track changes syntax. 4 | #' 5 | #' @param before (\code{character} of length 1) The text before the changes. 6 | #' @param after (\code{character} of length 1) The text after the changes. 7 | #' @param style (\code{character} of length 1) The type of change tags to add. 8 | #' Either "critic" for Critical Markdown tags, or "pandoc" for the track 9 | #' change tags used by pandoc. 10 | #' 11 | #' @keywords internal 12 | diff_to_markup <- function(before, after, style = "critic") { 13 | 14 | # Decide which diff formatting functions to use 15 | if (style == "critic") { 16 | addFunc <- criticAdd 17 | delFunc <- criticDel 18 | subFunc <- criticSub 19 | } else if (style == "pandoc") { 20 | addFunc <- pandocAdd 21 | delFunc <- pandocDel 22 | subFunc <- pandocSub 23 | } else { 24 | stop('Invlaid style "', style, '" entered.') 25 | } 26 | 27 | # Convert string to characters 28 | before <- unlist(strsplit(before, split = "")) 29 | after <- unlist(strsplit(after, split = "")) 30 | 31 | # Calculate diff 32 | difference <- diffobj::ses(before, after) 33 | 34 | apply_addition <- function(text, index, afterStart, afterEnd) { 35 | c(text[seq_len(index)], 36 | addFunc(after[afterStart:afterEnd]), 37 | text[index + seq_len(length(text) - index)]) 38 | } 39 | 40 | apply_deletion <- function(text, beforeStart, beforeEnd) { 41 | c(text[seq_len(beforeStart - 1)], 42 | delFunc(before[beforeStart:beforeEnd]), 43 | text[beforeEnd + seq_len(length(text) - beforeEnd)]) 44 | } 45 | 46 | apply_change <- function(text, beforeStart, beforeEnd, afterStart, afterEnd) { 47 | c(text[seq_len(beforeStart - 1)], 48 | subFunc(before[beforeStart:beforeEnd], after[afterStart:afterEnd]), 49 | text[beforeEnd + seq_len(length(text) - beforeEnd)]) 50 | } 51 | 52 | text <- before 53 | for (aDiff in rev(difference)) { 54 | sesValues <- getSesValues(aDiff) 55 | if (sesValues$type == "a") { 56 | text <- apply_addition(text, 57 | sesValues$beforeStart, 58 | sesValues$afterStart, 59 | sesValues$afterEnd) 60 | } else if (sesValues$type == "d") { 61 | text <- apply_deletion(text, 62 | sesValues$beforeStart, 63 | sesValues$beforeEnd) 64 | } else if (sesValues$type == "c") { 65 | text <- apply_change(text, 66 | sesValues$beforeStart, 67 | sesValues$beforeEnd, 68 | sesValues$afterStart, 69 | sesValues$afterEnd) 70 | } 71 | } 72 | 73 | # Convert bact to single string 74 | return(paste0(text, collapse = "")) 75 | } 76 | 77 | 78 | #' Parse SES strings 79 | #' 80 | #' Parse a SES string returned by \code{\link[diffobj]{ses}} into a list of values. 81 | #' 82 | #' @param sesText (\code{character} of length 1) A SES string 83 | #' 84 | #' @keywords internal 85 | getSesValues <- function(sesText) { 86 | # Extract relevant values from string 87 | matches <- as.list(stringr::str_match(sesText, "([0-9]+),?([0-9]*)([a-z]+)([0-9]+),?([0-9]*)")[, -1]) 88 | names(matches) <- c("beforeStart", "beforeEnd", "type", "afterStart", "afterEnd") 89 | 90 | # Fill in implied values 91 | if (matches$beforeEnd == "") { 92 | matches$beforeEnd <- matches$beforeStart 93 | } 94 | if (matches$afterEnd == "") { 95 | matches$afterEnd <- matches$afterStart 96 | } 97 | 98 | # Convert indexes to numeric 99 | matches$beforeEnd <- as.numeric(matches$beforeEnd) 100 | matches$afterEnd <- as.numeric(matches$afterEnd) 101 | matches$beforeStart <- as.numeric(matches$beforeStart) 102 | matches$afterStart <- as.numeric(matches$afterStart) 103 | 104 | return(matches) 105 | } 106 | 107 | 108 | #' @keywords internal 109 | criticAdd <- function(text) { 110 | paste0("{++", paste0(text, collapse = ""), "++}") 111 | } 112 | 113 | #' @keywords internal 114 | criticDel <- function(text) { 115 | paste0("{--", paste0(text, collapse = ""), "--}") 116 | } 117 | 118 | #' @keywords internal 119 | criticSub <- function(from, to) { 120 | paste0("{~~", paste0(from, collapse = ""), "~>", paste0(to, collapse = ""), "~~}") 121 | } 122 | 123 | 124 | #' @keywords internal 125 | pandocAdd <- function(text, author = "unknown", timestamp = lubridate::date()) { 126 | paste0('', paste0(text, collapse = ""), '') 127 | } 128 | 129 | #' @keywords internal 130 | pandocDel <- function(text, author = "unknown", timestamp = lubridate::date()) { 131 | paste0('', paste0(text, collapse = ""), '') 132 | } 133 | 134 | #' @keywords internal 135 | pandocSub <- function(from, to, author = "unknown", timestamp = lubridate::date()) { 136 | paste0(pandocDel(from, author = author, timestamp = timestamp), 137 | pandocAdd(from, author = author, timestamp = timestamp)) 138 | } 139 | -------------------------------------------------------------------------------- /R/hello.R: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | # 3 | # This is an example function named 'hello' 4 | # which prints 'Hello, world!'. 5 | # 6 | # You can learn more about package authoring with RStudio at: 7 | # 8 | # http://r-pkgs.had.co.nz/ 9 | # 10 | # Some useful keyboard shortcuts for package authoring: 11 | # 12 | # Build and Reload Package: 'Cmd + Shift + B' 13 | # Check Package: 'Cmd + Shift + E' 14 | # Test Package: 'Cmd + Shift + T' 15 | 16 | hello <- function() { 17 | print("Hello, world!") 18 | } 19 | -------------------------------------------------------------------------------- /R/macros.R: -------------------------------------------------------------------------------- 1 | #' Tracking an addition 2 | #' 3 | #' Call this function as an addin to add text at the cursor postion. 4 | #' 5 | #' @export 6 | trackAdd <- function() { 7 | con <- rstudioapi::getActiveDocumentContext() 8 | 9 | # get cursor position 10 | docPos <- con$selection[[1]]$range$end 11 | 12 | # Add markup 13 | rstudioapi::insertText("{++++}", id = con$id) 14 | 15 | # move cursor 16 | docPosNew <- docPos + c(0, 3) 17 | rstudioapi::setCursorPosition(docPosNew, id = con$id) 18 | } 19 | 20 | #' Tracking a substitution 21 | #' 22 | #' Call this function as an addin to add the markdown track changes output for substitution. 23 | #' 24 | #' @export 25 | trackSubstitute <- function() { 26 | con <- rstudioapi::getSourceEditorContext() 27 | 28 | # Get selected text 29 | selection <- con$selection[[1]]$text 30 | 31 | # Add markup 32 | docPos <- con$selection[[1]]$range$end 33 | rstudioapi::insertText(paste0("{~~", selection, "~>~~}"), 34 | id = con$id) 35 | 36 | # Move cursor 37 | docPosNew <- docPos + c(0, 5) 38 | rstudioapi::setCursorPosition(docPosNew, id = con$id) 39 | } 40 | 41 | 42 | #' Tracking highlighting 43 | #' 44 | #' Call this function as an addin to add the markdown track changes output for highlighting 45 | #' 46 | #' @export 47 | trackHighlight <- function() { 48 | con <- rstudioapi::getSourceEditorContext() 49 | 50 | # Get selected text 51 | selection <- con$selection[[1]]$text 52 | 53 | # Add markup 54 | docPos <- con$selection[[1]]$range$end 55 | rstudioapi::insertText(paste0("{==", selection, "==}{>><<}"), 56 | id = con$id) 57 | 58 | # Move cursor 59 | docPosNew <- docPos + c(0, 9) 60 | rstudioapi::setCursorPosition(docPosNew, id = con$id) 61 | } 62 | 63 | #' Tracking a deletion 64 | #' 65 | #' Call this function as an addin to delete highlighted text. 66 | #' 67 | #' @export 68 | trackDelete <- function() { 69 | con <- rstudioapi::getActiveDocumentContext() 70 | 71 | # start of highlight 72 | startPos <- con$selection[[1]]$range$start 73 | 74 | # end of highlight 75 | endPos <- con$selection[[1]]$range$end 76 | 77 | # Add markup 78 | rstudioapi::insertText(location = startPos, "{--", id = con$id) 79 | rstudioapi::insertText(location = endPos + c(0, 3), "--}", 80 | id = con$id) 81 | 82 | # move cursor 83 | startPosNew <- endPos + c(0, 6) 84 | rstudioapi::setCursorPosition(startPosNew, id = con$id) 85 | } 86 | 87 | #' Insert a comment 88 | #' 89 | #' Call this function as an addin to add a comment in a tracked doc at cursor position. 90 | #' 91 | #' @export 92 | trackComment <- function() { 93 | con <- rstudioapi::getActiveDocumentContext() 94 | 95 | # cursor position 96 | docPos <- con$selection[[1]]$range$end 97 | 98 | # insert markup 99 | rstudioapi::insertText(location = docPos, "{>><<}", id = con$id) 100 | 101 | # move cursor 102 | startPosNew <- docPos + c(0, 3) 103 | rstudioapi::setCursorPosition(startPosNew, id = con$id) 104 | } 105 | -------------------------------------------------------------------------------- /R/render_changes.R: -------------------------------------------------------------------------------- 1 | #' Render changes as HTML 2 | #' 3 | #' Makes and HTML rendering of the track changes markup in markdown. 4 | #' 5 | #' @param text The markdown as a character vector with a single value (i.e. not 6 | #' one value per line). Either `text` or `file` must be used, but not both. 7 | #' @param file The file containing the markdown to be rendered. Either `text` or 8 | #' `file` must be used, but not both. 9 | #' @inheritDotParams rmarkdown::render 10 | #' 11 | #' @keywords internal 12 | render_changes <- function(text = NULL, file = NULL, ...) { 13 | 14 | # Check user input 15 | if (sum(c(is.null(text), is.null(file))) != 1) { 16 | stop("Either `text` or `file` must be used, but not both.") 17 | } 18 | if (! is.null(text) && length(text) != 1) { 19 | stop("Input to `text` must be of length 1") 20 | } 21 | 22 | # Read file if needed 23 | if (is.null(text)) { 24 | text = readr::read_file(file) 25 | } 26 | 27 | # Replace additions markup 28 | text <- gsub(text, pattern = "\\{\\+\\+(.*?)\\+\\+\\}", 29 | replacement = render_html_add("\\1")) 30 | text <- gsub(text, pattern = "\\{--(.*?)--\\}", 31 | replacement = render_html_delete("\\1")) 32 | text <- gsub(text, pattern = "\\{~~(.*?)~>(.*?)~~\\}", 33 | replacement = render_html_substitution("\\1", "\\2")) 34 | text <- gsub(text, pattern = "\\{==(.*?)==\\}\\{>>(.*?)<<\\}", 35 | replacement = render_html_highlight("\\1", "\\2")) 36 | text <- gsub(text, pattern = "\\{>>(.*?)<<\\}", 37 | replacement = render_html_comment("\\1")) 38 | 39 | # Convert to html 40 | temp_input <- tempfile() 41 | on.exit(file.remove(temp_input)) 42 | readr::write_file(text, path = temp_input) 43 | rmarkdown::render(temp_input, rmarkdown::html_document(), ...) 44 | } 45 | 46 | 47 | #' Format text as an addition 48 | #' 49 | #' Format text as an addition using HTML 50 | #' 51 | #' @param text The text to format 52 | #' 53 | #' @keywords internal 54 | render_html_add <- function(text) { 55 | paste0('', text, '') 56 | } 57 | 58 | 59 | #' Format text as a deletion 60 | #' 61 | #' Format text as a deletion using HTML 62 | #' 63 | #' @param text The text to format 64 | #' 65 | #' @keywords internal 66 | render_html_delete <- function(text) { 67 | paste0('', text, '') 68 | } 69 | 70 | 71 | #' Format text as a substitution 72 | #' 73 | #' Format text as a substitution using HTML 74 | #' 75 | #' @param from What the text was before the change 76 | #' @param to What the text is after the change 77 | #' 78 | #' @keywords internal 79 | render_html_substitution <- function(from, to) { 80 | paste0(render_html_delete(from), render_html_add(to)) 81 | } 82 | 83 | 84 | #' Format text as a substitution 85 | #' 86 | #' Format text as a substitution using HTML 87 | #' 88 | #' @param from What the text was before the change 89 | #' @param to What the text is after the change 90 | #' 91 | #' @keywords internal 92 | render_html_highlight <- function(text, comment) { 93 | paste0('
', text, '', comment, '
') 94 | } 95 | 96 | 97 | #' Format text as a comment 98 | #' 99 | #' Format text as a comment using HTML 100 | #' 101 | #' @param from What the text was before the change 102 | #' @param to What the text is after the change 103 | #' 104 | #' @keywords internal 105 | render_html_comment <- function(comment) { 106 | paste0('
💬', comment, '
') 107 | } 108 | -------------------------------------------------------------------------------- /R/shinytrack.R: -------------------------------------------------------------------------------- 1 | #' Edit and track changes within markdown 2 | #' @import shiny 3 | #' @import rstudioapi 4 | #' @import miniUI 5 | #' @import shinyAce 6 | trackChangesViewer <- function() { 7 | 8 | # necessary paths 9 | # tmp <- file.path(tempdir(),"trackmd") 10 | # dir.create(tmp, showWarnings = FALSE) 11 | # tmpfle <- tempfile(tmpdir = tmp, fileext = ".html") 12 | # readr::write_file(x = "here is some html", path = tmpfle) 13 | # shiny::addResourcePath("trackmd", tmp) 14 | 15 | # Get the document context. 16 | context <- rstudioapi::getSourceEditorContext() 17 | doc <- context$contents 18 | tags <- shiny::tags 19 | cmLink <- tags$a(href = "http://criticmarkup.com/", "CriticMarkup") 20 | 21 | ui <- miniPage( 22 | # includeHighlightJs(), 23 | # title 24 | gadgetTitleBar("Record Additions & Deletions"), 25 | 26 | miniContentPanel( 27 | h4("Inspired by ", cmLink, " for tracking changes in Markdown."), 28 | hr(), 29 | # miniTabstripPanel( 30 | # edit panel 31 | # miniTabPanel( title = "Edit", 32 | # fluidRow( 33 | 34 | # # render the markup as html 35 | # column(6, 36 | # includeHTML(tmpfle) 37 | # ), 38 | 39 | # editor for marking up 40 | # column(6, 41 | shinyAce::aceEditor("editor", value = paste(doc, collapse = "\n")) 42 | #uiOutput("document", container = rCodeContainer) 43 | # ) 44 | # ) 45 | # ), 46 | 47 | # # review panel 48 | # miniTabPanel(title = "Review", 49 | # fluidRow( 50 | # column(8, 51 | # includeHTML(tmpfle) 52 | # ), 53 | # column(3, 54 | # actionButton("next", "Next")) 55 | # ) 56 | # 57 | # ) 58 | 59 | # ) 60 | # stableColumnLayout( 61 | # checkboxInput("brace.newline", "Place left braces '{' on a new line?", FALSE), 62 | # numericInput("indent", "Indent size: ", 2), 63 | # numericInput("width", "Column width: ", 60) 64 | # ), 65 | #uiOutput("document", container = rCodeContainer) 66 | ) 67 | ) 68 | 69 | server <- function(input, output, session) { 70 | 71 | observe({ 72 | updateAceEditor( 73 | session, "editor" 74 | ) 75 | }) 76 | 77 | observeEvent(input$done, { 78 | contents <- input$editor 79 | aftr <- paste0(contents, collapse = "\n") 80 | markedup <- trackmd:::diff_to_markup(paste0(doc, collapse = "\n"), aftr) 81 | rstudioapi::setDocumentContents(markedup, id = context$id) 82 | invisible(stopApp()) 83 | }) 84 | 85 | } 86 | 87 | viewer <- dialogViewer("Record Changes", width = 1000, height = 800) 88 | runGadget(ui, server, viewer = viewer) 89 | 90 | } 91 | 92 | 93 | #' Make an R script to render changes 94 | #' 95 | #' Makes an R script to convert markdown with critic markup to an HTML with track changes. 96 | #' 97 | #' @param siteDir The directory to save the script in. 98 | #' 99 | #' @keywords internal 100 | makeBuildScript <- function(siteDir) { 101 | readr::write_file("trackmd:::render_changes(file = commandArgs()[1], output = commandArgs()[2])", 102 | path = file.path(siteDir, "build.R")) 103 | } -------------------------------------------------------------------------------- /R/trackChanges.R: -------------------------------------------------------------------------------- 1 | #' Start tracking changes on a file 2 | #' 3 | #' Start tracking all changes to a file and preview changes in the viewer. 4 | #' The changes will be added to the original file as markup when done. 5 | #' 6 | #' @param path The path to the file to track. 7 | #' @param wait The number of seconds between each check for changes 8 | #' 9 | #' @examples 10 | #' trackChanges("my_file.txt") 11 | #' 12 | #' @export 13 | trackChanges <- function(path, wait = 1){ 14 | 15 | # when was the file at path last changed? 16 | last.modified <- file.info(path)$mtime 17 | # initialize changes 18 | tmpDirPath <- initializeChanges(path) 19 | # filename of copy of the file in our directory 20 | tmpfilePath <- file.path(tmpDirPath, basename(path)) 21 | 22 | # get initial html 23 | refreshDiff(origPath = tmpfilePath, newPath = path, 24 | outDir = tmpDirPath) 25 | 26 | # load the html in the viewer 27 | servr::httw(dir = tmpDirPath, daemon = T) 28 | 29 | daemons <- servr::daemon_list() 30 | daemonID <- daemons[length(daemons)] 31 | 32 | # set up checking of tmp dir 33 | check <- function(){ 34 | if(last.modified != file.info(path)$mtime ){ 35 | refreshDiff(origPath = tmpfilePath, newPath = path, 36 | outDir = tmpDirPath) 37 | last.modified <<- file.info(path)$mtime 38 | } 39 | if (daemonID %in% servr::daemon_list()){ 40 | later::later(check, wait) 41 | } else { 42 | 43 | applyChanges(path, tmpfilePath) 44 | 45 | } 46 | 47 | } 48 | later::later(check, wait) 49 | } 50 | 51 | 52 | #' Initialize track changes tmp dir 53 | #' 54 | #' Initialize track changes tmp dir 55 | #' 56 | #' @param path The path to the users original file to track. 57 | #' 58 | #' @keywords internal 59 | initializeChanges <- function(path){ 60 | # Create temporary directory where the diff is hosted 61 | tmpDirPath <- checkTmpPath(tempdir()) 62 | dir.create(tmpDirPath, showWarnings = FALSE) 63 | 64 | # Step 2: copy the user's file (path) 65 | copypath <- file.path(tmpDirPath, basename(path)) 66 | file.copy(from = path, to = copypath) 67 | 68 | return(tmpDirPath) 69 | } 70 | 71 | #' Create a HTML of the diff 72 | #' 73 | #' Create a HTML rendering of the diff 74 | #' 75 | #' @param origPath The path to the file being edited 76 | #' @param newPath The path to the copy of the inital state of the file. 77 | #' @param outDir Where to save the "index.html" file with the rendered diff. 78 | #' 79 | #' @keywords internal 80 | refreshDiff <- function(origPath, newPath, outDir) { 81 | orig <- readr::read_file(origPath) 82 | new <- readr::read_file(newPath) 83 | diff <- trackmd:::diff_to_markup(orig, new) 84 | 85 | render_changes(text = diff, output_dir = outDir, output_file = "index.html", quiet = TRUE) 86 | 87 | } 88 | 89 | #' Replace user's file with diff markup 90 | #' 91 | #' Replace user's file with diff markup 92 | #' 93 | #' @param origPath The path to the file being edited 94 | #' @param newPath The path to the copy of the inital state of the file. 95 | #' 96 | #' @keywords internal 97 | applyChanges <- function(origPath, newPath){ 98 | # Create diff 99 | new <- readr::read_file(origPath) 100 | old <- readr::read_file(newPath) 101 | diff <- diff_to_markup(old, new) 102 | 103 | # Replace user's file 104 | readr::write_file(diff, path = origPath) 105 | 106 | # delete temp file 107 | unlink(tmpfilePath, recursive = T) 108 | } 109 | 110 | #' Fix problems with temp dir path 111 | #' 112 | #' Fix problems with temp dir path 113 | #' 114 | #' @param tmpfilePath The path to fix 115 | #' 116 | #' @keywords internal 117 | checkTmpPath <- function(tmpfilePath){ 118 | fixedPath <- gsub(pattern = "//", replace = "/" , tmpfilePath, fixed = T) 119 | return(fixedPath) 120 | } 121 | -------------------------------------------------------------------------------- /README-NOT.md: -------------------------------------------------------------------------------- 1 | 2 | [![Project Status: Abandoned – Initial development has started, but there has not yet been a stable, usable release; the project has been abandoned and the author(s) do not intend on continuing development.](https://www.repostatus.org/badges/latest/abandoned.svg)](https://www.repostatus.org/#abandoned) 3 | 4 | 5 | trackmd 6 | ======= 7 | 8 | RStudio addin for tracking changes in Markdown format. Inspired by 9 | [Critic Markup](http://criticmarkup.com/). 10 | 11 | Created at the RopenSci unconference. [Original 12 | Issue](https://github.com/ropensci/unconf18/issues/76) 13 | 14 | Main feature: `trackmd::trackChanges()` 15 | ======================================= 16 | 17 | How to use: 18 | 19 | 1. Open up the .Rmd or .md you want to edit. 20 | 2. Run `trackmd::trackChanges(file)` where `file` is a character with 21 | the path of the file you just opened. 22 | 3. Make some changes in the file. 23 | 4. Save the file. 24 | 5. See your changes in the viewer! 25 | 6. Run `servr::daemon_stop()`, then click in the .Rmd or .md you edited 26 | to see the marked up file with changes. 27 | 28 | ![](trackchanges.gif) 29 | 30 | Future 31 | ====== 32 | 33 | - \[ \] A little buggy. Fix those. 34 | - \[ \] "Smarter" coloring: word by word instead of character by 35 | character? 36 | - \[ \] Addins to turn track changes on and off 37 | - \[ \] Accept / reject changes 38 | - \[ \] Communicate with track changes and comments in .docx files 39 | (Ultimate goal of original issue! Collaborate seamlessly with Word 40 | users.) 41 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: md_document 3 | --- 4 | 5 | 6 | 7 | [![Project Status: WIP - Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](http://www.repostatus.org/badges/latest/wip.svg)](http://www.repostatus.org/#wip) 8 | 9 | # trackmd 10 | 11 | RStudio addin for tracking changes in Markdown format. Inspired by [Critic Markup](http://criticmarkup.com/). 12 | 13 | Created at the RopenSci unconference. [Original Issue](https://github.com/ropensci/unconf18/issues/76) 14 | 15 | # Main feature: `trackmd::trackChanges()` 16 | 17 | How to use: 18 | 19 | 1. Open up the .Rmd or .md you want to edit. 20 | 2. Run `trackmd::trackChanges(file)` where `file` is a character with the path of the file you just opened. 21 | 3. Make some changes in the file. 22 | 4. Save the file. 23 | 5. See your changes in the viewer! 24 | 6. Run `servr::daemon_stop()`, then click in the .Rmd or .md you edited to see the marked up file with changes. 25 | 26 | ![](trackchanges.gif) 27 | 28 | # Future 29 | 30 | - [ ] A little buggy. Fix those. 31 | - [ ] "Smarter" coloring: word by word instead of character by character? 32 | - [ ] Addins to turn track changes on and off 33 | - [ ] Accept / reject changes 34 | - [ ] Communicate with track changes and comments in .docx files (Ultimate goal of original issue! Collaborate seamlessly with Word users.) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trackmd 2 | 3 | [![Project Status: Abandoned](https://www.repostatus.org/badges/latest/abandoned.svg)](https://www.repostatus.org/#abandoned) 4 | 5 | This repository has been archived. The former README is now in [README-NOT.md](README-NOT.md). 6 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Add 2 | Description: Tracks addition at cursor position 3 | Binding: trackAdd 4 | Interactive: false 5 | 6 | Name: Substitute 7 | Description: Tracks substitution at cursor position 8 | Binding: trackSubstitute 9 | Interactive: false 10 | 11 | Name: Highlight 12 | Description: Tracks highlighting at cursor position 13 | Binding: trackHighlight 14 | Interactive: false 15 | 16 | Name: Delete 17 | Description: Marks highlighted text for deletion 18 | Binding: trackDelete 19 | Interactive: false 20 | 21 | Name: Comment 22 | Description: Inserts comment at cursor position 23 | Binding: trackComment 24 | Interactive: false 25 | 26 | Name: Record Changes 27 | Description: Shiny UI for editing, and recording text changes in the markup trackmd format. 28 | Binding: trackChangesViewer 29 | Interactive: true 30 | -------------------------------------------------------------------------------- /inst/style/diff_display.css: -------------------------------------------------------------------------------- 1 | .comment { 2 | position: relative; 3 | display: inline-block; 4 | border-bottom: 1px dotted black; 5 | } 6 | 7 | .comment .commenttext { 8 | visibility: hidden; 9 | width: 120px; 10 | background-color: #ffe392; 11 | color: black; 12 | text-align: center; 13 | border-radius: 6px; 14 | padding: 5px 0; 15 | position: absolute; 16 | z-index: 1; 17 | top: -5px; 18 | left: 110%; 19 | } 20 | 21 | .comment .commenttext::after { 22 | content: ""; 23 | position: absolute; 24 | top: 50%; 25 | right: 100%; 26 | margin-top: -5px; 27 | border-width: 5px; 28 | border-style: solid; 29 | border-color: transparent #ffe392 transparent transparent; 30 | } 31 | 32 | .comment:hover .commenttext { 33 | visibility: visible; 34 | } -------------------------------------------------------------------------------- /man/applyChanges.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trackChanges.R 3 | \name{applyChanges} 4 | \alias{applyChanges} 5 | \title{Replace user's file with diff markup} 6 | \usage{ 7 | applyChanges(origPath, newPath) 8 | } 9 | \arguments{ 10 | \item{origPath}{The path to the file being edited} 11 | 12 | \item{newPath}{The path to the copy of the inital state of the file.} 13 | } 14 | \description{ 15 | Replace user's file with diff markup 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/checkTmpPath.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trackChanges.R 3 | \name{checkTmpPath} 4 | \alias{checkTmpPath} 5 | \title{Fix problems with temp dir path} 6 | \usage{ 7 | checkTmpPath(tmpfilePath) 8 | } 9 | \arguments{ 10 | \item{tmpfilePath}{The path to fix} 11 | } 12 | \description{ 13 | Fix problems with temp dir path 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/diff_to_markup.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/diff_to_markup.R 3 | \name{diff_to_markup} 4 | \alias{diff_to_markup} 5 | \title{Create track changes diff} 6 | \usage{ 7 | diff_to_markup(before, after, style = "critic") 8 | } 9 | \arguments{ 10 | \item{before}{(\code{character} of length 1) The text before the changes.} 11 | 12 | \item{after}{(\code{character} of length 1) The text after the changes.} 13 | 14 | \item{style}{(\code{character} of length 1) The type of change tags to add. 15 | Either "critic" for Critical Markdown tags, or "pandoc" for the track 16 | change tags used by pandoc.} 17 | } 18 | \description{ 19 | Compares two strings and creates a diff using the markdown track changes syntax. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/getSesValues.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/diff_to_markup.R 3 | \name{getSesValues} 4 | \alias{getSesValues} 5 | \title{Parse SES strings} 6 | \usage{ 7 | getSesValues(sesText) 8 | } 9 | \arguments{ 10 | \item{sesText}{(\code{character} of length 1) A SES string} 11 | } 12 | \description{ 13 | Parse a SES string returned by \code{\link[diffobj]{ses}} into a list of values. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/hello.Rd: -------------------------------------------------------------------------------- 1 | \name{hello} 2 | \alias{hello} 3 | \title{Hello, World!} 4 | \usage{ 5 | hello() 6 | } 7 | \description{ 8 | Prints 'Hello, world!'. 9 | } 10 | \examples{ 11 | hello() 12 | } 13 | -------------------------------------------------------------------------------- /man/initializeChanges.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trackChanges.R 3 | \name{initializeChanges} 4 | \alias{initializeChanges} 5 | \title{Initialize track changes tmp dir} 6 | \usage{ 7 | initializeChanges(path) 8 | } 9 | \arguments{ 10 | \item{path}{The path to the users original file to track.} 11 | } 12 | \description{ 13 | Initialize track changes tmp dir 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/makeBuildScript.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinytrack.R 3 | \name{makeBuildScript} 4 | \alias{makeBuildScript} 5 | \title{Make an R script to render changes} 6 | \usage{ 7 | makeBuildScript(siteDir) 8 | } 9 | \arguments{ 10 | \item{siteDir}{The directory to save the script in.} 11 | } 12 | \description{ 13 | Makes an R script to convert markdown with critic markup to an HTML with track changes. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/refreshDiff.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trackChanges.R 3 | \name{refreshDiff} 4 | \alias{refreshDiff} 5 | \title{Create a HTML of the diff} 6 | \usage{ 7 | refreshDiff(origPath, newPath, outDir) 8 | } 9 | \arguments{ 10 | \item{origPath}{The path to the file being edited} 11 | 12 | \item{newPath}{The path to the copy of the inital state of the file.} 13 | 14 | \item{outDir}{Where to save the "index.html" file with the rendered diff.} 15 | } 16 | \description{ 17 | Create a HTML rendering of the diff 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/render_changes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render_changes.R 3 | \name{render_changes} 4 | \alias{render_changes} 5 | \title{Render changes as HTML} 6 | \usage{ 7 | render_changes(text = NULL, file = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{text}{The markdown as a character vector with a single value (i.e. not 11 | one value per line). Either `text` or `file` must be used, but not both.} 12 | 13 | \item{file}{The file containing the markdown to be rendered. Either `text` or 14 | `file` must be used, but not both.} 15 | 16 | \item{...}{Arguments passed on to \code{rmarkdown::render} 17 | \describe{ 18 | \item{input}{Input file (R script, Rmd, or plain markdown).} 19 | \item{output_format}{R Markdown output format to convert to. Pass 20 | \code{"all"} to render all formats defined within the file. Pass 21 | the name of a format (e.g. \code{"html_document"}) to render a single 22 | format or pass a vector of format names to render multiple formats. 23 | Alternatively you can pass an output format object; 24 | e.g. \code{html_document()}. If \code{NULL} is passed then the 25 | output format is the first one defined in the YAML metadata of the 26 | input file (defaulting to HTML if none is specified).} 27 | \item{output_options}{List of output options that can override the options 28 | specified in metadata (e.g. could be used to force \code{self_contained} or 29 | \code{mathjax = "local"}). Note that this is only valid when the output 30 | format is read from metadata (i.e. not a custom format object passed to 31 | \code{output_format}).} 32 | \item{output_file}{Output file. If \code{NULL} then a default based on 33 | the name of the input file is chosen.} 34 | \item{output_dir}{Output directory. An alternate directory to write 35 | the output file to (defaults to the directory of the input file).} 36 | \item{intermediates_dir}{Intermediate files directory. If \code{NULL}, 37 | intermediate files are written to the same directory as the input file; 38 | otherwise.} 39 | \item{knit_root_dir}{The working directory in which to knit the document; uses 40 | knitr's \code{root.dir} knit option. \code{NULL} means to follow the knitr 41 | default, which is to use the parent directory of the document.} 42 | \item{runtime}{The runtime target for rendering. \code{static} produces output 43 | intended for static files; \code{shiny} produces output suitable for use in a 44 | Shiny document (see \code{\link{run}}). The default, \code{auto}, 45 | allows the \code{runtime} target specified in the YAML metadata to take 46 | precedence, and renders for a \code{static} runtime target otherwise.} 47 | \item{clean}{\code{TRUE} to clean intermediate files created 48 | during rendering.} 49 | \item{params}{List of named parameters that override custom params 50 | specified within the YAML front-matter (e.g. specifying a dataset to 51 | read or a date range to confine output to). Pass \code{"ask"} to start 52 | an application that helps guide parameter configuration.} 53 | \item{knit_meta}{(For expert use) Meta data generated by \pkg{knitr}.} 54 | \item{envir}{The environment in which the code chunks are 55 | to be evaluated during knitting (can use 56 | \code{\link{new.env}()} to guarantee an empty new 57 | environment).} 58 | \item{run_pandoc}{Whether to run Pandoc to convert Markdown output.} 59 | \item{quiet}{\code{TRUE} to suppress printing of the 60 | pandoc command line.} 61 | \item{encoding}{The encoding of the input file; see 62 | \code{\link{file}}.} 63 | }} 64 | } 65 | \description{ 66 | Makes and HTML rendering of the track changes markup in markdown. 67 | } 68 | \keyword{internal} 69 | -------------------------------------------------------------------------------- /man/render_html_add.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render_changes.R 3 | \name{render_html_add} 4 | \alias{render_html_add} 5 | \title{Format text as an addition} 6 | \usage{ 7 | render_html_add(text) 8 | } 9 | \arguments{ 10 | \item{text}{The text to format} 11 | } 12 | \description{ 13 | Format text as an addition using HTML 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/render_html_comment.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render_changes.R 3 | \name{render_html_comment} 4 | \alias{render_html_comment} 5 | \title{Format text as a comment} 6 | \usage{ 7 | render_html_comment(comment) 8 | } 9 | \arguments{ 10 | \item{from}{What the text was before the change} 11 | 12 | \item{to}{What the text is after the change} 13 | } 14 | \description{ 15 | Format text as a comment using HTML 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/render_html_delete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render_changes.R 3 | \name{render_html_delete} 4 | \alias{render_html_delete} 5 | \title{Format text as a deletion} 6 | \usage{ 7 | render_html_delete(text) 8 | } 9 | \arguments{ 10 | \item{text}{The text to format} 11 | } 12 | \description{ 13 | Format text as a deletion using HTML 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/render_html_highlight.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render_changes.R 3 | \name{render_html_highlight} 4 | \alias{render_html_highlight} 5 | \title{Format text as a substitution} 6 | \usage{ 7 | render_html_highlight(text, comment) 8 | } 9 | \arguments{ 10 | \item{from}{What the text was before the change} 11 | 12 | \item{to}{What the text is after the change} 13 | } 14 | \description{ 15 | Format text as a substitution using HTML 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/render_html_substitution.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/render_changes.R 3 | \name{render_html_substitution} 4 | \alias{render_html_substitution} 5 | \title{Format text as a substitution} 6 | \usage{ 7 | render_html_substitution(from, to) 8 | } 9 | \arguments{ 10 | \item{from}{What the text was before the change} 11 | 12 | \item{to}{What the text is after the change} 13 | } 14 | \description{ 15 | Format text as a substitution using HTML 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/trackAdd.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{trackAdd} 4 | \alias{trackAdd} 5 | \title{Tracking an addition} 6 | \usage{ 7 | trackAdd() 8 | } 9 | \description{ 10 | Call this function as an addin to add text at the cursor postion. 11 | } 12 | -------------------------------------------------------------------------------- /man/trackChanges.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trackChanges.R 3 | \name{trackChanges} 4 | \alias{trackChanges} 5 | \title{Start tracking changes on a file} 6 | \usage{ 7 | trackChanges(path, wait = 1) 8 | } 9 | \arguments{ 10 | \item{path}{The path to the file to track.} 11 | 12 | \item{wait}{The number of seconds between each check for changes} 13 | } 14 | \description{ 15 | Start tracking all changes to a file and preview changes in the viewer. 16 | The changes will be added to the original file as markup when done. 17 | } 18 | \examples{ 19 | trackChanges("my_file.txt") 20 | 21 | } 22 | -------------------------------------------------------------------------------- /man/trackChangesViewer.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shinytrack.R 3 | \name{trackChangesViewer} 4 | \alias{trackChangesViewer} 5 | \title{Edit and track changes within markdown} 6 | \usage{ 7 | trackChangesViewer() 8 | } 9 | \description{ 10 | Edit and track changes within markdown 11 | } 12 | -------------------------------------------------------------------------------- /man/trackComment.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{trackComment} 4 | \alias{trackComment} 5 | \title{Insert a comment} 6 | \usage{ 7 | trackComment() 8 | } 9 | \description{ 10 | Call this function as an addin to add a comment in a tracked doc at cursor position. 11 | } 12 | -------------------------------------------------------------------------------- /man/trackDelete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{trackDelete} 4 | \alias{trackDelete} 5 | \title{Tracking a deletion} 6 | \usage{ 7 | trackDelete() 8 | } 9 | \description{ 10 | Call this function as an addin to delete highlighted text. 11 | } 12 | -------------------------------------------------------------------------------- /man/trackHighlight.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{trackHighlight} 4 | \alias{trackHighlight} 5 | \title{Tracking highlighting} 6 | \usage{ 7 | trackHighlight() 8 | } 9 | \description{ 10 | Call this function as an addin to add the markdown track changes output for highlighting 11 | } 12 | -------------------------------------------------------------------------------- /man/trackSubstitute.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{trackSubstitute} 4 | \alias{trackSubstitute} 5 | \title{Tracking a substitution} 6 | \usage{ 7 | trackSubstitute() 8 | } 9 | \description{ 10 | Call this function as an addin to add the markdown track changes output for substitution. 11 | } 12 | -------------------------------------------------------------------------------- /trackchanges.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci-archive/trackmd/2bec5b553081692ae6c16389b7eea8dbdf844f15/trackchanges.gif --------------------------------------------------------------------------------