├── _pkgdown.yml ├── .gitignore ├── man ├── figures │ ├── popup1.png │ ├── screenshot1.png │ ├── screenshot2.png │ ├── screenshot3.png │ └── screenshot4.png ├── confl_macro_jira.Rd ├── confl_macro_toc.Rd ├── confl_macro_excerpt.Rd ├── conf_macro_generator.Rd ├── confl_macro_info.Rd ├── conflr.Rd ├── confl_macro_expand.Rd ├── confl_contentbody.Rd ├── confl_user.Rd ├── confl_space.Rd ├── confl_attachment.Rd ├── confl_content.Rd └── confluence_document.Rd ├── tests ├── testthat │ ├── plot1.png │ ├── helpers.R │ ├── test-mock-progress.R │ ├── test-contentbody.R │ ├── test-invalid-credential.R │ ├── test-content.R │ ├── test-render.R │ ├── test-embed-images.R │ ├── test-toc.R │ ├── test-utils.R │ ├── test-tabset.R │ └── test-front-matter.R └── testthat.R ├── docs ├── reference │ ├── figures │ │ ├── popup1.png │ │ ├── screenshot1.png │ │ ├── screenshot2.png │ │ ├── screenshot3.png │ │ └── screenshot4.png │ ├── conflr.html │ ├── confl_macro_jira.html │ ├── confl_macro_excerpt.html │ ├── confl_macro_toc.html │ ├── conf_macro_generator.html │ ├── confl_user.html │ ├── confl_contentbody.html │ ├── confl_macro_expand.html │ ├── confl_space.html │ └── confl_create_post_from_Rmd.html ├── pkgdown.yml ├── link.svg ├── bootstrap-toc.css ├── docsearch.js ├── pkgdown.js ├── bootstrap-toc.js ├── authors.html ├── 404.html ├── CONTRIBUTING.html └── pkgdown.css ├── inst ├── rstudio │ └── addins.dcf └── extdata │ └── example.Rmd ├── .travis.yml ├── .Rbuildignore ├── cran-comments.md ├── conflr.Rproj ├── R ├── conflr-package.R ├── progress.R ├── user.R ├── contentbody.R ├── space.R ├── attachment.R ├── util.R ├── tabset.R ├── content.R ├── macros.R ├── addin-internals.R └── document.R ├── NAMESPACE ├── CONTRIBUTING.md ├── DESCRIPTION ├── NEWS.md └── CODE_OF_CONDUCT.md /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | inst/doc 6 | -------------------------------------------------------------------------------- /man/figures/popup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/man/figures/popup1.png -------------------------------------------------------------------------------- /tests/testthat/plot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/tests/testthat/plot1.png -------------------------------------------------------------------------------- /man/figures/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/man/figures/screenshot1.png -------------------------------------------------------------------------------- /man/figures/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/man/figures/screenshot2.png -------------------------------------------------------------------------------- /man/figures/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/man/figures/screenshot3.png -------------------------------------------------------------------------------- /man/figures/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/man/figures/screenshot4.png -------------------------------------------------------------------------------- /docs/reference/figures/popup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/docs/reference/figures/popup1.png -------------------------------------------------------------------------------- /docs/reference/figures/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/docs/reference/figures/screenshot1.png -------------------------------------------------------------------------------- /docs/reference/figures/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/docs/reference/figures/screenshot2.png -------------------------------------------------------------------------------- /docs/reference/figures/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/docs/reference/figures/screenshot3.png -------------------------------------------------------------------------------- /docs/reference/figures/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/conflr/HEAD/docs/reference/figures/screenshot4.png -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.9.2.1 2 | pkgdown: 1.5.1 3 | pkgdown_sha: ~ 4 | articles: [] 5 | last_built: 2020-06-06T06:26Z 6 | 7 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Post to Confluence 2 | Description: Knit an R Markdown file and post the result Markdown file to Confluence 3 | Binding: confl_create_post_from_Rmd_addin 4 | Interactive: true 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | sudo: false 5 | latex: false 6 | cache: packages 7 | 8 | matrix: 9 | include: 10 | - r: devel 11 | - r: release 12 | - r: oldrel 13 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^docs$ 2 | ^_pkgdown\.yml$ 3 | ^.*\.Rproj$ 4 | ^\.Rproj\.user$ 5 | ^LICENSE\.md$ 6 | ^README\.Rmd$ 7 | ^README-.*\.png$ 8 | ^CODE_OF_CONDUCT\.md$ 9 | ^CONTRIBUTING\.md$ 10 | ^\.travis\.yml$ 11 | ^\.drone\.yml$ 12 | ^cran-comments\.md$ 13 | ^CRAN-RELEASE$ 14 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * local macOS: release 3 | * Travis Ubuntu: oldrel, release, devel 4 | * win-builder: devel 5 | * r-hub: devel 6 | 7 | ## R CMD check results 8 | 9 | 0 errors | 0 warnings | 0 note 10 | 11 | * This is a maintainance release to fix the errors on CRAN checks: 12 | * Add pandoc to SystemRequirements on DESCRIPTION. 13 | * Skip tests when pandoc is not available. 14 | -------------------------------------------------------------------------------- /conflr.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /man/confl_macro_jira.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{confl_macro_jira} 4 | \alias{confl_macro_jira} 5 | \title{Generate Confluence macro referencing a Jira ticket} 6 | \usage{ 7 | confl_macro_jira(key) 8 | } 9 | \arguments{ 10 | \item{key}{Jira ticket id, eg CONFLR-XXXX} 11 | } 12 | \value{ 13 | HTML as string 14 | } 15 | \description{ 16 | Generate Confluence macro referencing a Jira ticket 17 | } 18 | \examples{ 19 | confl_macro_jira('CONFLR-42') 20 | } 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | library(testthat) 12 | library(conflr) 13 | 14 | test_check("conflr") 15 | -------------------------------------------------------------------------------- /inst/extdata/example.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Conflr Example" 3 | output: 4 | conflr::confluence_document: 5 | code_folding: hide 6 | --- 7 | 8 | ```{r setup, include=FALSE} 9 | knitr::opts_chunk$set(echo = TRUE) 10 | ``` 11 | 12 | ## Simple example 13 | 14 | You can embed an R code chunk like this: 15 | 16 | ```{r cars} 17 | summary(cars) 18 | ``` 19 | 20 | ## Including Plots 21 | 22 | You can also embed plots, for example: 23 | 24 | ```{r pressure} 25 | plot(pressure) 26 | ``` 27 | 28 | ## Tables 29 | 30 | ```{r table} 31 | knitr::kable(head(iris)) 32 | ``` 33 | 34 | ## Math 35 | 36 | $$ 37 | 2 + 2 = 5 38 | $$ 39 | -------------------------------------------------------------------------------- /man/confl_macro_toc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{confl_macro_toc} 4 | \alias{confl_macro_toc} 5 | \title{Generate Confluence macro for dynamic Table of Contents} 6 | \usage{ 7 | confl_macro_toc(levels) 8 | } 9 | \arguments{ 10 | \item{levels}{max number of levels to show} 11 | } 12 | \value{ 13 | HTML as string 14 | } 15 | \description{ 16 | Generate Confluence macro for dynamic Table of Contents 17 | } 18 | \examples{ 19 | confl_macro_toc(2) 20 | } 21 | \references{ 22 | \url{https://confluence.atlassian.com/doc/table-of-contents-macro-182682099.html} 23 | } 24 | -------------------------------------------------------------------------------- /man/confl_macro_excerpt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{confl_macro_excerpt} 4 | \alias{confl_macro_excerpt} 5 | \title{Generate Confluence macro for an excerpt block} 6 | \usage{ 7 | confl_macro_excerpt(body, hidden = TRUE) 8 | } 9 | \arguments{ 10 | \item{body}{HTML content of the excerpt} 11 | 12 | \item{hidden}{if the \code{body} should be shown on the actual page} 13 | } 14 | \value{ 15 | HTML as string 16 | } 17 | \description{ 18 | Generate Confluence macro for an excerpt block 19 | } 20 | \references{ 21 | \url{https://confluence.atlassian.com/doc/excerpt-macro-148062.html} 22 | } 23 | -------------------------------------------------------------------------------- /R/conflr-package.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | #' R Client for 'Confluence' API 13 | #' 14 | #' @name conflr 15 | #' @importFrom utils browseURL 16 | #' @importFrom glue glue glue_collapse 17 | #' @import rlang 18 | "_PACKAGE" 19 | -------------------------------------------------------------------------------- /tests/testthat/helpers.R: -------------------------------------------------------------------------------- 1 | should_not_be_called <- function(...) { 2 | stop(deparse(match.call()[[1]]), "() should not be called", call. = FALSE) 3 | } 4 | 5 | do_confl_create_post_from_Rmd <- function(mock, front_matter = NULL, ..., body = "test\n") { 6 | tmp <- tempfile(fileext = ".Rmd") 7 | on.exit(unlink(tmp), add = TRUE) 8 | 9 | writeLines(c("---", front_matter, "---\n", body), tmp, sep = "\n") 10 | with_mock( 11 | "conflr:::confl_upload" = mock, 12 | "conflr:::confl_get_current_user" = function(...) list(username = "user"), 13 | "conflr:::try_get_personal_space_key" = should_not_be_called, 14 | { 15 | confl_create_post_from_Rmd(tmp, interactive = FALSE, ...) 16 | } 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /man/conf_macro_generator.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{conf_macro_generator} 4 | \alias{conf_macro_generator} 5 | \title{General Confluence macro builder for internal use} 6 | \usage{ 7 | conf_macro_generator( 8 | type = c("inline", "block"), 9 | name, 10 | parameters = NULL, 11 | body = NULL 12 | ) 13 | } 14 | \arguments{ 15 | \item{type}{inline or block code style to be used for the HTML content} 16 | 17 | \item{name}{Confluence macro name} 18 | 19 | \item{parameters}{named list of optional macro parameters} 20 | 21 | \item{body}{optional \code{confl-ac-rich-text-body} content} 22 | } 23 | \value{ 24 | HTML 25 | } 26 | \description{ 27 | General Confluence macro builder for internal use 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/confl_macro_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{confl_macro_info} 4 | \alias{confl_macro_info} 5 | \alias{confl_macro_tip} 6 | \alias{confl_macro_note} 7 | \alias{confl_macro_warning} 8 | \title{Generate Confluence macro for a Info, Tip, Note, or Warning block} 9 | \usage{ 10 | confl_macro_info(body) 11 | 12 | confl_macro_tip(body) 13 | 14 | confl_macro_note(body) 15 | 16 | confl_macro_warning(body) 17 | } 18 | \arguments{ 19 | \item{body}{HTML content of the block} 20 | } 21 | \value{ 22 | HTML as string 23 | } 24 | \description{ 25 | Generate Confluence macro for a Info, Tip, Note, or Warning block 26 | } 27 | \references{ 28 | \url{https://confluence.atlassian.com/doc/info-tip-note-and-warning-macros-51872369.html} 29 | } 30 | -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /tests/testthat/test-mock-progress.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that("ConsoleProgress mocks minimum features of shiny::Progress", { 12 | expect_silent(progress <- ConsoleProgress$new(min = 0, max = 2)) 13 | expect_silent(progress$set(value = 1)) 14 | expect_silent(progress$set(detail = 1)) 15 | expect_message(progress$set(message = "foo"), "foo") 16 | expect_silent(progress$close()) 17 | }) 18 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(confl_contentbody_convert) 4 | export(confl_create_post_from_Rmd) 5 | export(confl_delete_page) 6 | export(confl_get_current_user) 7 | export(confl_get_page) 8 | export(confl_get_space) 9 | export(confl_get_user) 10 | export(confl_list_attachments) 11 | export(confl_list_pages) 12 | export(confl_list_spaces) 13 | export(confl_macro_excerpt) 14 | export(confl_macro_expand) 15 | export(confl_macro_info) 16 | export(confl_macro_jira) 17 | export(confl_macro_note) 18 | export(confl_macro_tip) 19 | export(confl_macro_toc) 20 | export(confl_macro_warning) 21 | export(confl_post_attachment) 22 | export(confl_post_page) 23 | export(confl_update_attachment_data) 24 | export(confl_update_attachment_metadata) 25 | export(confl_update_page) 26 | export(confluence_document) 27 | import(rlang) 28 | importFrom(glue,glue) 29 | importFrom(glue,glue_collapse) 30 | importFrom(utils,browseURL) 31 | -------------------------------------------------------------------------------- /man/conflr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/conflr-package.R 3 | \docType{package} 4 | \name{conflr} 5 | \alias{conflr} 6 | \alias{conflr-package} 7 | \title{R Client for 'Confluence' API} 8 | \description{ 9 | Provides utilities for working with various 'Confluence' API 10 | , including a 11 | functionality to convert an R Markdown document to 'Confluence' format and 12 | upload it to 'Confluence' automatically. 13 | } 14 | \seealso{ 15 | Useful links: 16 | \itemize{ 17 | \item \url{https://line.github.io/conflr/} 18 | \item \url{https://github.com/line/conflr} 19 | \item Report bugs at \url{https://github.com/line/conflr/issues} 20 | } 21 | 22 | } 23 | \author{ 24 | \strong{Maintainer}: Hiroaki Yutani \email{hiroaki.yutani@linecorp.com} 25 | 26 | Other contributors: 27 | \itemize{ 28 | \item LINE Corporation [copyright holder] 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /man/confl_macro_expand.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/macros.R 3 | \name{confl_macro_expand} 4 | \alias{confl_macro_expand} 5 | \title{Generate Confluence macro for an expand block} 6 | \usage{ 7 | confl_macro_expand(title, body) 8 | } 9 | \arguments{ 10 | \item{title}{defines the text that appears next to the expand/collapse icon} 11 | 12 | \item{body}{this HTML content will be visible when someone clicks the macro title} 13 | } 14 | \value{ 15 | HTML as string 16 | } 17 | \description{ 18 | Generate Confluence macro for an expand block 19 | } 20 | \note{ 21 | \code{content} needs to be HTML, so look at \code{commonmark::markdown_html}, \code{pander::pander} and eg \code{xtable} for doing the conversion before passing to \code{confluence_expand} 22 | } 23 | \examples{ 24 | \dontrun{ 25 | confl_macro_expand( 26 | 'Example block', 27 | commonmark::markdown_html(pander::pander_return(list(a = list(b = 4), c = 2)))) 28 | } 29 | } 30 | \references{ 31 | \url{https://confluence.atlassian.com/doc/expand-macro-223222352.html} 32 | } 33 | -------------------------------------------------------------------------------- /man/confl_contentbody.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/contentbody.R 3 | \name{confl_contentbody} 4 | \alias{confl_contentbody} 5 | \alias{confl_contentbody_convert} 6 | \title{Converts between content body representations} 7 | \usage{ 8 | confl_contentbody_convert( 9 | x, 10 | from = c("wiki", "storage", "editor", "view", "export_view", "styled_view"), 11 | to = c("storage", "editor", "view", "export_view", "styled_view") 12 | ) 13 | } 14 | \arguments{ 15 | \item{x}{The content body to convert.} 16 | 17 | \item{from}{The format to convert from.} 18 | 19 | \item{to}{The format to convert to.} 20 | } 21 | \value{ 22 | The API response as a list. 23 | } 24 | \description{ 25 | Converts between content body representations 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # Convert to a Math macro 30 | confl_contentbody_convert("\\\\[1+1=2\\\\]") 31 | 32 | # Convert to an Expand macro 33 | confl_contentbody_convert("\{expand\}detail is here \{expand\}") 34 | } 35 | 36 | } 37 | \seealso{ 38 | \url{https://docs.atlassian.com/ConfluenceServer/rest/latest/} 39 | } 40 | -------------------------------------------------------------------------------- /man/confl_user.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/user.R 3 | \name{confl_user} 4 | \alias{confl_user} 5 | \alias{confl_get_user} 6 | \alias{confl_get_current_user} 7 | \title{Non-admin User Operations} 8 | \usage{ 9 | confl_get_user(key = NULL, username = NULL, expand = NULL) 10 | 11 | confl_get_current_user(expand = NULL) 12 | } 13 | \arguments{ 14 | \item{key}{Userkey of the user to request from this resource.} 15 | 16 | \item{username}{Username of the user to request from this resource.} 17 | 18 | \item{expand}{A comma separated list of properties to expand. To refer the nested 19 | contents, use periods. (e.g. \verb{body.storage,history}).} 20 | } 21 | \value{ 22 | The API response as a list. 23 | } 24 | \description{ 25 | Non-admin User Operations 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # Get the information of the current user 30 | my_user <- confl_get_current_user() 31 | 32 | # Show display name 33 | my_user$displayName 34 | 35 | # Get the information of a user whose name is "user1" 36 | other_user <- confl_get_user(username = "user1") 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /tests/testthat/test-contentbody.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that("confl_contentbody_convert() works", { 12 | skip_on_cran() 13 | 14 | res <- structure(list(status_code = 200), class = "response") 15 | m <- mockery::mock(res) 16 | 17 | with_mock( 18 | "conflr::confl_verb" = m, 19 | "httr::content" = function(res) NULL, 20 | { 21 | confl_contentbody_convert("{cheese}", "wiki", "storage") 22 | } 23 | ) 24 | 25 | args <- mockery::mock_args(m)[[1]] 26 | expect_equal(args[[2]], "/contentbody/convert/storage") 27 | expect_equal( 28 | args$body, 29 | list( 30 | value = "{cheese}", 31 | representation = "wiki" 32 | ) 33 | ) 34 | }) 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to conflr project 2 | 3 | First of all, thank you so much for taking your time to contribute! conflr is not very different from any other open 4 | source projects you are aware of. It will be amazing if you could help us by doing any of the following: 5 | 6 | - File an issue in [the issue tracker](https://github.com/line/conflr/issues) to report bugs and propose new features and improvements. 7 | - Ask a question by creating a new issue in [the issue tracker](https://github.com/line/conflr/issues). 8 | - Browse [the list of previously answered questions](https://github.com/line/conflr/issues?q=label%3Aquestion). 9 | - Contribute your work by sending [a pull request](https://github.com/line/conflr/pulls). 10 | 11 | ## Contributor license agreement 12 | 13 | When you are sending a pull request and it's a non-trivial change beyond fixing typos, please sign [the ICLA (individual contributor license agreement)](https://cla-assistant.io/line/conflr). Please [contact us](dl_oss_dev@linecorp.com) if you need the CCLA (corporate contributor license agreement). 14 | 15 | ## Code of conduct 16 | 17 | We expect contributors to follow [our code of conduct](https://github.com/line/conflr/blob/master/CODE_OF_CONDUCT.md). 18 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: conflr 2 | Type: Package 3 | Title: Client for 'Confluence' API 4 | Version: 0.1.1.9000 5 | Authors@R: c( 6 | person("Hiroaki", "Yutani", email = "hiroaki.yutani@linecorp.com", role = c("aut", "cre")), 7 | person(family = "LINE Corporation", role = c("cph")) 8 | ) 9 | Description: Provides utilities for working with various 'Confluence' API 10 | , including a 11 | functionality to convert an R Markdown document to 'Confluence' format and 12 | upload it to 'Confluence' automatically. 13 | URL: https://line.github.io/conflr/, https://github.com/line/conflr 14 | SystemRequirements: pandoc (>= 1.12.3) - https://pandoc.org 15 | BugReports: https://github.com/line/conflr/issues 16 | License: GPL-3 17 | Encoding: UTF-8 18 | LazyData: true 19 | Imports: 20 | askpass, 21 | commonmark, 22 | curl, 23 | glue, 24 | httr, 25 | knitr, 26 | miniUI, 27 | purrr, 28 | rmarkdown, 29 | rstudioapi, 30 | shiny, 31 | stringi, 32 | xml2, 33 | R6, 34 | rlang (>= 0.3.0) 35 | Suggests: 36 | mockery, 37 | testthat (>= 2.1.0), 38 | withr 39 | RoxygenNote: 7.1.1 40 | Roxygen: list(markdown = TRUE) 41 | Config/runiverse/noindex: true 42 | -------------------------------------------------------------------------------- /R/progress.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | # A mock of shiny::Progress() for console usages 12 | ConsoleProgress <- R6::R6Class( 13 | "ConsoleProgress", 14 | public = list( 15 | initialize = function(...) { 16 | # All arguments are ignored 17 | }, 18 | set = function(value = NULL, message = NULL, detail = NULL) { 19 | # value is ignored 20 | 21 | # If message is set, show the message 22 | if (!is.null(message)) { 23 | message(message) 24 | } 25 | }, 26 | close = function() { 27 | # Do nothing 28 | } 29 | ), 30 | 31 | private = list() 32 | ) 33 | 34 | new_progress <- function(session = NULL, min = 0, max = 1) { 35 | if (!is.null(session)) { 36 | shiny::Progress$new(session, min = min, max = max) 37 | } else { 38 | ConsoleProgress$new() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/testthat/test-invalid-credential.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that("confluence_document() stops early", { 12 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 13 | 14 | tmp <- tempfile(fileext = ".Rmd") 15 | on.exit(unlink(tmp), add = TRUE) 16 | writeLines(c("---", "title: title1", "---\n", "test"), tmp, sep = "\n") 17 | 18 | knit_mock <- mockery::mock(NULL) 19 | 20 | withr::local_envvar(list( 21 | CONFLUENCE_URL = "base_url", 22 | CONFLUENCE_USERNAME = "username", 23 | CONFLUENCE_PASSWORD = "password" 24 | )) 25 | 26 | expect_error( 27 | with_mock( 28 | "httr::VERB" = function(...) abort("Unauthorized (HTTP 401)"), 29 | "knitr::knit" = knit_mock, 30 | { 31 | confl_create_post_from_Rmd(tmp, interactive = FALSE) 32 | } 33 | ), 34 | "Invalid credentials!", 35 | fixed = TRUE 36 | ) 37 | 38 | # knit should not be called 39 | mockery::expect_called(knit_mock, 0) 40 | }) 41 | -------------------------------------------------------------------------------- /man/confl_space.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/space.R 3 | \name{confl_space} 4 | \alias{confl_space} 5 | \alias{confl_list_spaces} 6 | \alias{confl_get_space} 7 | \title{REST Wrapper for the SpaceService} 8 | \usage{ 9 | confl_list_spaces( 10 | spaceKey = NULL, 11 | type = c("global", "personal"), 12 | status = c("current", "archived"), 13 | label = NULL, 14 | favourite = NULL, 15 | expand = NULL, 16 | start = NULL, 17 | limit = 25 18 | ) 19 | 20 | confl_get_space(spaceKey, expand = NULL) 21 | } 22 | \arguments{ 23 | \item{spaceKey}{The space key to find content under.} 24 | 25 | \item{type}{Filter the list of spaces returned by type (\code{global}, \code{personal}).} 26 | 27 | \item{status}{Filter the list of spaces returned by status (\code{current}, \code{archived}).} 28 | 29 | \item{label}{Filter the list of spaces returned by label.} 30 | 31 | \item{favourite}{Filter the list of spaces returned by favourites.} 32 | 33 | \item{expand}{A comma separated list of properties to expand. To refer the nested 34 | contents, use periods. (e.g. \verb{body.storage,history}).} 35 | 36 | \item{start}{The start point of the collection to return.} 37 | 38 | \item{limit}{The limit of the number of items to return, this may be restricted by fixed system limits.} 39 | } 40 | \value{ 41 | The API response as a list. 42 | } 43 | \description{ 44 | REST Wrapper for the SpaceService 45 | } 46 | \examples{ 47 | \dontrun{ 48 | # Get the information of a space named "space1" 49 | confl_get_space("space1") 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /R/user.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | #' Non-admin User Operations 13 | #' 14 | #' @name confl_user 15 | #' @param key 16 | #' Userkey of the user to request from this resource. 17 | #' @param username 18 | #' Username of the user to request from this resource. 19 | #' @inheritParams confl_content 20 | #' 21 | #' @return 22 | #' The API response as a list. 23 | #' 24 | #' @examples 25 | #' \dontrun{ 26 | #' # Get the information of the current user 27 | #' my_user <- confl_get_current_user() 28 | #' 29 | #' # Show display name 30 | #' my_user$displayName 31 | #' 32 | #' # Get the information of a user whose name is "user1" 33 | #' other_user <- confl_get_user(username = "user1") 34 | #' } 35 | #' 36 | #' @export 37 | confl_get_user <- function(key = NULL, username = NULL, expand = NULL) { 38 | query <- list(key = key, username = username, expand = expand) 39 | res <- confl_verb("GET", "/user", query = purrr::compact(query)) 40 | httr::content(res) 41 | } 42 | 43 | #' @rdname confl_user 44 | #' @export 45 | confl_get_current_user <- function(expand = NULL) { 46 | query <- list(expand = expand) 47 | res <- confl_verb("GET", "/user/current", query = purrr::compact(query)) 48 | httr::content(res) 49 | } 50 | -------------------------------------------------------------------------------- /R/contentbody.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | #' Converts between content body representations 13 | #' 14 | #' @name confl_contentbody 15 | #' @param x 16 | #' The content body to convert. 17 | #' @param from 18 | #' The format to convert from. 19 | #' @param to 20 | #' The format to convert to. 21 | #' 22 | #' @return 23 | #' The API response as a list. 24 | #' 25 | #' @examples 26 | #' \dontrun{ 27 | #' # Convert to a Math macro 28 | #' confl_contentbody_convert("\\[1+1=2\\]") 29 | #' 30 | #' # Convert to an Expand macro 31 | #' confl_contentbody_convert("\{expand\}detail is here \{expand\}") 32 | #' } 33 | #' 34 | #' @seealso 35 | #' 36 | #' @export 37 | confl_contentbody_convert <- function(x, 38 | from = c("wiki", "storage", "editor", "view", "export_view", "styled_view"), 39 | to = c("storage", "editor", "view", "export_view", "styled_view")) { 40 | if (length(x) != 1) { 41 | abort("`x` must be length 1") 42 | } 43 | 44 | from <- arg_match(from) 45 | to <- arg_match(to) 46 | 47 | res <- confl_verb("POST", glue("/contentbody/convert/{to}"), 48 | body = list(value = x, representation = from), encode = "json" 49 | ) 50 | httr::content(res)$value 51 | } 52 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /tests/testthat/test-content.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that("confl_post_page() works", { 12 | skip_on_cran() 13 | 14 | res <- structure(list(status_code = 200), class = "response") 15 | m <- mockery::mock(res) 16 | 17 | with_mock( 18 | "conflr::confl_verb" = m, 19 | "httr::content" = function(res) NULL, 20 | { 21 | confl_post_page("page", "space1", "title", "

