├── .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('
')
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('')
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 | [](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 | 
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 | [](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 | 
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 | [](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
--------------------------------------------------------------------------------