├── _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\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\n\n\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\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 \nh2 "
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 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
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 |
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 |
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 | key
130 | Jira ticket id, eg CONFLR-XXXX
131 |
132 |
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 |
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 | body
130 | HTML content of the excerpt
131 |
132 |
133 | hidden
134 | if the body should be shown on the actual page
135 |
136 |
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 |
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 |
130 |
135 |
136 |
137 |
138 |
139 |
144 |
145 |
146 |
147 |
148 |
149 |
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 | levels
130 | max number of levels to show
131 |
132 |
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 |
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 | type
135 | inline or block code style to be used for the HTML content
136 |
137 |
138 | name
139 | Confluence macro name
140 |
141 |
142 | parameters
143 | named list of optional macro parameters
144 |
145 |
146 | body
147 | optional confl-ac-rich-text-body content
148 |
149 |
150 |
151 |
Value
152 |
153 |
HTML
154 |
155 |
156 |
161 |
162 |
163 |
164 |
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 | key
132 | Userkey of the user to request from this resource.
133 |
134 |
135 | username
136 | Username of the user to request from this resource.
137 |
138 |
139 | expand
140 | A comma separated list of properties to expand. To refer the nested
141 | contents, use periods. (e.g. body.storage,history).
142 |
143 |
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 |
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 | x
134 | The content body to convert.
135 |
136 |
137 | from
138 | The format to convert from.
139 |
140 |
141 | to
142 | The format to convert to.
143 |
144 |
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 |
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 | title
130 | defines the text that appears next to the expand/collapse icon
131 |
132 |
133 | body
134 | this HTML content will be visible when someone clicks the macro title
135 |
136 |
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 |
154 |
155 |
160 |
161 |
162 |
163 |
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 | spaceKey
141 | The space key to find content under.
142 |
143 |
144 | type
145 | Filter the list of spaces returned by type (global, personal).
146 |
147 |
148 | status
149 | Filter the list of spaces returned by status (current, archived).
150 |
151 |
152 | label
153 | Filter the list of spaces returned by label.
154 |
155 |
156 | favourite
157 | Filter the list of spaces returned by favourites.
158 |
159 |
160 | expand
161 | A comma separated list of properties to expand. To refer the nested
162 | contents, use periods. (e.g. body.storage,history).
163 |
164 |
165 | start
166 | The start point of the collection to return.
167 |
168 |
169 | limit
170 | The limit of the number of items to return, this may be restricted by fixed system limits.
171 |
172 |
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 |
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 | Rmd_file
139 | Path to an .Rmd file.
140 |
141 |
142 | interactive
143 | If FALSE, shiny interface is not launched.
144 |
145 |
146 | params
147 | If provided, a list of named parameters that override custom
148 | params in the YAML front-matter.
149 |
150 |
151 | ...
152 | Ignored.
153 |
154 |
155 | title
156 | Title of the post.
157 |
158 |
159 | space_key
160 | The space key to find content under.
161 |
162 |
163 | type
164 | If provided, this overwrites the YAML front matter type
165 |
166 |
167 | parent_id
168 | The page ID of the parent pages.
169 |
170 |
171 | toc
172 | If TRUE, add TOC.
173 |
174 |
175 | update
176 | If TRUE, overwrite the existing page (if it exists).
177 |
178 |
179 | use_original_size
180 | If TRUE, use the original image sizes.
181 |
182 |
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 |
--------------------------------------------------------------------------------