foo

") 22 | } 23 | ) 24 | 25 | args <- mockery::mock_args(m)[[1]] 26 | expect_equal(args$body, list( 27 | type = "page", 28 | title = "title", 29 | space = list( 30 | key = "space1" 31 | ), 32 | body = list( 33 | storage = list( 34 | value = "

foo

", 35 | representation = "storage" 36 | ) 37 | ) 38 | )) 39 | }) 40 | 41 | test_that("confl_update_page() works", { 42 | skip_on_cran() 43 | 44 | res <- structure(list(status_code = 200), class = "response") 45 | m <- mockery::mock(res) 46 | info <- list(version = list(number = 11L), type = "page") 47 | m2 <- mockery::mock(info) 48 | 49 | with_mock( 50 | "conflr::confl_verb" = m, 51 | "conflr::confl_get_page" = m2, 52 | "httr::content" = function(res) NULL, 53 | { 54 | confl_update_page("1234", "title", "

foo

") 55 | } 56 | ) 57 | args <- mockery::mock_args(m)[[1]] 58 | expect_equal(args$body, list( 59 | type = "page", 60 | title = "title", 61 | body = list( 62 | storage = list( 63 | value = "

foo

", 64 | representation = "storage" 65 | ) 66 | ), 67 | version = list( 68 | number = 12L, 69 | minorEdit = FALSE 70 | ) 71 | )) 72 | }) 73 | -------------------------------------------------------------------------------- /man/confl_attachment.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/attachment.R 3 | \name{confl_attachment} 4 | \alias{confl_attachment} 5 | \alias{confl_list_attachments} 6 | \alias{confl_post_attachment} 7 | \alias{confl_update_attachment_metadata} 8 | \alias{confl_update_attachment_data} 9 | \title{CRUD Operations for Attachments on Content} 10 | \usage{ 11 | confl_list_attachments( 12 | id, 13 | filename = NULL, 14 | mediaType = NULL, 15 | start = 0, 16 | limit = 50, 17 | expand = NULL 18 | ) 19 | 20 | confl_post_attachment(id, path, minor_edit = FALSE) 21 | 22 | confl_update_attachment_metadata(id, attachmentId, ...) 23 | 24 | confl_update_attachment_data(id, attachmentId, path, ..., minor_edit = FALSE) 25 | } 26 | \arguments{ 27 | \item{id}{The ID of a page that attachments belong to.} 28 | 29 | \item{filename}{Filter parameter to return only the Attachment with the matching file name. Optional.} 30 | 31 | \item{mediaType}{Filter parameter to return only Attachments with a matching Media-Type. Optional.} 32 | 33 | \item{start}{The start point of the collection to return.} 34 | 35 | \item{limit}{The limit of the number of items to return, this may be restricted by fixed system limits.} 36 | 37 | \item{expand}{A comma separated list of properties to expand. To refer the nested 38 | contents, use periods. (e.g. \verb{body.storage,history}).} 39 | 40 | \item{path}{Path to a file to upload.} 41 | 42 | \item{minor_edit}{If \code{TRUE}, will mark the \code{update} as a minor edit not notifying watchers.} 43 | 44 | \item{attachmentId}{The ID of an attachment.} 45 | 46 | \item{...}{Other arguments passed to 'query'.} 47 | } 48 | \value{ 49 | The API response as a list. 50 | } 51 | \description{ 52 | CRUD Operations for Attachments on Content 53 | } 54 | \examples{ 55 | \dontrun{ 56 | # Create a dummy text file 57 | tmp_txt <- tempfile(fileext = ".txt") 58 | cat("foo", file = tmp_txt) 59 | 60 | # Upload the file to a page whose ID is "123" 61 | confl_post_attachment("123", tmp_txt) 62 | 63 | # Confirm the file is attatched to the page 64 | result <- confl_list_attachments("123", filename = basename(tmp_txt)) 65 | length(result$results) # should be 1 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /tests/testthat/test-render.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that("rmarkdown::render() works when no space_key", { 12 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 13 | 14 | tmp <- tempfile(fileext = ".Rmd") 15 | on.exit(unlink(tmp)) 16 | 17 | writeLines( 18 | "--- 19 | title: title1 20 | output: 21 | conflr::confluence_document: 22 | toc: false 23 | space_key: space1 24 | --- 25 | 26 | # h1 27 | ## h2 28 | ", tmp 29 | ) 30 | 31 | with_mock( 32 | "conflr::confl_list_attachments" = function(...) list(results = list()), 33 | "conflr::confl_update_page" = function(...) abort("", class = "success"), 34 | "conflr::confl_post_page" = function(...) list(id = 1), 35 | "conflr::confl_get_current_user" = function(...) list(username = "user"), 36 | "conflr:::try_get_existing_page_id" = function(...) NULL, 37 | "conflr:::try_get_personal_space_key" = should_not_be_called, 38 | { 39 | expect_error(rmarkdown::render(tmp), class = "success") 40 | } 41 | ) 42 | }) 43 | 44 | 45 | test_that("rmarkdown::render() aborts when no space_key", { 46 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 47 | 48 | tmp <- tempfile(fileext = ".Rmd") 49 | on.exit(unlink(tmp)) 50 | 51 | writeLines( 52 | "--- 53 | title: title1 54 | output: 55 | conflr::confluence_document: 56 | toc: false 57 | --- 58 | 59 | # h1 60 | ## h2 61 | ", tmp 62 | ) 63 | 64 | with_mock( 65 | "conflr::confl_get_current_user" = function(...) list(username = "user"), 66 | "conflr:::try_get_existing_page_id" = function(...) NULL, 67 | "conflr:::try_get_personal_space_key" = should_not_be_called, 68 | { 69 | expect_error(rmarkdown::render(tmp), "Please provide `space_key`!") 70 | } 71 | ) 72 | }) 73 | -------------------------------------------------------------------------------- /R/space.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | #' REST Wrapper for the SpaceService 13 | #' 14 | #' @name confl_space 15 | #' @param spaceKey 16 | #' The space key to find content under. 17 | #' @param type 18 | #' Filter the list of spaces returned by type (`global`, `personal`). 19 | #' @param status 20 | #' Filter the list of spaces returned by status (`current`, `archived`). 21 | #' @param label 22 | #' Filter the list of spaces returned by label. 23 | #' @param favourite 24 | #' Filter the list of spaces returned by favourites. 25 | #' @inheritParams confl_content 26 | #' 27 | #' @return 28 | #' The API response as a list. 29 | #' 30 | #' @examples 31 | #' \dontrun{ 32 | #' # Get the information of a space named "space1" 33 | #' confl_get_space("space1") 34 | #' } 35 | #' 36 | #' @export 37 | confl_list_spaces <- function(spaceKey = NULL, 38 | type = c("global", "personal"), 39 | status = c("current", "archived"), 40 | label = NULL, 41 | favourite = NULL, 42 | expand = NULL, 43 | start = NULL, 44 | limit = 25) { 45 | type <- arg_match(type) 46 | status <- arg_match(status) 47 | query <- list( 48 | type = type, status = status, label = label, favourite = favourite, 49 | expand = expand, start = start, limit = limit 50 | ) 51 | res <- confl_verb("GET", "/space", query = purrr::compact(query)) 52 | httr::content(res) 53 | } 54 | 55 | #' @name confl_space 56 | #' @export 57 | confl_get_space <- function(spaceKey, expand = NULL) { 58 | query <- list(expand = expand) 59 | res <- confl_verb("GET", glue("/space/{spaceKey}"), query = purrr::compact(query)) 60 | httr::content(res) 61 | } 62 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /man/confl_content.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/content.R 3 | \name{confl_content} 4 | \alias{confl_content} 5 | \alias{confl_list_pages} 6 | \alias{confl_get_page} 7 | \alias{confl_post_page} 8 | \alias{confl_update_page} 9 | \alias{confl_delete_page} 10 | \title{REST Wrapper for the ContentService} 11 | \usage{ 12 | confl_list_pages( 13 | type = c("page", "blogpost", "comment", "attachment"), 14 | limit = 10, 15 | start = 0, 16 | spaceKey = NULL, 17 | title = NULL, 18 | expand = NULL 19 | ) 20 | 21 | confl_get_page(id, expand = "body.storage") 22 | 23 | confl_post_page( 24 | type = c("page", "blogpost"), 25 | spaceKey, 26 | title, 27 | body, 28 | ancestors = NULL 29 | ) 30 | 31 | confl_update_page(id, title, body, minor_edit = FALSE) 32 | 33 | confl_delete_page(id) 34 | } 35 | \arguments{ 36 | \item{type}{The content type to return. Default value: \code{page}. Valid values: \code{page}, \code{blogpost}.} 37 | 38 | \item{limit}{The limit of the number of items to return, this may be restricted by fixed system limits.} 39 | 40 | \item{start}{The start point of the collection to return.} 41 | 42 | \item{spaceKey}{The space key to find content under.} 43 | 44 | \item{title}{The title of the page to find. Required for \code{page} type.} 45 | 46 | \item{expand}{A comma separated list of properties to expand. To refer the nested 47 | contents, use periods. (e.g. \verb{body.storage,history}).} 48 | 49 | \item{id}{ID of the content.} 50 | 51 | \item{body}{The HTML source of the page.} 52 | 53 | \item{ancestors}{The page ID of the parent pages.} 54 | 55 | \item{minor_edit}{If \code{TRUE}, will mark the \code{update} as a minor edit not notifying watchers.} 56 | } 57 | \value{ 58 | The API response as a list. 59 | } 60 | \description{ 61 | REST Wrapper for the ContentService 62 | } 63 | \examples{ 64 | \dontrun{ 65 | # Create a page titled "title1" on a space named "space1" 66 | result <- confl_post_page( 67 | type = "page", 68 | spaceKey = "space1", 69 | title = "title1", 70 | body = "

