├── .gitignore ├── .quartoignore ├── README.md ├── _extensions └── highlightword │ ├── _extension.yml │ └── highlightword.js ├── example.gif ├── example.qmd └── quarto-revealjs-highlightword.Rproj /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *_files/ 3 | .Rproj.user 4 | .Rhistory 5 | .Rdata 6 | .httr-oauth 7 | .DS_Store 8 | .quarto 9 | -------------------------------------------------------------------------------- /.quartoignore: -------------------------------------------------------------------------------- 1 | example.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # highlightword Extension For Quarto 2 | 3 | Revealjs plugin to highlight specific parts of code. 4 | 5 | ![](example.gif) 6 | 7 | I don't intend to add more functionality to this project other than fixing bugs. 8 | 9 | There is a need for a more general highlighting plugin that works across multiple words (this plugin doesn't allow you to highlight two parts of code that is styled differently by the highligter.) 10 | 11 | ## Installing 12 | 13 | ```bash 14 | quarto add emilhvitfeldt/quarto-revealjs-highlightword 15 | ``` 16 | 17 | This will install the extension under the `_extensions` subdirectory. 18 | If you're using version control, you will want to check in this directory. 19 | 20 | Once an extension has been added, you can use the Reveal plugin by adding it to the `reveal-plugins` key. For example: 21 | 22 | ````` markdown 23 | --- 24 | title: "My Presentation" 25 | format: revealjs 26 | revealjs-plugins: 27 | - highlightword 28 | --- 29 | ````` 30 | 31 | ## Using 32 | 33 | Adding a fenced with with `.fragment .highlightword` and the `word` you need, plus what any valid CSS `style` you want applied. Additional arguments `number` and `chunk` changes which match should be highlighted. See examples for more detail. 34 | 35 | ## Example 36 | 37 | Here is the source code for a minimal example: [example.qmd](example.qmd). 38 | 39 | -------------------------------------------------------------------------------- /_extensions/highlightword/_extension.yml: -------------------------------------------------------------------------------- 1 | title: highlightword 2 | author: Emil Hvitfeldt 3 | version: 1.0.1 4 | quarto-required: ">=1.4.0" 5 | contributes: 6 | revealjs-plugins: 7 | - name: RevealHighlightword 8 | script: 9 | - highlightword.js 10 | -------------------------------------------------------------------------------- /_extensions/highlightword/highlightword.js: -------------------------------------------------------------------------------- 1 | window.RevealHighlightword= function () { 2 | return { 3 | id: "RevealHighlightword", 4 | init: function(deck) { 5 | initHighlightword(deck); 6 | } 7 | }; 8 | }; 9 | 10 | function replaceOccurrence(string, regex, n, replace) { 11 | var i = 0; 12 | return string.replace(regex, function(match) { 13 | i+=1; 14 | if(i===n) return replace; 15 | return match; 16 | }); 17 | } 18 | 19 | const highlight_apply = function(fragment) { 20 | if (fragment.classList.contains("highlightword")) { 21 | var chunk_id = 0 22 | if (fragment.dataset.chunk !== undefined) { 23 | chunk_id = fragment.dataset.chunk - 1; 24 | } 25 | var chunk = Reveal.getCurrentSlide().querySelectorAll("code.sourceCode")[chunk_id] 26 | 27 | word = fragment.dataset.word; 28 | if (word === undefined) { 29 | return 30 | } 31 | 32 | replacement = document.createElement("span"); 33 | replacement.innerText = word 34 | replacement.style.cssText = fragment.style.cssText; 35 | 36 | var number = 1 37 | if (fragment.dataset.number !== undefined) { 38 | number = Number(fragment.dataset.number); 39 | } 40 | 41 | let t = 0; 42 | chunk.innerHTML = chunk.innerHTML.replaceAll( 43 | word, 44 | match => ++t === number ? replacement.outerHTML : match 45 | ); 46 | } 47 | } 48 | 49 | const highlight_reverse = function(fragment) { 50 | if (fragment.classList.contains("highlightword")) { 51 | var chunk_id = 0 52 | if (fragment.dataset.chunk !== undefined) { 53 | chunk_id = fragment.dataset.chunk - 1; 54 | } 55 | var chunk = Reveal.getCurrentSlide().querySelectorAll("code.sourceCode")[chunk_id] 56 | 57 | word = fragment.dataset.word; 58 | if (word === undefined) { 59 | return 60 | } 61 | 62 | replacement = document.createElement("span"); 63 | replacement.innerText = word 64 | replacement.style.cssText = fragment.style.cssText; 65 | 66 | let t = 0; 67 | chunk.innerHTML = chunk.innerHTML.replace( 68 | replacement.outerHTML, word 69 | ); 70 | } 71 | } 72 | 73 | const initHighlightword = function(window) { 74 | window.on( 'fragmentshown', event => { 75 | event.fragments.forEach(highlight_apply); 76 | }); 77 | 78 | window.on( 'fragmenthidden', event => { 79 | event.fragments.forEach(highlight_reverse); 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilHvitfeldt/quarto-revealjs-highlightword/090d87b9af7e6c5d0a9048513a06619300bf3228/example.gif -------------------------------------------------------------------------------- /example.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "highlightword Example" 3 | format: 4 | revealjs: 5 | footer: 6 | revealjs-plugins: 7 | - highlightword 8 | knitr: true 9 | code-overflow: wrap 10 | code-line-numbers: false 11 | --- 12 | 13 | ## Simple usage 14 | 15 | Adding a fenced with with `.fragment .highlightword` and the `word` you need, plus what any valid CSS `style` you want applied. 16 | The following div added to this slide 17 | 18 | ````` {.markdown style="font-size:0.75em;"} 19 | ::: {.fragment .highlightword word="LinearRegression()" style="background:yellow;"} 20 | ::: 21 | ````` 22 | 23 | ::: {.fragment .highlightword word="LinearRegression()" number=1 chunk=2 style="background:yellow;"} 24 | ::: 25 | 26 | will highlight like so when slides are advanced: 27 | 28 | ```{python} 29 | #| eval: false 30 | #| echo: true 31 | from vetiver import VetiverModel 32 | from vetiver.data import mtcars 33 | from sklearn.linear_model import LinearRegression 34 | 35 | model = LinearRegression().fit(mtcars.drop(columns="mpg"), mtcars["mpg"]) 36 | v = VetiverModel(model, model_name = "cars_linear", 37 | prototype_data = mtcars.drop(columns="mpg")) 38 | v.description 39 | ``` 40 | 41 | ## Number argument 42 | 43 | The first instance of the word will be matched by default. Set `number` argument to change that 44 | 45 | ````` {.markdown style="font-size:0.75em;"} 46 | ::: {.fragment .highlightword word="VetiverModel" number=2 style="background:yellow;"} 47 | ::: 48 | ````` 49 | 50 | ::: {.fragment .highlightword word="VetiverModel" number=2 chunk=2 style="background:yellow;"} 51 | ::: 52 | 53 | to have the second instance highlighted 54 | 55 | ```{python} 56 | #| eval: false 57 | #| echo: true 58 | from vetiver import VetiverModel 59 | from vetiver.data import mtcars 60 | from sklearn.linear_model import LinearRegression 61 | 62 | model = LinearRegression().fit(mtcars.drop(columns="mpg"), mtcars["mpg"]) 63 | v = VetiverModel(model, model_name = "cars_linear", 64 | prototype_data = mtcars.drop(columns="mpg")) 65 | v.description 66 | ``` 67 | 68 | 69 | ## chunk argument 70 | 71 | The first code chunk will be search by default. 72 | 73 | ````` {.markdown style="font-size:0.75em;"} 74 | ::: {.fragment .highlightword word="VetiverModel" chunk=2 style="background:yellow;"} 75 | ::: 76 | ````` 77 | 78 | ::: {.fragment .highlightword word="VetiverModel" chunk=3 style="background:yellow;"} 79 | ::: 80 | 81 | chunk 1: Set `chunk` argument to change that. 82 | 83 | ```{python} 84 | #| eval: false 85 | #| echo: true 86 | from vetiver import VetiverModel 87 | from vetiver.data import mtcars 88 | from sklearn.linear_model import LinearRegression 89 | ``` 90 | 91 | chunk 2: 92 | notice that we didn't set `number=2` since this is the first instance of the word in this chunk. 93 | 94 | ```{python} 95 | #| eval: false 96 | #| echo: true 97 | model = LinearRegression().fit(mtcars.drop(columns="mpg"), mtcars["mpg"]) 98 | v = VetiverModel(model, model_name = "cars_linear", 99 | prototype_data = mtcars.drop(columns="mpg")) 100 | v.description 101 | ``` 102 | 103 | ## fragments 104 | 105 | This highlighting is still [revealjs fragments](https://quarto.org/docs/presentations/revealjs/advanced.html#fragments), so can change the ordering as well 106 | 107 | ````` {.markdown style="font-size:0.75em;"} 108 | ::: {.fragment .highlightword fragment-index=1 word="VetiverModel" number=2 style="background:yellow;"} 109 | ::: 110 | 111 | ::: {.fragment .highlightword fragment-index=1 word="v.description" number=1 style="background:pink;"} 112 | ::: 113 | ````` 114 | 115 | ::: {.fragment .highlightword fragment-index=1 word="VetiverModel" number=2 chunk=2 style="background:yellow;"} 116 | ::: 117 | 118 | ::: {.fragment .highlightword fragment-index=1 word="v.description" number=1 chunk=2 style="background:pink;"} 119 | ::: 120 | 121 | To make things out of order, or the same time 122 | 123 | ```{python} 124 | #| eval: false 125 | #| echo: true 126 | from vetiver import VetiverModel 127 | from vetiver.data import mtcars 128 | from sklearn.linear_model import LinearRegression 129 | 130 | model = LinearRegression().fit(mtcars.drop(columns="mpg"), mtcars["mpg"]) 131 | v = VetiverModel(model, model_name = "cars_linear", 132 | prototype_data = mtcars.drop(columns="mpg")) 133 | v.description 134 | ``` 135 | -------------------------------------------------------------------------------- /quarto-revealjs-highlightword.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: Knitr 13 | LaTeX: pdfLaTeX 14 | --------------------------------------------------------------------------------