example

This is example

" 71 | ) 72 | 73 | # Jump to the result page 74 | browseURL(paste0(result$`_links`$base, result$`_links`$webui)) 75 | 76 | # List pages under space "space1" up to 10 pages 77 | confl_list_pages(spaceKey = "space1") 78 | } 79 | 80 | } 81 | \seealso{ 82 | \url{https://docs.atlassian.com/ConfluenceServer/rest/latest/} 83 | } 84 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # conflr (development version) 2 | 3 | * New `minor_edit` parameter (default to `FALSE`) in the YAML front 4 | matter to skip sending notifications on page or attachment updates 5 | to watchers (#20, #107 @daroczig, #108). 6 | 7 | * New helper functions with the `confl_macro` prefix to generate HTML 8 | tags for the following Confluence macros: Table of Contents, Jira 9 | ticket references, Expand and Excerpt blocks (#111 @daroczig). 10 | 11 | * Add an experimental support for tabset (#113). 12 | 13 | # conflr 0.1.1 14 | 15 | * A maintainance relase to fix errors on CRAN check. 16 | 17 | # conflr 0.1.0 18 | 19 | * First CRAN release. 20 | 21 | ## Major changes 22 | 23 | * conflr now works outside RStudio (e.g. Emacs/Vim) (#10). 24 | 25 | * conflr now fits batch/console uses; you can either 26 | 1. run `confl_create_post_from_Rmd()` with `interactive = FALSE` so that it 27 | doesn't show Shiny popups (#32, @ndiquattro). 28 | 2. set `output: conflr::confluence_document` in the YAML front matter of the 29 | R Markdown file, and run `rmarkdown::render()` (#44, @kazutan / #80). 30 | 31 | ``` md 32 | --- 33 | title: "I love Confluence" 34 | output: 35 | conflr::confluence_document: 36 | space_key: "space1" 37 | --- 38 | ``` 39 | 40 | * conflr provides several new options: 41 | * "Use original image sizes" option controls whether to resize the image 42 | (default) or not (#21). 43 | * "TOC" option adds a table of contents and "TOC depth" option changes the 44 | max level of headers to include (#67). 45 | * "Fold code blocks" option controls whether to fold codes (default) or not 46 | (#81). 47 | 48 | ### Minor changes 49 | 50 | * `confl_create_post_from_Rmd()` gets `params` argument for parameterized R 51 | Markdown (#37, @ellisvalentiner). 52 | 53 | * External images are now converted properly (#39). 54 | 55 | * Add an option `conflr_addin_clear_password_after_success` not to cache the 56 | password as envvars. (#41 and #48, @Curycu). 57 | 58 | * A new function `confl_contentbody_convert()` converts the Confluence-related 59 | formats by using the Confluence REST API (#58). 60 | 61 | * `confl_post_page()` and `confl_update_page()` no longer translate the 62 | Confluence macros automatically. Accordingly, they lose `image_size_default` 63 | and `supported_syntax_highlighting` arguments (#76). 64 | 65 | * `confl_create_post_from_Rmd()` now handles documents containing Confluence 66 | macro tags (i.e. `` or ``) properly (#76). 67 | 68 | # conflr 0.0.5 69 | 70 | * Initial release on GitHub 71 | -------------------------------------------------------------------------------- /tests/testthat/test-embed-images.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | base64_img <- knitr::image_uri("plot1.png") 12 | 13 | test_that("embed_images() works for current dir", { 14 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 15 | 16 | md_text <- "#\u30c6\u30b9\u30c8\n![](./plot1.png)\n" 17 | html_text <- commonmark::markdown_html(md_text) 18 | result <- embed_images(html_text, "./plot1.png", "./plot1.png") 19 | 20 | expect_equal(result, stringi::stri_replace_all_fixed(html_text, "./plot1.png", base64_img)) 21 | }) 22 | 23 | test_that("embed_images() works for multiple images", { 24 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 25 | 26 | md_text <- "#\u30c6\u30b9\u30c8\n![](./plot1.png)\n![](https://example.com/test.png)\n![](./plot1.png)\n" 27 | html_text <- commonmark::markdown_html(md_text) 28 | result <- embed_images(html_text, "./plot1.png", "./plot1.png") 29 | 30 | expect_equal(result, stringi::stri_replace_all_fixed(html_text, "./plot1.png", base64_img)) 31 | }) 32 | 33 | test_that("embed_images() works for non-ASCII dir", { 34 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 35 | 36 | # LATIN SMALL LETTER O WITH DIAERESIS 37 | tmp_dir <- file.path(tempdir(), "\u00f6") 38 | on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE) 39 | dir.create(tmp_dir) 40 | file.copy("plot1.png", file.path(tmp_dir, "plot1.png")) 41 | 42 | # NOTE: specifying "title" is needed, otherwise test may fail with the latest version of Pandoc 43 | md_text <- "# test\n![](%C3%B6/plot1.png \"title\")\n" 44 | html_text <- commonmark::markdown_html(md_text) 45 | expected <- stringi::stri_replace_all_fixed(html_text, "%C3%B6/plot1.png", base64_img) 46 | 47 | # unit test 48 | result1 <- embed_images(html_text, "%C3%B6/plot1.png", file.path(tmp_dir, "plot1.png")) 49 | expect_equal(result1, expected) 50 | 51 | # integrated test 52 | Rmd_with_some_settings <- 53 | 'title: "title1" 54 | output: 55 | conflr::confluence_document: 56 | space_key: "space1"' 57 | confl_upload_mock <- mockery::mock(NULL) 58 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_some_settings, body = md_text) 59 | result2 <- mockery::mock_args(confl_upload_mock)[[1]] 60 | 61 | # expect_equal(result2$html_text, html_text) 62 | expect_equal(result2$imgs, "%C3%B6/plot1.png") 63 | expect_equal(result2$imgs_realpath, "\u00f6/plot1.png") 64 | }) 65 | -------------------------------------------------------------------------------- /tests/testthat/test-toc.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that("TOC is added when set via argument", { 12 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 13 | 14 | tmp <- tempfile(fileext = ".Rmd") 15 | on.exit(unlink(tmp)) 16 | 17 | writeLines( 18 | "--- 19 | title: title1 20 | output: 21 | conflr::confluence_document: 22 | toc: false 23 | space_key: space1 24 | --- 25 | 26 | # h1 27 | ## h2 28 | ", tmp 29 | ) 30 | 31 | mock <- mockery::mock(NULL) 32 | with_mock( 33 | "conflr::confl_list_attachments" = function(...) list(results = list()), 34 | "conflr::confl_update_page" = mock, 35 | "conflr::confl_get_current_user" = function(...) list(username = "user"), 36 | "conflr:::try_get_existing_page_id" = function(...) 1, 37 | "conflr:::try_get_personal_space_key" = should_not_be_called, 38 | { 39 | confl_create_post_from_Rmd(tmp, interactive = FALSE, update = TRUE, toc = TRUE) 40 | } 41 | ) 42 | 43 | expect_equal( 44 | mockery::mock_args(mock)[[1]]$body, 45 | '

46 | 47 | 7 48 | 49 |

50 |

h1

51 |

h2

' 52 | ) 53 | }) 54 | 55 | test_that("TOC is added when set via front-matter", { 56 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 57 | 58 | tmp <- tempfile(fileext = ".Rmd") 59 | on.exit(unlink(tmp)) 60 | 61 | writeLines( 62 | "--- 63 | title: title1 64 | output: 65 | conflr::confluence_document: 66 | space_key: space1 67 | toc: true 68 | toc_depth: 3 69 | --- 70 | 71 | # h1 72 | ## h2 73 | ", tmp 74 | ) 75 | 76 | mock <- mockery::mock(NULL, cycle = TRUE) 77 | with_mock( 78 | "conflr::confl_list_attachments" = function(...) list(results = list()), 79 | "conflr::confl_update_page" = mock, 80 | "conflr::confl_get_current_user" = function(...) list(username = "user"), 81 | "conflr:::try_get_existing_page_id" = function(...) 1, 82 | "conflr:::try_get_personal_space_key" = should_not_be_called, 83 | { 84 | confl_create_post_from_Rmd(tmp, interactive = FALSE, update = TRUE) 85 | confl_create_post_from_Rmd(tmp, interactive = FALSE, update = TRUE, toc = FALSE) 86 | } 87 | ) 88 | 89 | expect_equal( 90 | mockery::mock_args(mock)[[1]]$body, 91 | '

92 | 93 | 3 94 | 95 |

96 |

h1

97 |

h2

' 98 | ) 99 | 100 | expect_equal( 101 | mockery::mock_args(mock)[[2]]$body, 102 | "

h1

\n

h2

" 103 | ) 104 | }) 105 | -------------------------------------------------------------------------------- /R/attachment.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | #' CRUD Operations for Attachments on Content 13 | #' 14 | #' @name confl_attachment 15 | #' @param id 16 | #' The ID of a page that attachments belong to. 17 | #' @param filename 18 | #' Filter parameter to return only the Attachment with the matching file name. Optional. 19 | #' @param mediaType 20 | #' Filter parameter to return only Attachments with a matching Media-Type. Optional. 21 | #' @inheritParams confl_content 22 | #' 23 | #' @return 24 | #' The API response as a list. 25 | #' 26 | #' @examples 27 | #' \dontrun{ 28 | #' # Create a dummy text file 29 | #' tmp_txt <- tempfile(fileext = ".txt") 30 | #' cat("foo", file = tmp_txt) 31 | #' 32 | #' # Upload the file to a page whose ID is "123" 33 | #' confl_post_attachment("123", tmp_txt) 34 | #' 35 | #' # Confirm the file is attatched to the page 36 | #' result <- confl_list_attachments("123", filename = basename(tmp_txt)) 37 | #' length(result$results) # should be 1 38 | #' } 39 | #' 40 | #' @export 41 | confl_list_attachments <- function(id, 42 | filename = NULL, 43 | mediaType = NULL, 44 | start = 0, 45 | limit = 50, 46 | expand = NULL) { 47 | id <- as.character(id) 48 | query <- list(limit = limit, start = start, filename = filename, mediaType = mediaType, expand = expand) 49 | res <- confl_verb("GET", glue("/content/{id}/child/attachment"), 50 | query = purrr::compact(query) 51 | ) 52 | httr::content(res) 53 | } 54 | 55 | #' @rdname confl_attachment 56 | #' @param path Path to a file to upload. 57 | #' @export 58 | confl_post_attachment <- function(id, path, minor_edit = FALSE) { 59 | id <- as.character(id) 60 | res <- confl_verb("POST", glue("/content/{id}/child/attachment"), 61 | body = list(file = httr::upload_file(path), minorEdit = minor_edit), 62 | httr::add_headers(`X-Atlassian-Token` = "nocheck") 63 | ) 64 | httr::content(res) 65 | } 66 | 67 | #' @rdname confl_attachment 68 | #' @param attachmentId The ID of an attachment. 69 | #' @param ... Other arguments passed to 'query'. 70 | #' @export 71 | confl_update_attachment_metadata <- function(id, attachmentId, ...) { 72 | id <- as.character(id) 73 | res <- confl_verb("PUT", glue("/content/{id}/child/attachment/{attachmentId}"), 74 | query = list(...) 75 | ) 76 | httr::content(res) 77 | } 78 | 79 | #' @rdname confl_attachment 80 | #' @export 81 | confl_update_attachment_data <- function(id, attachmentId, path, ..., minor_edit = FALSE) { 82 | id <- as.character(id) 83 | res <- confl_verb("POST", glue("/content/{id}/child/attachment/{attachmentId}/data"), 84 | body = list(file = httr::upload_file(path), minorEdit = minor_edit), 85 | httr::add_headers(`X-Atlassian-Token` = "nocheck") 86 | ) 87 | httr::content(res) 88 | } 89 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [dl\_oss\_dev@linecorp.com](mailto:dl_oss_dev@linecorp.com). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /R/util.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | # Util 13 | `%||%` <- function(lhs, rhs) { 14 | if (is.null(lhs)) rhs else lhs 15 | } 16 | 17 | `%|""|%` <- function(lhs, rhs) { 18 | if (is.null(lhs) || identical(lhs, "")) rhs else lhs 19 | } 20 | 21 | ask_secret <- function(message) { 22 | if (!interactive()) { 23 | abort("Please set up environmental variables before running non-interactive session.") 24 | } 25 | 26 | askpass::askpass(message) 27 | } 28 | 29 | ask_non_secret <- function(title, message, default = NULL) { 30 | if (!interactive()) { 31 | abort("Please set up environmental variables before running non-interactive session.") 32 | } 33 | 34 | if (rstudioapi::isAvailable()) { 35 | return(rstudioapi::showPrompt(title, message, default)) 36 | } 37 | 38 | # Fallback to the readline 39 | readline(message) 40 | } 41 | 42 | ask_confluence_url <- function() ask_non_secret("URL", "Base URL of Confluence API: ", default = "https://") 43 | ask_confluence_username <- function() ask_non_secret("Username", "Username for Confluence: ", default = "") 44 | ask_confluence_password <- function() ask_secret("Password for Confluence: ") 45 | 46 | confl_verb <- function(verb, path, ...) { 47 | base_url <- Sys.getenv("CONFLUENCE_URL") %|""|% ask_confluence_url() 48 | # remove trailing / 49 | base_url <- stringi::stri_replace_last_regex(base_url, "/$", "") 50 | # remove /rest/api 51 | base_url <- stringi::stri_replace_last_regex(base_url, "/rest/api$", "") 52 | 53 | username <- Sys.getenv("CONFLUENCE_USERNAME") %|""|% ask_confluence_username() 54 | password <- Sys.getenv("CONFLUENCE_PASSWORD") %|""|% ask_confluence_password() 55 | 56 | res <- httr::VERB( 57 | verb = verb, 58 | url = glue("{base_url}/rest/api{path}"), 59 | httr::authenticate(username, password), 60 | ... 61 | ) 62 | 63 | if (httr::status_code(res) >= 300) { 64 | abort(paste( 65 | httr::http_condition(res, type = "error"), 66 | httr::content(res) 67 | )) 68 | } 69 | 70 | Sys.setenv(CONFLUENCE_URL = base_url) 71 | Sys.setenv(CONFLUENCE_USERNAME = username) 72 | Sys.setenv(CONFLUENCE_PASSWORD = password) 73 | 74 | res 75 | } 76 | 77 | # TODO: should modify only inside img tags. 78 | embed_images <- function(html_text, imgs, imgs_realpath) { 79 | for (i in seq_along(imgs)) { 80 | locs <- stringi::stri_locate_all_fixed(html_text, imgs[[i]])[[1]] 81 | for (loc in rev(split(locs, row(locs)))) { 82 | stringi::stri_sub(html_text, loc[1], loc[2]) <- knitr::image_uri(imgs_realpath[[i]]) 83 | } 84 | } 85 | 86 | html_text 87 | } 88 | 89 | abort_if_null <- function(...) { 90 | x <- quos(..., .named = TRUE) 91 | nulls <- purrr::map_lgl(x, ~ is.null(eval_tidy(.))) 92 | null_variables <- names(x)[nulls] 93 | 94 | if (length(null_variables) == 0) { 95 | return(invisible(NULL)) 96 | } 97 | 98 | null_variables <- glue("`{null_variables}`") 99 | null_variables <- glue_collapse(null_variables, sep = ", ", last = " and ") 100 | abort(glue("Please provide {null_variables}!")) 101 | } 102 | -------------------------------------------------------------------------------- /man/confluence_document.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/addin.R, R/document.R 3 | \name{confl_create_post_from_Rmd} 4 | \alias{confl_create_post_from_Rmd} 5 | \alias{confluence_document} 6 | \title{Publish R Markdown Document to 'Confluence'} 7 | \usage{ 8 | confl_create_post_from_Rmd(Rmd_file, interactive = NULL, params = NULL, ...) 9 | 10 | confluence_document( 11 | title = NULL, 12 | space_key = NULL, 13 | parent_id = NULL, 14 | type = c("page", "blogpost"), 15 | toc = FALSE, 16 | toc_depth = 7, 17 | code_folding = c("none", "hide"), 18 | supported_syntax_highlighting = getOption("conflr_supported_syntax_highlighting"), 19 | update = NULL, 20 | use_original_size = FALSE, 21 | minor_edit = FALSE, 22 | interactive = NULL 23 | ) 24 | } 25 | \arguments{ 26 | \item{Rmd_file}{Path to an .Rmd file.} 27 | 28 | \item{interactive}{If \code{FALSE}, shiny interface is not launched.} 29 | 30 | \item{params}{If provided, a list of named parameters that override custom 31 | params in the YAML front-matter.} 32 | 33 | \item{...}{Arguments passed to \code{confluence_documents()}.} 34 | 35 | \item{title}{Title of the post.} 36 | 37 | \item{space_key}{The space key to find content under.} 38 | 39 | \item{parent_id}{The page ID of the parent pages.} 40 | 41 | \item{type}{The content type to return. Default value: \code{page}. Valid values: \code{page}, \code{blogpost}.} 42 | 43 | \item{toc}{If \code{TRUE}, include a table of contents in the output.} 44 | 45 | \item{toc_depth}{The max level of headers to include in the table of contents.} 46 | 47 | \item{code_folding}{If \code{"hide"}, fold code blocks by default.} 48 | 49 | \item{supported_syntax_highlighting}{A named character vector of supported syntax highlighting other than default (e.g. \code{c(r = "r")}).} 50 | 51 | \item{update}{If \code{TRUE}, overwrite the existing page (if it exists).} 52 | 53 | \item{use_original_size}{If \code{TRUE}, use the original image sizes.} 54 | 55 | \item{minor_edit}{If \code{TRUE}, will mark the \code{update} as a minor edit not notifying watchers.} 56 | } 57 | \value{ 58 | \code{confl_create_post_from_Rmd()} returns the URL of the published page. 59 | 60 | \code{confluence_document()} returns an \code{rmarkdown_output_format} object. 61 | } 62 | \description{ 63 | Knit and post a given R Markdown file to 'Confluence'. 64 | } 65 | \details{ 66 | All options of \code{confluence_document()} can also be specified via the argument 67 | of \code{confl_create_post_from_Rmd}. If an option is specified on both, the one given 68 | as an argument will be used.\preformatted{--- 69 | title: "title1" 70 | output: 71 | confluence_document: 72 | space_key: "space1" 73 | parent_id: 1234 74 | toc: TRUE 75 | toc_depth: 4 76 | code_folding: hide 77 | supported_syntax_highlighting: 78 | r: r 79 | foo: bar 80 | update: true 81 | use_original_size: true 82 | minor_edit: false 83 | --- 84 | 85 | ... 86 | } 87 | } 88 | \examples{ 89 | example_Rmd <- system.file("extdata/example.Rmd", package = "conflr") 90 | 91 | \dontrun{ 92 | # Convert an R Markdown document into a 'Confluence' page interactively 93 | confl_create_post_from_Rmd(example_Rmd) 94 | 95 | # You can override most of the parameters of confluence_document() 96 | confl_create_post_from_Rmd(example_Rmd, space_key = "space1", toc = TRUE) 97 | } 98 | 99 | \dontrun{ 100 | # A custom R markdown format that can be passed to rmarkdown::render() 101 | format <- confluence_document(space_key = "space1") 102 | rmarkdown::render(system.file("extdata/example.Rmd", package = "conflr"), format) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $(".examples, div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent; 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | test_that('%|""|% works', { 12 | expect_equal("a" %|""|% "b", "a") 13 | expect_equal("" %|""|% "b", "b") 14 | expect_equal(NULL %|""|% "b", "b") 15 | expect_equal("" %|""|% "", "") 16 | }) 17 | 18 | envvars <- list( 19 | CONFLUENCE_URL = "", 20 | CONFLUENCE_USERNAME = "user", 21 | CONFLUENCE_PASSWORD = "pass" 22 | ) 23 | 24 | test_that("confl_verb() asks for credentials if it is not set", { 25 | skip_on_cran() 26 | 27 | # If the request succeeds, the provided credential is stored as an envvar 28 | res_success <- structure(list(status_code = 200), class = "response") 29 | mock_success <- mockery::mock(res_success, cycle = TRUE) 30 | mock_ask <- mockery::mock("foo") 31 | 32 | with_mock( 33 | "httr::VERB" = mock_success, 34 | "conflr::ask_confluence_url" = mock_ask, 35 | "conflr::ask_confluence_username" = mock_ask, 36 | "conflr::ask_confluence_password" = mock_ask, 37 | withr::with_envvar( 38 | envvars, 39 | { 40 | confl_verb("GET", "/") 41 | expect_equal(Sys.getenv("CONFLUENCE_URL"), "foo") 42 | } 43 | ) 44 | ) 45 | 46 | mockery::expect_called(mock_ask, 1) 47 | mockery::expect_call(mock_ask, n = 1, ask_confluence_url()) 48 | 49 | 50 | # If the request fails, the provided credential is discarded 51 | 52 | res_failure <- structure(list(status_code = 500), class = "response") 53 | mock_failure <- mockery::mock(res_failure, cycle = TRUE) 54 | 55 | mock_ask2 <- mockery::mock("foo") 56 | 57 | with_mock( 58 | "httr::VERB" = mock_failure, 59 | "conflr::ask_confluence_url" = mock_ask2, 60 | "conflr::ask_confluence_username" = mock_ask2, 61 | "conflr::ask_confluence_password" = mock_ask2, 62 | withr::with_envvar( 63 | envvars, 64 | { 65 | expect_error(confl_verb("GET", "/")) 66 | # the url provided should not be stored 67 | expect_equal(Sys.getenv("CONFLUENCE_URL"), "") 68 | } 69 | ) 70 | ) 71 | 72 | mockery::expect_called(mock_ask2, 1) 73 | mockery::expect_call(mock_ask2, n = 1, ask_confluence_url()) 74 | }) 75 | 76 | test_that("try_get_existing_page_id() works", { 77 | with_mock( 78 | "conflr::confl_list_pages" = function(...) list(size = 1, results = list(list(id = 1))), 79 | { 80 | expect_equal(try_get_existing_page_id("foo", "bar"), 1) 81 | } 82 | ) 83 | 84 | with_mock( 85 | "conflr::confl_list_pages" = function(...) list(size = 0, results = list()), 86 | { 87 | expect_equal(try_get_existing_page_id("foo", "bar"), NULL) 88 | } 89 | ) 90 | }) 91 | 92 | test_that("try_get_personal_space_key() handles personal spaces", { 93 | with_mock( 94 | "conflr::confl_get_space" = function(...) list(key = "space"), 95 | { 96 | expect_equal(try_get_personal_space_key("username"), "space") 97 | } 98 | ) 99 | 100 | with_mock( 101 | "conflr::confl_get_space" = function(...) abort(), 102 | { 103 | expect_equal(try_get_personal_space_key("unknown"), NULL) 104 | } 105 | ) 106 | }) 107 | 108 | test_that("abort_if_null() works", { 109 | expect_error(abort_if_null(x = NULL), "Please provide `x`!") 110 | expect_error(abort_if_null(x = NULL, y = 1), "Please provide `x`!") 111 | expect_error(abort_if_null(x = NULL, y = 1, z = NULL), "Please provide `x` and `z`!") 112 | expect_silent(abort_if_null(x = 1, y = "a", z = NA)) 113 | 114 | x <- NULL 115 | y <- 1 116 | z <- NULL 117 | expect_error(abort_if_null(x), "Please provide `x`!") 118 | expect_error(abort_if_null(x, y, z), "Please provide `x` and `z`!") 119 | expect_error(abort_if_null(x, y = NULL, z), "Please provide `x`, `y` and `z`!") 120 | expect_silent(abort_if_null(x = 1, y = "a", z = NA)) 121 | }) 122 | -------------------------------------------------------------------------------- /R/tabset.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | # {.tabset} notation will be brown away on conversion to commonmark as it is a Pandoc-only syntax. 12 | wrap_tabsets <- function(x) { 13 | stringi::stri_replace_all_regex(x, 14 | "(^#+.*?)\\{[^{}]*.tabset[^{}]*\\}", 15 | "$1\n\n\n", 16 | multiline = TRUE 17 | ) 18 | } 19 | 20 | mark_tabsets <- function(html_doc) { 21 | # If there are no tabset-start tag, do nothing 22 | if (length(xml2::xml_find_all(html_doc, "//body//tabset-start")) == 0) { 23 | return(NULL) 24 | } 25 | 26 | xpath <- c("//body//tabset-start", glue("//body//h{i}", i = 1:9)) 27 | tags <- xml2::xml_find_all(html_doc, glue_collapse(xpath, sep = "|")) 28 | 29 | pos_tabset_start <- which(xml2::xml_name(tags) == "tabset-start") 30 | 31 | second_char <- substr(xml2::xml_name(tags), 2, 2) 32 | second_char[pos_tabset_start] <- NA 33 | h_levels <- as.integer(second_char) 34 | 35 | i <- 1 36 | while(i <= length(pos_tabset_start)) { 37 | start <- pos_tabset_start[i] 38 | 39 | h <- h_levels[start - 1] 40 | # The corresponding end is the nearest position among the ones that are larger than the start. 41 | pos_tabset_end_candidates <- which(h_levels <= h) 42 | end <- pos_tabset_end_candidates[pos_tabset_end_candidates > start] 43 | 44 | if (length(end) == 0) { 45 | # If there's no corresponding end, it means the end of the document is the end of the tabset. 46 | end <- length(tags) + 1 47 | xml2::xml_add_child(xml2::xml_find_first(html_doc, "//body"), xml2::as_xml_document(""), .where = "after") 48 | } else { 49 | end <- min(end) 50 | xml2::xml_add_sibling(tags[[end]], xml2::as_xml_document(""), .where = "before") 51 | } 52 | 53 | # Tabs are the header tags whose level is one step lower than the start, 54 | # and is between the start and the end 55 | pos_tab_candidates <- which(h_levels == h + 1) 56 | pos_tab <- pos_tab_candidates[start < pos_tab_candidates & pos_tab_candidates < end] 57 | 58 | if (length(pos_tab) > 0) { 59 | xml2::xml_set_name(tags[pos_tab[1]], "tabset-tab-first") 60 | xml2::xml_set_name(tags[pos_tab[-1]], "tabset-tab") 61 | } 62 | 63 | # If there's some tag inside the tabset, remove them. 64 | invalid_tabsets <- pos_tabset_start[start < pos_tabset_start & pos_tabset_start < end] 65 | if (length(invalid_tabsets) > 0) { 66 | i <- i + length(invalid_tabsets) 67 | purrr::walk(tags[invalid_tabsets], xml2::xml_remove) 68 | } 69 | 70 | i <- i + 1 71 | } 72 | 73 | NULL 74 | } 75 | 76 | 77 | replace_tabsets <- function(x) { 78 | x <- replace_tabsets_start(x) 79 | x <- replace_tabsets_tabs_first(x) 80 | x <- replace_tabsets_tabs(x) 81 | x <- replace_tabsets_end(x) 82 | x 83 | } 84 | 85 | 86 | replace_tabsets_start <- function(x) { 87 | stringi::stri_replace_all_regex( 88 | x, 89 | # TODO: ()(.*?)() doesn't work (c.f. https://stackoverflow.com/a/40556433) 90 | "()([^<>]+)()\\s*", 91 | '$1$2$3 92 | 93 | $2 94 | ', 95 | multiline = TRUE, 96 | dotall = TRUE 97 | ) 98 | } 99 | 100 | replace_tabsets_tabs_first <- function(x) { 101 | stringi::stri_replace_all_regex( 102 | x, 103 | "\\s*(.*?)\\s*", 104 | ' 105 | $1 106 | ', 107 | multiline = TRUE, 108 | dotall = TRUE 109 | ) 110 | } 111 | 112 | replace_tabsets_tabs <- function(x) { 113 | stringi::stri_replace_all_regex( 114 | x, 115 | "\\s*(.*?)\\s*", 116 | ' 117 | 118 | 119 | 120 | $1 121 | ', 122 | multiline = TRUE, 123 | dotall = TRUE 124 | ) 125 | } 126 | 127 | replace_tabsets_end <- function(x) { 128 | stringi::stri_replace_all_regex( 129 | x, 130 | "", 131 | ' 132 | 133 | 134 | 135 | ', 136 | multiline = TRUE, 137 | dotall = TRUE 138 | ) 139 | } 140 | -------------------------------------------------------------------------------- /R/content.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | 12 | #' REST Wrapper for the ContentService 13 | #' 14 | #' @name confl_content 15 | #' @param type 16 | #' The content type to return. Default value: `page`. Valid values: `page`, `blogpost`. 17 | #' @param limit 18 | #' The limit of the number of items to return, this may be restricted by fixed system limits. 19 | #' @param start 20 | #' The start point of the collection to return. 21 | #' @param spaceKey 22 | #' The space key to find content under. 23 | #' @param title 24 | #' The title of the page to find. Required for `page` type. 25 | #' @param expand 26 | #' A comma separated list of properties to expand. To refer the nested 27 | #' contents, use periods. (e.g. `body.storage,history`). 28 | #' 29 | #' @return 30 | #' The API response as a list. 31 | #' 32 | #' @seealso 33 | #' 34 | #' @examples 35 | #' \dontrun{ 36 | #' # Create a page titled "title1" on a space named "space1" 37 | #' result <- confl_post_page( 38 | #' type = "page", 39 | #' spaceKey = "space1", 40 | #' title = "title1", 41 | #' body = "

    example

    This is example

    " 42 | #' ) 43 | #' 44 | #' # Jump to the result page 45 | #' browseURL(paste0(result$`_links`$base, result$`_links`$webui)) 46 | #' 47 | #' # List pages under space "space1" up to 10 pages 48 | #' confl_list_pages(spaceKey = "space1") 49 | #' } 50 | #' 51 | #' @export 52 | confl_list_pages <- function(type = c("page", "blogpost", "comment", "attachment"), 53 | limit = 10, 54 | start = 0, 55 | spaceKey = NULL, 56 | title = NULL, 57 | expand = NULL) { 58 | type <- arg_match(type) 59 | query <- list(type = type, limit = limit, start = start, spaceKey = spaceKey, title = title, expand = expand) 60 | res <- confl_verb("GET", "/content/", query = purrr::compact(query)) 61 | httr::content(res) 62 | } 63 | 64 | 65 | #' @rdname confl_content 66 | #' 67 | #' @param id 68 | #' ID of the content. 69 | #' @export 70 | confl_get_page <- function(id, expand = "body.storage") { 71 | id <- as.character(id) 72 | res <- confl_verb("GET", glue("/content/{id}"), query = list(expand = expand)) 73 | httr::content(res) 74 | } 75 | 76 | #' @rdname confl_content 77 | #' @param body 78 | #' The HTML source of the page. 79 | #' @param ancestors 80 | #' The page ID of the parent pages. 81 | #' @export 82 | confl_post_page <- function(type = c("page", "blogpost"), 83 | spaceKey, 84 | title, 85 | body, 86 | ancestors = NULL) { 87 | type <- arg_match(type) 88 | 89 | req_body <- list( 90 | type = type, 91 | title = title, 92 | space = list(key = spaceKey), 93 | body = list(storage = list(value = body, representation = "storage")) 94 | ) 95 | 96 | if (!is.null(ancestors) && !identical(ancestors, "")) { 97 | ancestors <- stringi::stri_trim_both(ancestors) 98 | req_body$ancestors <- purrr::map(ancestors, ~ list(id = .)) 99 | } 100 | res <- confl_verb("POST", "/content/", 101 | body = req_body, encode = "json" 102 | ) 103 | httr::content(res) 104 | } 105 | 106 | #' @rdname confl_content 107 | #' @param minor_edit 108 | #' If `TRUE`, will mark the `update` as a minor edit not notifying watchers. 109 | #' @export 110 | confl_update_page <- function(id, 111 | title, 112 | body, 113 | minor_edit = FALSE) { 114 | id <- as.character(id) 115 | page_info <- confl_get_page(id, expand = "version") 116 | 117 | res <- confl_verb("PUT", glue("/content/{id}"), 118 | body = list( 119 | type = page_info$type, 120 | title = title, 121 | body = list(storage = list(value = body, representation = "storage")), 122 | version = list( 123 | number = page_info$version$number + 1L, 124 | minorEdit = minor_edit 125 | ) 126 | ), 127 | encode = "json" 128 | ) 129 | httr::content(res) 130 | } 131 | 132 | 133 | #' @rdname confl_content 134 | #' @export 135 | confl_delete_page <- function(id) { 136 | id <- as.character(id) 137 | res <- confl_verb("DELETE", glue("/content/{id}")) 138 | httr::content(res) 139 | } 140 | -------------------------------------------------------------------------------- /tests/testthat/test-tabset.R: -------------------------------------------------------------------------------- 1 | test_that("wrap_tabsets() works", { 2 | expect <- "# h1\n# h2 \n\n\n\ntest" 3 | 4 | expect_equal(wrap_tabsets("# h1\n# h2 {.tabset}\ntest"), expect) 5 | expect_equal(wrap_tabsets("# h1\n# h2 { .tabset }\ntest"), expect) 6 | expect_equal(wrap_tabsets("# h1\n# h2 {.some-class .tabset .other-class}\ntest"), expect) 7 | }) 8 | 9 | html_doc <- function(x) { 10 | x <- commonmark::markdown_html(x) 11 | xml2::read_xml( 12 | paste0("", x, ""), 13 | options = c("RECOVER", "NOERROR", "NOBLANKS") 14 | ) 15 | } 16 | 17 | test_that("mark_tabsets() works", { 18 | # Do nothing on the document without tabsets 19 | expect_null(mark_tabsets(html_doc("## t1\n##t2"))) 20 | 21 | html_doc1 <- html_doc( 22 | " 23 | ## t1 24 | 25 | 26 | 27 | ### t2 28 | 29 | content2 30 | 31 | ### t3 32 | 33 | content3 34 | 35 | ") 36 | 37 | mark_tabsets(html_doc1) 38 | 39 | expect_equal( 40 | as.character(html_doc1), 41 | ' 42 | 43 |

    t1

    44 | 45 | t2 46 |

    content2

    47 | t3 48 |

    content3

    49 | 50 | 51 | ') 52 | 53 | html_doc2 <- html_doc( 54 | " 55 | ## t1 56 | 57 | 58 | 59 | ### t2 60 | 61 | content2 62 | 63 | # t3 64 | 65 | 66 | 67 | ## t4 68 | 69 | content4 70 | 71 | ## t5 72 | 73 | # t1 74 | 75 | ") 76 | 77 | mark_tabsets(html_doc2) 78 | 79 | expect_equal( 80 | as.character(html_doc2), 81 | ' 82 | 83 |

    t1

    84 | 85 | t2 86 |

    content2

    87 | 88 |

    t3

    89 | 90 | t4 91 |

    content4

    92 | t5 93 | 94 |

    t1

    95 | 96 | ') 97 | 98 | # second should be ignored, and t4-1 is not a tab 99 | html_doc3 <- html_doc( 100 | " 101 | ## t1 102 | 103 | 104 | 105 | ### t2 106 | 107 | content2 108 | 109 | ### t3 110 | 111 | 112 | 113 | ### t4 114 | 115 | #### t4-1 116 | 117 | content4 118 | 119 | ### t5 120 | 121 | # t1 122 | 123 | ") 124 | 125 | mark_tabsets(html_doc3) 126 | 127 | expect_equal( 128 | as.character(html_doc3), 129 | ' 130 | 131 |

    t1

    132 | 133 | t2 134 |

    content2

    135 | t3 136 | t4 137 |

    t4-1

    138 |

    content4

    139 | t5 140 | 141 |

    t1

    142 | 143 | ') 144 | }) 145 | 146 | test_that("translate_to_confl_macro() can handle tabset", { 147 | # code chunk 148 | html_text1 <- commonmark::markdown_html( 149 | " 150 | ## t1 151 | 152 | 153 | 154 | ### t2 155 | 156 | content2 157 | 158 | ### t3 159 | 160 | content3 161 | 162 | " 163 | ) 164 | expect_equal( 165 | translate_to_confl_macro(html_text1, supported_syntax_highlighting_default), 166 | '

    t1

    167 | 168 | t1 169 | 170 | 171 | t2 172 | 173 |

    content2

    174 |
    175 |
    176 | 177 | 178 | t3 179 | 180 |

    content3

    181 |
    182 |
    183 | 184 |
    185 |
    ') 186 | 187 | 188 | # code chunk 189 | html_text2 <- commonmark::markdown_html( 190 | " 191 | ## t0 192 | 193 | ## t1 194 | 195 | 196 | 197 | ### t2 198 | 199 | content2 200 | 201 | ### t3 202 | 203 | content3 204 | 205 | ## t4 206 | 207 | conent4 208 | 209 | ") 210 | 211 | expect_equal( 212 | translate_to_confl_macro(html_text2, supported_syntax_highlighting_default), 213 | '

    t0

    214 |

    t1

    215 | 216 | t1 217 | 218 | 219 | t2 220 | 221 |

    content2

    222 |
    223 |
    224 | 225 | 226 | t3 227 | 228 |

    content3

    229 |
    230 |
    231 | 232 |
    233 |
    234 |

    t4

    235 |

    conent4

    ') 236 | }) 237 | -------------------------------------------------------------------------------- /R/macros.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2020 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | #' General Confluence macro builder for internal use 12 | #' @param type inline or block code style to be used for the HTML content 13 | #' @param name Confluence macro name 14 | #' @param parameters named list of optional macro parameters 15 | #' @param body optional \code{confl-ac-rich-text-body} content 16 | #' @return HTML 17 | #' @keywords internal 18 | conf_macro_generator <- function(type = c('inline', 'block'), 19 | name, parameters = NULL, body = NULL) { 20 | 21 | type <- match.arg(type) 22 | 23 | open <- switch(type, inline = '`', block = '\n```{=html}\n') 24 | close <- switch(type, inline = '`{=html}', block = '\n```\n') 25 | spacer <- switch(type, inline = '', block = '\n') 26 | 27 | macro <- glue('{open}{spacer}') 28 | 29 | if (!is.null(parameters)) { 30 | for (parameter in names(parameters)) { 31 | macro <- paste0( 32 | macro, 33 | glue(''), 34 | parameters[[parameter]], 35 | '{spacer}') 36 | } 37 | } 38 | 39 | if (!is.null(body)) { 40 | macro <- paste0( 41 | macro, 42 | '', 43 | spacer, body, spacer, 44 | '', 45 | spacer) 46 | } 47 | 48 | macro <- paste0(macro, '') 49 | macro <- paste0(macro, close) 50 | 51 | macro 52 | 53 | } 54 | 55 | #' Generate Confluence macro for dynamic Table of Contents 56 | #' @param levels max number of levels to show 57 | #' @return HTML as string 58 | #' @export 59 | #' @references \url{https://confluence.atlassian.com/doc/table-of-contents-macro-182682099.html} 60 | #' @examples 61 | #' confl_macro_toc(2) 62 | confl_macro_toc <- function(levels) { 63 | conf_macro_generator(type = 'block', name = 'toc', parameters = list(levels = levels)) 64 | } 65 | 66 | 67 | #' Generate Confluence macro referencing a Jira ticket 68 | #' @param key Jira ticket id, eg CONFLR-XXXX 69 | #' @return HTML as string 70 | #' @export 71 | #' @examples 72 | #' confl_macro_jira('CONFLR-42') 73 | confl_macro_jira <- function(key) { 74 | conf_macro_generator(type = 'inline', name = 'jira', parameters = list(key = key)) 75 | } 76 | 77 | 78 | #' Generate Confluence macro for an expand block 79 | #' @param title defines the text that appears next to the expand/collapse icon 80 | #' @param body this HTML content will be visible when someone clicks the macro title 81 | #' @return HTML as string 82 | #' @export 83 | #' @references \url{https://confluence.atlassian.com/doc/expand-macro-223222352.html} 84 | #' @note \code{content} needs to be HTML, so look at \code{commonmark::markdown_html}, \code{pander::pander} and eg \code{xtable} for doing the conversion before passing to \code{confluence_expand} 85 | #' @examples \dontrun{ 86 | #' confl_macro_expand( 87 | #' 'Example block', 88 | #' commonmark::markdown_html(pander::pander_return(list(a = list(b = 4), c = 2)))) 89 | #' } 90 | confl_macro_expand <- function(title, body) { 91 | conf_macro_generator(type = 'block', name = 'expand', 92 | parameters = list(title = title), 93 | body = body) 94 | } 95 | 96 | 97 | #' Generate Confluence macro for an excerpt block 98 | #' @param body HTML content of the excerpt 99 | #' @param hidden if the \code{body} should be shown on the actual page 100 | #' @return HTML as string 101 | #' @export 102 | #' @references \url{https://confluence.atlassian.com/doc/excerpt-macro-148062.html} 103 | confl_macro_excerpt <- function(body, hidden = TRUE) { 104 | conf_macro_generator(type = 'block', name = 'excerpt', 105 | parameters = list(hidden = tolower(hidden)), 106 | body = body) 107 | } 108 | 109 | 110 | #' Generate Confluence macro for a Info, Tip, Note, or Warning block 111 | #' @param body HTML content of the block 112 | #' @return HTML as string 113 | #' @usage 114 | #' confl_macro_info(body) 115 | #' 116 | #' confl_macro_tip(body) 117 | #' 118 | #' confl_macro_note(body) 119 | #' 120 | #' confl_macro_warning(body) 121 | #' @export 122 | #' @references \url{https://confluence.atlassian.com/doc/info-tip-note-and-warning-macros-51872369.html} 123 | #' @aliases confl_macro_info confl_macro_tip confl_macro_note confl_macro_warning 124 | confl_macro_info <- function(body) { 125 | conf_macro_generator(type = 'block', name = 'info', body = body) 126 | } 127 | #' @export 128 | confl_macro_tip <- function(body) { 129 | conf_macro_generator(type = 'block', name = 'tip', body = body) 130 | } 131 | #' @export 132 | confl_macro_note <- function(body) { 133 | conf_macro_generator(type = 'block', name = 'note', body = body) 134 | } 135 | #' @export 136 | confl_macro_warning <- function(body) { 137 | conf_macro_generator(type = 'block', name = 'warning', body = body) 138 | } 139 | -------------------------------------------------------------------------------- /R/addin-internals.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | confl_upload <- function(title, space_key, type, parent_id, html_text, 12 | imgs, imgs_realpath, 13 | id = NULL, 14 | toc = FALSE, toc_depth = 7, 15 | code_folding = "none", 16 | supported_syntax_highlighting = getOption("conflr_supported_syntax_highlighting"), 17 | update = FALSE, 18 | use_original_size = FALSE, 19 | minor_edit = FALSE, 20 | session = NULL) { 21 | # TODO: NULL arguments should be `compact()`ed in confluence_document(), 22 | # but it's not possible to provide a backward-compatibility for 23 | # confluence_settings. So we need to detect the NULL here. 24 | abort_if_null(title, space_key, type) 25 | 26 | # 1) id is NULL, update is TRUE : proceed 27 | # 2) id is NULL, update is FALSE: proceed 28 | # 3) id is NULL but the page exists, update is TRUE : proceed 29 | # 4) id is NULL but the page exists, update is FALSE: abort 30 | # 5) id is not NULL, update is TRUE : proceed 31 | # 6) id is not NULL, update is FALSE: abort 32 | 33 | # If id is NULL, check if there's the same title of the page 34 | if (is.null(id)) { 35 | # TODO: we check if there's an existing page twice; here and 36 | # confl_update_interactively(). Can we remove the duplication? 37 | id <- try_get_existing_page_id(title = title, space_key = space_key) 38 | } 39 | 40 | # If there's an existing page and the user don't want to update, abort 41 | if (!is.null(id)) { 42 | if (!isTRUE(update)) { 43 | abort("Page already exists. Re-run with `update = TRUE` to overwrite.") 44 | } 45 | } else { 46 | # if the page doesn't exist yet, create a blank page 47 | blank_page <- confl_post_page( 48 | type = type, 49 | spaceKey = space_key, 50 | title = title, 51 | body = "", 52 | ancestors = parent_id 53 | ) 54 | id <- blank_page$id 55 | } 56 | 57 | progress <- new_progress(session, min = 0, max = 2) 58 | on.exit(progress$close()) 59 | 60 | # Step 1) Upload Images 61 | progress$set(message = "Checking the existing images...") 62 | 63 | # Check if the images already exist 64 | imgs_exist <- confl_list_attachments(id) 65 | imgs_exist_ids <- purrr::map_chr(imgs_exist$results, "id") 66 | names(imgs_exist_ids) <- purrr::map_chr(imgs_exist$results, "title") 67 | 68 | progress$set(message = "Uploading the images...") 69 | num_imgs <- length(imgs) 70 | for (i in seq_along(imgs)) { 71 | progress$set(detail = imgs[i]) 72 | 73 | # attempt to avoid rate limits 74 | Sys.sleep(0.2) 75 | 76 | img_id <- imgs_exist_ids[basename(imgs[i])] 77 | if (is.na(img_id)) { 78 | confl_post_attachment(id, imgs_realpath[i], minor_edit = minor_edit) 79 | } else { 80 | confl_update_attachment_data(id, img_id, imgs_realpath[i], minor_edit = minor_edit) 81 | } 82 | 83 | progress$set(value = i / num_imgs) 84 | } 85 | 86 | # Step 2) Upload the document 87 | progress$set(message = "Uploading the document...") 88 | 89 | html_text <- translate_to_confl_macro( 90 | html_text, 91 | image_size_default = if (!use_original_size) 600 else NULL, 92 | supported_syntax_highlighting = supported_syntax_highlighting, 93 | code_folding = code_folding 94 | ) 95 | 96 | # Restore and tags before actually posting to Confluence 97 | html_text <- restore_confluence_namespaces(html_text) 98 | 99 | if (toc) { 100 | toc_tag <- paste( 101 | "

    ", 102 | ' ', 103 | glue(' {toc_depth}'), 104 | " ", 105 | "

    ", 106 | sep = "\n" 107 | ) 108 | html_text <- paste(toc_tag, html_text, sep = "\n") 109 | } 110 | 111 | result <- confl_update_page( 112 | id = id, 113 | title = title, 114 | body = html_text, 115 | minor_edit = minor_edit 116 | ) 117 | result_url <- paste0(result$`_links`$base, result$`_links`$webui) 118 | 119 | progress$set(value = 2, message = "Done!") 120 | Sys.sleep(2) 121 | 122 | if (!is.null(session)) { 123 | shiny::stopApp() 124 | } 125 | 126 | # Even on non-interactive sessions, jump to the URL if knitting is done on RStudio 127 | if (interactive() || 128 | (identical(Sys.getenv("RSTUDIO"), "1") && identical(Sys.getenv("TESTTHAT"), ""))) { 129 | browseURL(result_url) 130 | } else { 131 | message(paste0("Results at: ", result_url)) 132 | } 133 | 134 | result_url 135 | } 136 | 137 | try_get_existing_page_id <- function(title, space_key) { 138 | existing_pages <- confl_list_pages(title = title, spaceKey = space_key) 139 | if (length(existing_pages$results) == 0) { 140 | return(NULL) 141 | } 142 | existing_pages$results[[1]]$id 143 | } 144 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Authors • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 105 | 106 | 107 | 108 |
    109 | 110 |
    111 |
    112 | 115 | 116 |
      117 |
    • 118 |

      Hiroaki Yutani. Author, maintainer. 119 |

      120 |
    • 121 |
    • 122 |

      LINE Corporation. Copyright holder. 123 |

      124 |
    • 125 |
    126 | 127 |
    128 | 129 |
    130 | 131 | 132 | 133 |
    134 | 137 | 138 |
    139 |

    Site built with pkgdown 1.5.1.

    140 |
    141 | 142 |
    143 |
    144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Page not found (404) • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 105 | 106 | 107 | 108 |
    109 | 110 |
    111 |
    112 | 115 | 116 | Content not found. Please use links in the navbar. 117 | 118 |
    119 | 120 | 125 | 126 |
    127 | 128 | 129 | 130 |
    131 | 134 | 135 |
    136 |

    Site built with pkgdown 1.5.1.

    137 |
    138 | 139 |
    140 |
    141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /tests/testthat/test-front-matter.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | expect_confluence_settings <- function(mock, ...) { 12 | expected <- list(...) 13 | cols_to_compare <- names(expected) 14 | expect_equal( 15 | mockery::mock_args(mock)[[1]][cols_to_compare], 16 | expected 17 | ) 18 | } 19 | 20 | Rmd_with_all_defaults <- 21 | 'title: "title1" 22 | output: 23 | conflr::confluence_document: 24 | space_key: "space1" 25 | parent_id: 1234 26 | toc: true 27 | toc_depth: 4 28 | supported_syntax_highlighting: 29 | r: r 30 | foo: bar 31 | update: true 32 | use_original_size: true' 33 | 34 | test_that("confluence_settings can be set from front-matter", { 35 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 36 | 37 | # case: all settings are specified in the Rmd 38 | confl_upload_mock <- mockery::mock(NULL) 39 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_all_defaults) 40 | 41 | expect_confluence_settings( 42 | confl_upload_mock, 43 | title = "title1", 44 | space_key = "space1", 45 | parent_id = 1234, 46 | toc = TRUE, 47 | toc_depth = 4, 48 | supported_syntax_highlighting = list(r = "r", foo = "bar"), 49 | update = TRUE, 50 | use_original_size = TRUE 51 | ) 52 | 53 | # case: args overwrite settings in the Rmd 54 | confl_upload_mock <- mockery::mock(NULL) 55 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_all_defaults, 56 | title = "title2", space_key = "space2", parent_id = 9999, 57 | toc = FALSE, toc_depth = 2, supported_syntax_highlighting = c(two_plus_two = "five"), 58 | update = FALSE, use_original_size = FALSE 59 | ) 60 | 61 | expect_confluence_settings( 62 | confl_upload_mock, 63 | title = "title2", 64 | space_key = "space2", 65 | parent_id = 9999, 66 | toc = FALSE, 67 | toc_depth = 2, 68 | supported_syntax_highlighting = c(two_plus_two = "five"), 69 | update = FALSE, 70 | use_original_size = FALSE 71 | ) 72 | }) 73 | 74 | Rmd_with_two_titles <- 75 | 'title: "title1" 76 | output: 77 | conflr::confluence_document: 78 | title: "title2" 79 | space_key: "space1" 80 | parent_id: 1234 81 | toc: TRUE 82 | toc_depth: 4 83 | supported_syntax_highlighting: 84 | r: r 85 | foo: bar 86 | update: true 87 | use_original_size: true' 88 | 89 | test_that("confluence_settings$title is prior to title", { 90 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 91 | 92 | # case: confluence_settings$title is prior to title 93 | confl_upload_mock <- mockery::mock(NULL) 94 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_two_titles) 95 | 96 | expect_confluence_settings( 97 | confl_upload_mock, 98 | title = "title2", 99 | space_key = "space1", 100 | parent_id = 1234, 101 | toc = TRUE, 102 | toc_depth = 4, 103 | supported_syntax_highlighting = list(r = "r", foo = "bar"), 104 | update = TRUE, 105 | use_original_size = TRUE 106 | ) 107 | 108 | # case: args overwrite settings in the Rmd 109 | confl_upload_mock <- mockery::mock(NULL) 110 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_two_titles, 111 | title = "title3" 112 | ) 113 | 114 | expect_confluence_settings( 115 | confl_upload_mock, 116 | title = "title3", 117 | space_key = "space1", 118 | parent_id = 1234, 119 | toc = TRUE, 120 | toc_depth = 4, 121 | supported_syntax_highlighting = list(r = "r", foo = "bar"), 122 | update = TRUE, 123 | use_original_size = TRUE 124 | ) 125 | }) 126 | 127 | Rmd_with_some_settings <- 128 | 'title: "title1" 129 | output: 130 | conflr::confluence_document: 131 | space_key: "space1"' 132 | 133 | test_that("confluence_settings can be specified partially", { 134 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 135 | 136 | # case: confluence_settings$title is prior to title 137 | confl_upload_mock <- mockery::mock(NULL) 138 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_some_settings) 139 | 140 | expect_confluence_settings( 141 | confl_upload_mock, 142 | title = "title1", 143 | space_key = "space1" 144 | ) 145 | }) 146 | 147 | test_that("supported_syntax_highlighting can be set via option", { 148 | skip_if_not(rmarkdown::pandoc_available()) 149 | 150 | # case: confluence_settings$title is prior to title 151 | confl_upload_mock <- mockery::mock(NULL) 152 | 153 | withr::with_options( 154 | list(conflr_supported_syntax_highlighting = "r"), 155 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_with_some_settings) 156 | ) 157 | 158 | expect_confluence_settings( 159 | confl_upload_mock, 160 | supported_syntax_highlighting = "r" 161 | ) 162 | }) 163 | 164 | 165 | Rmd_without_space_key <- 'title: "title1"' 166 | 167 | test_that("confluence_settings raise an error when any of mandatory parameters are missing", { 168 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 169 | 170 | # case: when space_key is not in the front-matter but in the arguments, it works 171 | confl_upload_mock <- mockery::mock(NULL) 172 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_without_space_key, space_key = "space2") 173 | expect_confluence_settings( 174 | confl_upload_mock, 175 | title = "title1", 176 | space_key = "space2" 177 | ) 178 | }) 179 | 180 | Rmd_deprecated <- 181 | 'title: "title1" 182 | confluence_settings: 183 | space_key: "space1" 184 | parent_id: 1234 185 | toc: true 186 | toc_depth: 4 187 | supported_syntax_highlighting: 188 | r: r 189 | foo: bar 190 | update: true 191 | use_original_size: true' 192 | 193 | test_that("confluence_settings are accepted for backward-compatibility", { 194 | skip_if_not(rmarkdown::pandoc_available("1.12.3")) 195 | 196 | # case: all settings are specified in the Rmd 197 | confl_upload_mock <- mockery::mock(NULL) 198 | expect_warning( 199 | do_confl_create_post_from_Rmd(confl_upload_mock, Rmd_deprecated) 200 | ) 201 | 202 | expect_confluence_settings( 203 | confl_upload_mock, 204 | title = "title1", 205 | space_key = "space1", 206 | parent_id = 1234, 207 | toc = TRUE, 208 | toc_depth = 4, 209 | supported_syntax_highlighting = list(r = "r", foo = "bar"), 210 | update = TRUE, 211 | use_original_size = TRUE 212 | ) 213 | }) 214 | -------------------------------------------------------------------------------- /docs/reference/conflr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | R Client for 'Confluence' API — conflr • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
    66 |
    67 | 109 | 110 | 111 | 112 |
    113 | 114 |
    115 |
    116 | 121 | 122 |
    123 |

    Provides utilities for working with various 'Confluence' API 124 | <https://docs.atlassian.com/ConfluenceServer/rest/latest/>, including a 125 | functionality to convert an R Markdown document to 'Confluence' format and 126 | upload it to 'Confluence' automatically.

    127 |
    128 | 129 | 130 | 131 |

    See also

    132 | 133 |

    Useful links:

    138 | 139 |
    140 | 141 |
    142 | 147 |
    148 | 149 | 150 |
    151 | 154 | 155 |
    156 |

    Site built with pkgdown 1.5.1.

    157 |
    158 | 159 |
    160 |
    161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/reference/confl_macro_jira.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Generate Confluence macro referencing a Jira ticket — confl_macro_jira • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    Generate Confluence macro referencing a Jira ticket

    121 |
    122 | 123 |
    confl_macro_jira(key)
    124 | 125 |

    Arguments

    126 | 127 | 128 | 129 | 130 | 131 | 132 |
    key

    Jira ticket id, eg CONFLR-XXXX

    133 | 134 |

    Value

    135 | 136 |

    HTML as string

    137 | 138 |

    Examples

    139 |
    confl_macro_jira('CONFLR-42')
    #> [1] "`<ac:structured-macro ac:name=\"jira\"><ac:parameter ac:name=\"key\">CONFLR-42</ac:parameter></ac:structured-macro>`{=html}"
    140 |
    141 | 146 |
    147 | 148 | 149 |
    150 | 153 | 154 |
    155 |

    Site built with pkgdown 1.5.1.

    156 |
    157 | 158 |
    159 |
    160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/reference/confl_macro_excerpt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Generate Confluence macro for an excerpt block — confl_macro_excerpt • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    Generate Confluence macro for an excerpt block

    121 |
    122 | 123 |
    confl_macro_excerpt(body, hidden = TRUE)
    124 | 125 |

    Arguments

    126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
    body

    HTML content of the excerpt

    hidden

    if the body should be shown on the actual page

    137 | 138 |

    Value

    139 | 140 |

    HTML as string

    141 |

    References

    142 | 143 |

    https://confluence.atlassian.com/doc/excerpt-macro-148062.html

    144 | 145 |
    146 | 151 |
    152 | 153 | 154 |
    155 | 158 | 159 |
    160 |

    Site built with pkgdown 1.5.1.

    161 |
    162 | 163 |
    164 |
    165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | How to contribute to conflr project • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 105 | 106 | 107 | 108 |
    109 | 110 |
    111 |
    112 | 115 | 116 |
    117 | 118 |

    First of all, thank you so much for taking your time to contribute! conflr is not very different from any other open source projects you are aware of. It will be amazing if you could help us by doing any of the following:

    119 | 125 |
    126 |

    127 | Contributor license agreement

    128 |

    When you are sending a pull request and it’s a non-trivial change beyond fixing typos, please sign the ICLA (individual contributor license agreement). Please contact us if you need the CCLA (corporate contributor license agreement).

    129 |
    130 |
    131 |

    132 | Code of conduct

    133 |

    We expect contributors to follow our code of conduct.

    134 |
    135 |
    136 | 137 |
    138 | 139 | 144 | 145 |
    146 | 147 | 148 | 149 |
    150 | 153 | 154 |
    155 |

    Site built with pkgdown 1.5.1.

    156 |
    157 | 158 |
    159 |
    160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/reference/confl_macro_toc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Generate Confluence macro for dynamic Table of Contents — confl_macro_toc • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    Generate Confluence macro for dynamic Table of Contents

    121 |
    122 | 123 |
    confl_macro_toc(levels)
    124 | 125 |

    Arguments

    126 | 127 | 128 | 129 | 130 | 131 | 132 |
    levels

    max number of levels to show

    133 | 134 |

    Value

    135 | 136 |

    HTML as string

    137 |

    References

    138 | 139 |

    https://confluence.atlassian.com/doc/table-of-contents-macro-182682099.html

    140 | 141 |

    Examples

    142 |
    confl_macro_toc(2)
    #> [1] "\n```{=html}\n<ac:structured-macro ac:name=\"toc\"><ac:parameter ac:name=\"levels\">2</ac:parameter></ac:structured-macro>\n```\n"
    143 |
    144 | 149 |
    150 | 151 | 152 |
    153 | 156 | 157 |
    158 |

    Site built with pkgdown 1.5.1.

    159 |
    160 | 161 |
    162 |
    163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/reference/conf_macro_generator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | General Confluence macro builder for internal use — conf_macro_generator • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    General Confluence macro builder for internal use

    121 |
    122 | 123 |
    conf_macro_generator(
    124 |   type = c("inline", "block"),
    125 |   name,
    126 |   parameters = NULL,
    127 |   body = NULL
    128 | )
    129 | 130 |

    Arguments

    131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 |
    type

    inline or block code style to be used for the HTML content

    name

    Confluence macro name

    parameters

    named list of optional macro parameters

    body

    optional confl-ac-rich-text-body content

    150 | 151 |

    Value

    152 | 153 |

    HTML

    154 | 155 |
    156 | 161 |
    162 | 163 | 164 |
    165 | 168 | 169 |
    170 |

    Site built with pkgdown 1.5.1.

    171 |
    172 | 173 |
    174 |
    175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/reference/confl_user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Non-admin User Operations — confl_user • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    Non-admin User Operations

    121 |
    122 | 123 |
    confl_get_user(key = NULL, username = NULL, expand = NULL)
    124 | 
    125 | confl_get_current_user(expand = NULL)
    126 | 127 |

    Arguments

    128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 142 | 143 |
    key

    Userkey of the user to request from this resource.

    username

    Username of the user to request from this resource.

    expand

    A comma separated list of properties to expand. To refer the nested 141 | contents, use periods. (e.g. body.storage,history).

    144 | 145 |

    Value

    146 | 147 |

    The API response as a list.

    148 | 149 |

    Examples

    150 |
    if (FALSE) { 151 | # Get the information of the current user 152 | my_user <- confl_get_current_user() 153 | 154 | # Show display name 155 | my_user$displayName 156 | 157 | # Get the information of a user whose name is "user1" 158 | other_user <- confl_get_user(username = "user1") 159 | }
    160 |
    161 | 166 |
    167 | 168 | 169 |
    170 | 173 | 174 |
    175 |

    Site built with pkgdown 1.5.1.

    176 |
    177 | 178 |
    179 |
    180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /docs/reference/confl_contentbody.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Converts between content body representations — confl_contentbody • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    Converts between content body representations

    121 |
    122 | 123 |
    confl_contentbody_convert(
    124 |   x,
    125 |   from = c("wiki", "storage", "editor", "view", "export_view", "styled_view"),
    126 |   to = c("storage", "editor", "view", "export_view", "styled_view")
    127 | )
    128 | 129 |

    Arguments

    130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
    x

    The content body to convert.

    from

    The format to convert from.

    to

    The format to convert to.

    145 | 146 |

    Value

    147 | 148 |

    The API response as a list.

    149 |

    See also

    150 | 151 | 152 | 153 |

    Examples

    154 |
    if (FALSE) { 155 | # Convert to a Math macro 156 | confl_contentbody_convert("\\[1+1=2\\]") 157 | 158 | # Convert to an Expand macro 159 | confl_contentbody_convert("{expand}detail is here {expand}") 160 | }
    161 |
    162 | 167 |
    168 | 169 | 170 |
    171 | 174 | 175 |
    176 |

    Site built with pkgdown 1.5.1.

    177 |
    178 | 179 |
    180 |
    181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /docs/reference/confl_macro_expand.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Generate Confluence macro for an expand block — confl_macro_expand • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    Generate Confluence macro for an expand block

    121 |
    122 | 123 |
    confl_macro_expand(title, body)
    124 | 125 |

    Arguments

    126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
    title

    defines the text that appears next to the expand/collapse icon

    body

    this HTML content will be visible when someone clicks the macro title

    137 | 138 |

    Value

    139 | 140 |

    HTML as string

    141 |

    Note

    142 | 143 |

    content needs to be HTML, so look at commonmark::markdown_html, pander::pander and eg xtable for doing the conversion before passing to confluence_expand

    144 |

    References

    145 | 146 |

    https://confluence.atlassian.com/doc/expand-macro-223222352.html

    147 | 148 |

    Examples

    149 |
    if (FALSE) { 150 | confl_macro_expand( 151 | 'Example block', 152 | commonmark::markdown_html(pander::pander_return(list(a = list(b = 4), c = 2)))) 153 | }
    154 |
    155 | 160 |
    161 | 162 | 163 |
    164 | 167 | 168 |
    169 |

    Site built with pkgdown 1.5.1.

    170 |
    171 | 172 |
    173 |
    174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /R/document.R: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 LINE Corporation 2 | # 3 | # conflr is free software; you can redistribute it and/or modify it under the 4 | # terms of the GNU General Public License as published by the Free Software 5 | # Foundation, version 3. 6 | # 7 | # conflr is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 9 | # A PARTICULAR PURPOSE. See for more details. 10 | 11 | #' Publish R Markdown Document to 'Confluence' 12 | #' 13 | #' Knit and post a given R Markdown file to 'Confluence'. 14 | #' 15 | #' @param interactive 16 | #' If `FALSE`, shiny interface is not launched. 17 | #' @param title 18 | #' Title of the post. 19 | #' @param space_key 20 | #' The space key to find content under. 21 | #' @param parent_id 22 | #' The page ID of the parent pages. 23 | #' @param toc 24 | #' If `TRUE`, include a table of contents in the output. 25 | #' @param toc_depth 26 | #' The max level of headers to include in the table of contents. 27 | #' @param code_folding 28 | #' If `"hide"`, fold code blocks by default. 29 | #' @param update 30 | #' If `TRUE`, overwrite the existing page (if it exists). 31 | #' @param use_original_size 32 | #' If `TRUE`, use the original image sizes. 33 | #' @param supported_syntax_highlighting 34 | #' A named character vector of supported syntax highlighting other than default (e.g. `c(r = "r")`). 35 | #' @param minor_edit 36 | #' If `TRUE`, will mark the `update` as a minor edit not notifying watchers. 37 | #' 38 | #' @return 39 | #' `confluence_document()` returns an `rmarkdown_output_format` object. 40 | #' 41 | #' @inheritParams confl_content 42 | #' 43 | #' @details 44 | #' All options of `confluence_document()` can also be specified via the argument 45 | #' of `confl_create_post_from_Rmd`. If an option is specified on both, the one given 46 | #' as an argument will be used. 47 | #' 48 | #' ``` 49 | #' --- 50 | #' title: "title1" 51 | #' output: 52 | #' confluence_document: 53 | #' space_key: "space1" 54 | #' parent_id: 1234 55 | #' toc: TRUE 56 | #' toc_depth: 4 57 | #' code_folding: hide 58 | #' supported_syntax_highlighting: 59 | #' r: r 60 | #' foo: bar 61 | #' update: true 62 | #' use_original_size: true 63 | #' minor_edit: false 64 | #' --- 65 | #' 66 | #' ... 67 | #' ``` 68 | #' 69 | #' @rdname confluence_document 70 | #' 71 | #' @examples 72 | #' \dontrun{ 73 | #' # A custom R markdown format that can be passed to rmarkdown::render() 74 | #' format <- confluence_document(space_key = "space1") 75 | #' rmarkdown::render(system.file("extdata/example.Rmd", package = "conflr"), format) 76 | #' } 77 | #' 78 | #' @export 79 | confluence_document <- function(title = NULL, 80 | # Use snake case for user-facing functions and use the actual API parameter name 81 | # in camel case for simple binding functions. 82 | space_key = NULL, 83 | parent_id = NULL, 84 | type = c("page", "blogpost"), 85 | toc = FALSE, 86 | toc_depth = 7, 87 | code_folding = c("none", "hide"), 88 | supported_syntax_highlighting = getOption("conflr_supported_syntax_highlighting"), 89 | update = NULL, 90 | use_original_size = FALSE, 91 | minor_edit = FALSE, 92 | interactive = NULL) { 93 | if (is.null(interactive)) { 94 | interactive <- interactive() 95 | } 96 | 97 | type <- arg_match(type) 98 | code_folding <- arg_match(code_folding) 99 | 100 | # This will be refered in post_processor() 101 | confluence_settings <- list( 102 | title = title, 103 | space_key = space_key, 104 | parent_id = parent_id, 105 | type = type, 106 | toc = toc, 107 | toc_depth = toc_depth, 108 | code_folding = code_folding, 109 | supported_syntax_highlighting = supported_syntax_highlighting, 110 | update = update, 111 | use_original_size = use_original_size, 112 | minor_edit = minor_edit 113 | ) 114 | 115 | format <- rmarkdown::md_document( 116 | variant = "commonmark", 117 | pandoc_args = "--wrap=none", 118 | md_extensions = "-tex_math_single_backslash-tex_math_dollars-raw_tex", 119 | preserve_yaml = FALSE 120 | ) 121 | 122 | knitr::opts_chunk$set( 123 | # NOTE: Usually, Confluence doesn't support syntax highlighting for R, which 124 | # makes it harder to distinguish the code block and the result block. So, 125 | # by default, collapse code and the result. But, if code_folding is enabled, 126 | # do not collapse because folded code blocks can be easily distinguished. 127 | collapse = !identical(confluence_settings$code_folding, "hide"), 128 | comment = "#>" 129 | ) 130 | 131 | username <- NULL 132 | 133 | format$pre_knit <- function(input_file) { 134 | # confirm the username and password are valid (and username will be useful later). 135 | tryCatch( 136 | username <<- confl_get_current_user()$username, 137 | error = function(e) { 138 | if (stringi::stri_detect_fixed(as.character(e), "Unauthorized (HTTP 401)")) { 139 | abort("Invalid credentials!") 140 | } else { 141 | cnd_signal(e) 142 | } 143 | } 144 | ) 145 | } 146 | 147 | format$pre_processor <- function(metadata, input_file, ...) { 148 | md_text_orig <- read_utf8(input_file) 149 | md_text <- wrap_tabsets(md_text_orig) 150 | 151 | if (!identical(md_text, md_text_orig)) { 152 | write_utf8(md_text, input_file) 153 | } 154 | 155 | NULL 156 | } 157 | 158 | format$post_processor <- function(front_matter, input_file, output_file, clean, verbose) { 159 | # For backward-compatibility 160 | if (has_name(front_matter, "confluence_settings")) { 161 | warn(paste0( 162 | "Set options via `confluence_settings` front-matter is deprecated and not fully supported.\n", 163 | "Please use `confluence_document` instead." 164 | )) 165 | 166 | # Dirty tweak to pass the default values. Note that, we no longer have 167 | # track on which arguments are defaults and which are supplied here. So, 168 | # the arguments are simply ignored for simplicity. 169 | defaults <- purrr::map(formals(confluence_document), eval_bare) 170 | # type needs to be arg_match()ed, but let's shortcut. 171 | defaults$type <- "page" 172 | defaults$code_folding <- "none" 173 | 174 | confluence_settings <- purrr::list_modify( 175 | defaults, 176 | !!!front_matter$confluence_settings # Overwrite the defaults by confluence_settings 177 | ) 178 | } 179 | 180 | # title can be specified as a seperate item on front matter 181 | confluence_settings$title <- confluence_settings$title %||% front_matter$title 182 | 183 | md_text <- read_utf8(output_file) 184 | 185 | # Replace and because they are not recognized as proper tags 186 | # by commonmark and accordingly get escaped. We need to replace the namespace 187 | # to bypass the unwanted conversions. The tags will be restored later in 188 | # confl_upload(). 189 | md_text <- mark_confluence_namespaces(md_text) 190 | 191 | html_text <- commonmark::markdown_html(md_text) 192 | 193 | imgs <- extract_image_paths(html_text) 194 | imgs_realpath <- curl::curl_unescape(imgs) 195 | 196 | # upload ------------------------------------------------------------------ 197 | 198 | if (interactive) { 199 | # On some Confluence, the key of a personal space can be guessed from the username 200 | if (is.null(confluence_settings$space_key)) { 201 | confluence_settings$space_key <- try_get_personal_space_key(username) 202 | } 203 | 204 | # Remove unused arguments 205 | confluence_settings$update <- NULL 206 | 207 | result_url <- exec( 208 | confl_upload_interactively, 209 | !!!confluence_settings, 210 | html_text = html_text, 211 | imgs = imgs, 212 | imgs_realpath = imgs_realpath 213 | ) 214 | } else { 215 | result_url <- exec( 216 | confl_upload, 217 | !!!confluence_settings, 218 | html_text = html_text, 219 | imgs = imgs, 220 | imgs_realpath = imgs_realpath 221 | ) 222 | } 223 | 224 | cat(result_url, file = paste0(output_file, "_result_url")) 225 | 226 | output_file 227 | } 228 | 229 | format 230 | } 231 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | position: relative; 22 | } 23 | 24 | body > .container { 25 | display: flex; 26 | height: 100%; 27 | flex-direction: column; 28 | } 29 | 30 | body > .container .row { 31 | flex: 1 0 auto; 32 | } 33 | 34 | footer { 35 | margin-top: 45px; 36 | padding: 35px 0 36px; 37 | border-top: 1px solid #e5e5e5; 38 | color: #666; 39 | display: flex; 40 | flex-shrink: 0; 41 | } 42 | footer p { 43 | margin-bottom: 0; 44 | } 45 | footer div { 46 | flex: 1; 47 | } 48 | footer .pkgdown { 49 | text-align: right; 50 | } 51 | footer p { 52 | margin-bottom: 0; 53 | } 54 | 55 | img.icon { 56 | float: right; 57 | } 58 | 59 | img { 60 | max-width: 100%; 61 | } 62 | 63 | /* Fix bug in bootstrap (only seen in firefox) */ 64 | summary { 65 | display: list-item; 66 | } 67 | 68 | /* Typographic tweaking ---------------------------------*/ 69 | 70 | .contents .page-header { 71 | margin-top: calc(-60px + 1em); 72 | } 73 | 74 | dd { 75 | margin-left: 3em; 76 | } 77 | 78 | /* Section anchors ---------------------------------*/ 79 | 80 | a.anchor { 81 | margin-left: -30px; 82 | display:inline-block; 83 | width: 30px; 84 | height: 30px; 85 | visibility: hidden; 86 | 87 | background-image: url(./link.svg); 88 | background-repeat: no-repeat; 89 | background-size: 20px 20px; 90 | background-position: center center; 91 | } 92 | 93 | .hasAnchor:hover a.anchor { 94 | visibility: visible; 95 | } 96 | 97 | @media (max-width: 767px) { 98 | .hasAnchor:hover a.anchor { 99 | visibility: hidden; 100 | } 101 | } 102 | 103 | 104 | /* Fixes for fixed navbar --------------------------*/ 105 | 106 | .contents h1, .contents h2, .contents h3, .contents h4 { 107 | padding-top: 60px; 108 | margin-top: -40px; 109 | } 110 | 111 | /* Navbar submenu --------------------------*/ 112 | 113 | .dropdown-submenu { 114 | position: relative; 115 | } 116 | 117 | .dropdown-submenu>.dropdown-menu { 118 | top: 0; 119 | left: 100%; 120 | margin-top: -6px; 121 | margin-left: -1px; 122 | border-radius: 0 6px 6px 6px; 123 | } 124 | 125 | .dropdown-submenu:hover>.dropdown-menu { 126 | display: block; 127 | } 128 | 129 | .dropdown-submenu>a:after { 130 | display: block; 131 | content: " "; 132 | float: right; 133 | width: 0; 134 | height: 0; 135 | border-color: transparent; 136 | border-style: solid; 137 | border-width: 5px 0 5px 5px; 138 | border-left-color: #cccccc; 139 | margin-top: 5px; 140 | margin-right: -10px; 141 | } 142 | 143 | .dropdown-submenu:hover>a:after { 144 | border-left-color: #ffffff; 145 | } 146 | 147 | .dropdown-submenu.pull-left { 148 | float: none; 149 | } 150 | 151 | .dropdown-submenu.pull-left>.dropdown-menu { 152 | left: -100%; 153 | margin-left: 10px; 154 | border-radius: 6px 0 6px 6px; 155 | } 156 | 157 | /* Sidebar --------------------------*/ 158 | 159 | #pkgdown-sidebar { 160 | margin-top: 30px; 161 | position: -webkit-sticky; 162 | position: sticky; 163 | top: 70px; 164 | } 165 | 166 | #pkgdown-sidebar h2 { 167 | font-size: 1.5em; 168 | margin-top: 1em; 169 | } 170 | 171 | #pkgdown-sidebar h2:first-child { 172 | margin-top: 0; 173 | } 174 | 175 | #pkgdown-sidebar .list-unstyled li { 176 | margin-bottom: 0.5em; 177 | } 178 | 179 | /* bootstrap-toc tweaks ------------------------------------------------------*/ 180 | 181 | /* All levels of nav */ 182 | 183 | nav[data-toggle='toc'] .nav > li > a { 184 | padding: 4px 20px 4px 6px; 185 | font-size: 1.5rem; 186 | font-weight: 400; 187 | color: inherit; 188 | } 189 | 190 | nav[data-toggle='toc'] .nav > li > a:hover, 191 | nav[data-toggle='toc'] .nav > li > a:focus { 192 | padding-left: 5px; 193 | color: inherit; 194 | border-left: 1px solid #878787; 195 | } 196 | 197 | nav[data-toggle='toc'] .nav > .active > a, 198 | nav[data-toggle='toc'] .nav > .active:hover > a, 199 | nav[data-toggle='toc'] .nav > .active:focus > a { 200 | padding-left: 5px; 201 | font-size: 1.5rem; 202 | font-weight: 400; 203 | color: inherit; 204 | border-left: 2px solid #878787; 205 | } 206 | 207 | /* Nav: second level (shown on .active) */ 208 | 209 | nav[data-toggle='toc'] .nav .nav { 210 | display: none; /* Hide by default, but at >768px, show it */ 211 | padding-bottom: 10px; 212 | } 213 | 214 | nav[data-toggle='toc'] .nav .nav > li > a { 215 | padding-left: 16px; 216 | font-size: 1.35rem; 217 | } 218 | 219 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 220 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 221 | padding-left: 15px; 222 | } 223 | 224 | nav[data-toggle='toc'] .nav .nav > .active > a, 225 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 226 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 227 | padding-left: 15px; 228 | font-weight: 500; 229 | font-size: 1.35rem; 230 | } 231 | 232 | /* orcid ------------------------------------------------------------------- */ 233 | 234 | .orcid { 235 | font-size: 16px; 236 | color: #A6CE39; 237 | /* margins are required by official ORCID trademark and display guidelines */ 238 | margin-left:4px; 239 | margin-right:4px; 240 | vertical-align: middle; 241 | } 242 | 243 | /* Reference index & topics ----------------------------------------------- */ 244 | 245 | .ref-index th {font-weight: normal;} 246 | 247 | .ref-index td {vertical-align: top;} 248 | .ref-index .icon {width: 40px;} 249 | .ref-index .alias {width: 40%;} 250 | .ref-index-icons .alias {width: calc(40% - 40px);} 251 | .ref-index .title {width: 60%;} 252 | 253 | .ref-arguments th {text-align: right; padding-right: 10px;} 254 | .ref-arguments th, .ref-arguments td {vertical-align: top;} 255 | .ref-arguments .name {width: 20%;} 256 | .ref-arguments .desc {width: 80%;} 257 | 258 | /* Nice scrolling for wide elements --------------------------------------- */ 259 | 260 | table { 261 | display: block; 262 | overflow: auto; 263 | } 264 | 265 | /* Syntax highlighting ---------------------------------------------------- */ 266 | 267 | pre { 268 | word-wrap: normal; 269 | word-break: normal; 270 | border: 1px solid #eee; 271 | } 272 | 273 | pre, code { 274 | background-color: #f8f8f8; 275 | color: #333; 276 | } 277 | 278 | pre code { 279 | overflow: auto; 280 | word-wrap: normal; 281 | white-space: pre; 282 | } 283 | 284 | pre .img { 285 | margin: 5px 0; 286 | } 287 | 288 | pre .img img { 289 | background-color: #fff; 290 | display: block; 291 | height: auto; 292 | } 293 | 294 | code a, pre a { 295 | color: #375f84; 296 | } 297 | 298 | a.sourceLine:hover { 299 | text-decoration: none; 300 | } 301 | 302 | .fl {color: #1514b5;} 303 | .fu {color: #000000;} /* function */ 304 | .ch,.st {color: #036a07;} /* string */ 305 | .kw {color: #264D66;} /* keyword */ 306 | .co {color: #888888;} /* comment */ 307 | 308 | .message { color: black; font-weight: bolder;} 309 | .error { color: orange; font-weight: bolder;} 310 | .warning { color: #6A0366; font-weight: bolder;} 311 | 312 | /* Clipboard --------------------------*/ 313 | 314 | .hasCopyButton { 315 | position: relative; 316 | } 317 | 318 | .btn-copy-ex { 319 | position: absolute; 320 | right: 0; 321 | top: 0; 322 | visibility: hidden; 323 | } 324 | 325 | .hasCopyButton:hover button.btn-copy-ex { 326 | visibility: visible; 327 | } 328 | 329 | /* headroom.js ------------------------ */ 330 | 331 | .headroom { 332 | will-change: transform; 333 | transition: transform 200ms linear; 334 | } 335 | .headroom--pinned { 336 | transform: translateY(0%); 337 | } 338 | .headroom--unpinned { 339 | transform: translateY(-100%); 340 | } 341 | 342 | /* mark.js ----------------------------*/ 343 | 344 | mark { 345 | background-color: rgba(255, 255, 51, 0.5); 346 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 347 | padding: 1px; 348 | } 349 | 350 | /* vertical spacing after htmlwidgets */ 351 | .html-widget { 352 | margin-bottom: 10px; 353 | } 354 | 355 | /* fontawesome ------------------------ */ 356 | 357 | .fab { 358 | font-family: "Font Awesome 5 Brands" !important; 359 | } 360 | 361 | /* don't display links in code chunks when printing */ 362 | /* source: https://stackoverflow.com/a/10781533 */ 363 | @media print { 364 | code a:link:after, code a:visited:after { 365 | content: ""; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /docs/reference/confl_space.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | REST Wrapper for the SpaceService — confl_space • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 106 | 107 | 108 | 109 |
    110 | 111 |
    112 |
    113 | 118 | 119 |
    120 |

    REST Wrapper for the SpaceService

    121 |
    122 | 123 |
    confl_list_spaces(
    124 |   spaceKey = NULL,
    125 |   type = c("global", "personal"),
    126 |   status = c("current", "archived"),
    127 |   label = NULL,
    128 |   favourite = NULL,
    129 |   expand = NULL,
    130 |   start = NULL,
    131 |   limit = 25
    132 | )
    133 | 
    134 | confl_get_space(spaceKey, expand = NULL)
    135 | 136 |

    Arguments

    137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 |
    spaceKey

    The space key to find content under.

    type

    Filter the list of spaces returned by type (global, personal).

    status

    Filter the list of spaces returned by status (current, archived).

    label

    Filter the list of spaces returned by label.

    favourite

    Filter the list of spaces returned by favourites.

    expand

    A comma separated list of properties to expand. To refer the nested 162 | contents, use periods. (e.g. body.storage,history).

    start

    The start point of the collection to return.

    limit

    The limit of the number of items to return, this may be restricted by fixed system limits.

    173 | 174 |

    Value

    175 | 176 |

    The API response as a list.

    177 | 178 |

    Examples

    179 |
    if (FALSE) { 180 | # Get the information of a space named "space1" 181 | confl_get_space("space1") 182 | }
    183 |
    184 | 189 |
    190 | 191 | 192 |
    193 | 196 | 197 |
    198 |

    Site built with pkgdown 1.5.1.

    199 |
    200 | 201 |
    202 |
    203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /docs/reference/confl_create_post_from_Rmd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Publish R Markdown Document to 'Confluence' — confl_create_post_from_Rmd • conflr 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 | 103 | 104 | 105 | 106 |
    107 | 108 |
    109 |
    110 | 115 | 116 |
    117 |

    Knit and post a given R Markdown file to 'Confluence'.

    118 |
    119 | 120 |
    confl_create_post_from_Rmd(
    121 |   Rmd_file,
    122 |   interactive = NULL,
    123 |   params = NULL,
    124 |   ...,
    125 |   title = NULL,
    126 |   space_key = NULL,
    127 |   type = NULL,
    128 |   parent_id = NULL,
    129 |   toc = NULL,
    130 |   update = NULL,
    131 |   use_original_size = NULL
    132 | )
    133 | 134 |

    Arguments

    135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 |
    Rmd_file

    Path to an .Rmd file.

    interactive

    If FALSE, shiny interface is not launched.

    params

    If provided, a list of named parameters that override custom 148 | params in the YAML front-matter.

    ...

    Ignored.

    title

    Title of the post.

    space_key

    The space key to find content under.

    type

    If provided, this overwrites the YAML front matter type

    parent_id

    The page ID of the parent pages.

    toc

    If TRUE, add TOC.

    update

    If TRUE, overwrite the existing page (if it exists).

    use_original_size

    If TRUE, use the original image sizes.

    183 | 184 |

    Details

    185 | 186 |

    title, type, space_key, parent_id, toc, update, and 187 | use_original_size can be specified as confluence_settings item in the 188 | front-matter of the Rmd file to knit. The arguments of 189 | confl_create_post_from_Rmd() overwrite these settings if provided.

    190 | 191 |
    192 | 200 |
    201 | 202 | 203 | 213 |
    214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | --------------------------------------------------------------------------------