A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Split up about into multiple pages

+305 -288
+3 -1
src/Applications/UI/Kit.elm
··· 387 387 link params = 388 388 slab 389 389 Html.a 390 - [ href params.url ] 390 + [ A.href params.url 391 + , A.target "_blank" 392 + ] 391 393 [ C.border_b_2 392 394 , C.border_base04 393 395 , C.inline_block
+1 -1
src/Applications/UI/Sources/Form.elm
··· 292 292 , chunk 293 293 [ C.text_sm, C.leading_snug, C.mb_8, C.mt_1, C.opacity_50 ] 294 294 [ text "You can find the instructions over " 295 - , UI.Kit.link { label = "here", url = "about#" ++ id } 295 + , UI.Kit.link { label = "here", url = "about/cors#" ++ id } 296 296 ] 297 297 ] 298 298
-266
src/Static/About/About.md
··· 1 - > A music player that connects to your cloud &amp; distributed storage 2 - 3 - [Return to the application](../) 4 - 5 - 6 - 7 - ## What makes it different? 8 - 9 - Diffuse is a decentralized music player consisting out of two main parts. One part is the music and the other is your data <small>(eg. playlists)</small>, both of which are in locations of your choice. Meaning that there's no central server for Diffuse, all of the processing happens on your device and all the data is in your control. You can use the [web version](https://diffuse.sh), the [native version](https://github.com/icidasset/diffuse/releases) or host it yourself by downloading the pre-built packages from [Github](https://github.com/icidasset/diffuse). 10 - 11 - 12 - ### Music layer 13 - 14 - This layer connects to the services on which your music is stored, no data is written to these services. You can combine all of the following: 15 - 16 - - [Amazon S3](https://aws.amazon.com/s3/) 17 - - [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) 18 - - [Azure File Storage](https://azure.microsoft.com/en-us/services/storage/files/) 19 - - [Dropbox](https://dropbox.com/) 20 - - [Google Drive](https://drive.google.com/) 21 - - [IPFS](https://ipfs.io/) <small>(supports DNSLink & IPNS)</small> 22 - - [WebDAV](https://en.wikipedia.org/wiki/WebDAV) 23 - 24 - 25 - ### User layer 26 - 27 - This layer will use a single service on which to store your data. Your data being your settings, favourites, playlists, etc. You can choose between these services: 28 - 29 - - [Dropbox](https://www.dropbox.com/) 30 - - [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) <small>(Browser)</small> 31 - - [IPFS](https://ipfs.io/) <small>(using MFS)</small> 32 - - [RemoteStorage](https://remotestorage.io/) 33 - 34 - 35 - 36 - <div id="How" /> 37 - 38 - ## How does it work? 39 - 40 - Diffuse locates all the music files on the given services, extracts the metadata and then stores it via the previously-explained user layer. 41 - 42 - 43 - ### Supported File Formats 44 - 45 - - MP3 46 - - MP4/M4A 47 - - FLAC 48 - - OGG 49 - - WAV 50 - - WEBM 51 - 52 - <small><em>Note, support may vary depending on your <a href="https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers#Index_of_media_container_formats_file_types">browser</a>.</em></small> 53 - 54 - 55 - <div id="CORS" /> 56 - 57 - ### CORS 58 - 59 - There's only one thing you need to do yourself so that the service you chose will work with the application, and that's setting up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross-Origin Resource Sharing). Here are the instructions you'll need for each service: 60 - 61 - <div id="CORS__S3" /> 62 - 63 - #### Amazon S3 64 - 65 - You can find the CORS configuration editor under the "Permissions" tab, on the S3 AWS Console. 66 - 67 - ```xml 68 - <?xml version="1.0" encoding="UTF-8"?> 69 - <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 70 - <CORSRule> 71 - <AllowedOrigin>*</AllowedOrigin> 72 - <AllowedMethod>GET</AllowedMethod> 73 - <AllowedMethod>HEAD</AllowedMethod> 74 - <MaxAgeSeconds>31536000</MaxAgeSeconds> 75 - <ExposeHeader>Content-Length</ExposeHeader> 76 - <ExposeHeader>Content-Type</ExposeHeader> 77 - <AllowedHeader>Range</AllowedHeader> 78 - </CORSRule> 79 - </CORSConfiguration> 80 - ``` 81 - 82 - <div id="CORS__BTFS" /> 83 - 84 - #### BTFS 85 - 86 - Add the domain of the app, with the protocol, to the __list of allowed origins__. 87 - 88 - ```shell 89 - btfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["https://diffuse.sh", "http://diffuse.sh.ipns.localhost:8080", "http://127.0.0.1:44999"]' 90 - ``` 91 - 92 - You can also make this change in the Web UI, you'll find it under "Settings → BTFS Config". 93 - 94 - ```javascript 95 - { 96 - "API": { 97 - "HTTPHeaders": { 98 - "Access-Control-Allow-Origin": [ 99 - "https://diffuse.sh", // 🎵 Default 100 - "http://diffuse.sh.ipns.localhost:8080", // IPNS 101 - "http://127.0.0.1:44999" // Electron app 102 - ] 103 - } 104 - } 105 - } 106 - ``` 107 - 108 - <div id="CORS__Dropbox" /> 109 - 110 - #### Dropbox 111 - 112 - _Not necessary._ 113 - 114 - <div id="CORS__Google-Drive" /> 115 - 116 - #### Google Drive 117 - 118 - _Not necessary._ 119 - 120 - <div id="CORS__IPFS" /> 121 - 122 - #### IPFS 123 - 124 - Add the domain of the app, with the protocol, to the __list of allowed origins__. 125 - 126 - ```shell 127 - ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["https://diffuse.sh", "http://diffuse.sh.ipns.localhost:8080", "http://127.0.0.1:44999"]' 128 - ``` 129 - 130 - You can also make this change in the Web UI, you'll find it under "Settings → IPFS Config". 131 - 132 - ```javascript 133 - { 134 - "API": { 135 - "HTTPHeaders": { 136 - "Access-Control-Allow-Origin": [ 137 - "https://diffuse.sh", // 🎵 Default 138 - "http://diffuse.sh.ipns.localhost:8080", // IPNS through IPFS Companion 139 - "http://127.0.0.1:44999" // Electron app 140 - ] 141 - } 142 - } 143 - } 144 - ``` 145 - 146 - <div id="CORS__Azure" /> 147 - 148 - #### Microsoft Azure Storage 149 - 150 - You can find the CORS configuration under the "Settings -> CORS". 151 - Then fill in the following in the input boxes (left to right): 152 - 153 - ``` 154 - ALLOWED ORIGINS * 155 - ALLOWED METHODS GET, HEAD 156 - ALLOWED HEADERS Range 157 - EXPOSED HEADERS Content-Length, Content-Range 158 - MAX AGE 0 159 - ``` 160 - 161 - <div id="CORS__WebDAV" /> 162 - 163 - #### WebDAV 164 - 165 - __Depends on your WebDAV server.__ 166 - Example setup for Henrique Dias's [WebDAV server](https://github.com/hacdias/webdav): 167 - 168 - ```yaml 169 - cors: 170 - enabled: true 171 - credentials: true 172 - 173 - allowed_headers: 174 - - Authorization 175 - - Content-Type 176 - - Depth 177 - - Range 178 - allowed_methods: 179 - - GET 180 - - HEAD 181 - - PROPFIND 182 - allowed_hosts: 183 - - https://diffuse.sh 184 - - http://127.0.0.1:44999 185 - exposed_headers: 186 - - Content-Length 187 - - Content-Type 188 - ``` 189 - 190 - 191 - 192 - <div id="UI" /> 193 - 194 - ## UI 195 - 196 - There are a few "hidden" features: 197 - 198 - - **Tracks have a context menu** which can be opened by either right clicking, 199 - or holding it (ie. a long tap). Use the ALT key whilst right clicking 200 - on a track to show the alternative track-context menu with more specialized options. 201 - - **You can reorder items** in the queue or a playlist with drag-and-drop. 202 - Select the item you want to move by tapping on it, then tap and hold to move it around. 203 - - You can select multiple tracks using the SHIFT key and then add that selection 204 - to the queue or a playlist using the context menu. 205 - - Click on the now-playing bit to scroll to that track. 206 - - Double tap on a EQ setting to reset it to its default value. 207 - 208 - ### Playlists 209 - 210 - To add something to a playlist, and create that playlist if it doesn't exist yet, you open the context menu of a track. To move tracks around in a playlist, first select the track, then drag it. 211 - 212 - ### Search 213 - 214 - ```shell 215 - # Show me every track where the title, artist or album contains the term 'Parkway' and the term 'Drive'. Terms are separated by spaces (eg. "Killing with a smile" has four terms). 216 - Parkway Drive 217 - 218 - # Show me every track of which the artist's name contains 'park'. 219 - artist:park* 220 - 221 - # Show me every track from Parkway Drive's "Deep Blue" album. 222 - artist:Parkway Drive album:Deep Blue 223 - 224 - # Show me every track from Parkway Drive but not their "Atlas" album. 225 - artist:Parkway Drive - album:Atlas 226 - ``` 227 - 228 - ### Keyboard 229 - 230 - The app should be usable with only the keyboard, there are various keyboard shortcuts: 231 - 232 - ``` 233 - L - Select playlist using autocompletion 234 - N - Scroll to currently-playing track 235 - P - Play / Pause 236 - R - Toggle Repeat 237 - S - Toggle Shuffle 238 - 239 - { / } - Previous / Next 240 - < / > - Seek forwards / Seek backwards 241 - 242 - Alternatively you can use the media-control keys, 243 - if your browser supports it. 244 - 245 - ESC - Close overlay, close context-menu, deselect album cover, etc. 246 - 247 - 1 - Tracks 248 - 2 - Playlists 249 - 3 - Queue 250 - 4 - EQ 251 - 252 - 8 - Sources 253 - 9 - Settings 254 - ``` 255 - 256 - 257 - 258 - <div id="QA" /> 259 - 260 - ## Q&A 261 - 262 - ### I used version one, where's my data? 263 - 264 - There's a small link, or button if you will, on the "Settings -> Import / Export" 265 - page that will allow you to import data from version 1 of the app. Note that this 266 - will need to reflect the authentication/storage method you chose in version 1.
+141
src/Static/About/CORS.md
··· 1 + > A music player that connects to your cloud &amp; distributed storage 2 + 3 + [Return to the application](../../) 4 + [About](../) 5 + 6 + 7 + 8 + <div id="CORS" /> 9 + 10 + ### CORS 11 + 12 + There's only one thing you need to do yourself so that the service you chose will work with the application, and that's setting up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross-Origin Resource Sharing). Here are the instructions you'll need for each service: 13 + 14 + <div id="CORS__S3" /> 15 + 16 + #### Amazon S3 17 + 18 + You can find the CORS configuration editor under the "Permissions" tab, on the S3 AWS Console. 19 + 20 + ```xml 21 + <?xml version="1.0" encoding="UTF-8"?> 22 + <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 23 + <CORSRule> 24 + <AllowedOrigin>*</AllowedOrigin> 25 + <AllowedMethod>GET</AllowedMethod> 26 + <AllowedMethod>HEAD</AllowedMethod> 27 + <MaxAgeSeconds>31536000</MaxAgeSeconds> 28 + <ExposeHeader>Content-Length</ExposeHeader> 29 + <ExposeHeader>Content-Type</ExposeHeader> 30 + <AllowedHeader>Range</AllowedHeader> 31 + </CORSRule> 32 + </CORSConfiguration> 33 + ``` 34 + 35 + <div id="CORS__BTFS" /> 36 + 37 + #### BTFS 38 + 39 + Add the domain of the app, with the protocol, to the __list of allowed origins__. 40 + 41 + ```shell 42 + btfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["https://diffuse.sh", "http://diffuse.sh.ipns.localhost:8080", "http://127.0.0.1:44999"]' 43 + ``` 44 + 45 + You can also make this change in the Web UI, you'll find it under "Settings → BTFS Config". 46 + 47 + ```javascript 48 + { 49 + "API": { 50 + "HTTPHeaders": { 51 + "Access-Control-Allow-Origin": [ 52 + "https://diffuse.sh", // 🎵 Default 53 + "http://diffuse.sh.ipns.localhost:8080", // IPNS 54 + "http://127.0.0.1:44999" // Electron app 55 + ] 56 + } 57 + } 58 + } 59 + ``` 60 + 61 + <div id="CORS__Dropbox" /> 62 + 63 + #### Dropbox 64 + 65 + _Not necessary._ 66 + 67 + <div id="CORS__Google-Drive" /> 68 + 69 + #### Google Drive 70 + 71 + _Not necessary._ 72 + 73 + <div id="CORS__IPFS" /> 74 + 75 + #### IPFS 76 + 77 + Add the domain of the app, with the protocol, to the __list of allowed origins__. 78 + 79 + ```shell 80 + ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["https://diffuse.sh", "http://diffuse.sh.ipns.localhost:8080", "http://127.0.0.1:44999"]' 81 + ``` 82 + 83 + You can also make this change in the Web UI, you'll find it under "Settings → IPFS Config". 84 + 85 + ```javascript 86 + { 87 + "API": { 88 + "HTTPHeaders": { 89 + "Access-Control-Allow-Origin": [ 90 + "https://diffuse.sh", // 🎵 Default 91 + "http://diffuse.sh.ipns.localhost:8080", // IPNS through IPFS Companion 92 + "http://127.0.0.1:44999" // Electron app 93 + ] 94 + } 95 + } 96 + } 97 + ``` 98 + 99 + <div id="CORS__Azure" /> 100 + 101 + #### Microsoft Azure Storage 102 + 103 + You can find the CORS configuration under the "Settings -> CORS". 104 + Then fill in the following in the input boxes (left to right): 105 + 106 + ``` 107 + ALLOWED ORIGINS * 108 + ALLOWED METHODS GET, HEAD 109 + ALLOWED HEADERS Range 110 + EXPOSED HEADERS Content-Length, Content-Range 111 + MAX AGE 0 112 + ``` 113 + 114 + <div id="CORS__WebDAV" /> 115 + 116 + #### WebDAV 117 + 118 + __Depends on your WebDAV server.__ 119 + Example setup for Henrique Dias's [WebDAV server](https://github.com/hacdias/webdav): 120 + 121 + ```yaml 122 + cors: 123 + enabled: true 124 + credentials: true 125 + 126 + allowed_headers: 127 + - Authorization 128 + - Content-Type 129 + - Depth 130 + - Range 131 + allowed_methods: 132 + - GET 133 + - HEAD 134 + - PROPFIND 135 + allowed_hosts: 136 + - https://diffuse.sh 137 + - http://127.0.0.1:44999 138 + exposed_headers: 139 + - Content-Length 140 + - Content-Type 141 + ```
+131
src/Static/About/Index.md
··· 1 + > A music player that connects to your cloud &amp; distributed storage 2 + 3 + [Return to the application](../) 4 + [CORS instructions](cors/) 5 + 6 + 7 + 8 + ## What makes it different? 9 + 10 + Diffuse is a decentralized music player consisting out of two main parts. One part is the music and the other is your data <small>(eg. playlists)</small>, both of which are in locations of your choice. Meaning that there's no central server for Diffuse, all of the processing happens on your device and all the data is in your control. You can use the [web version](https://diffuse.sh), the [native version](https://github.com/icidasset/diffuse/releases) or host it yourself by downloading the pre-built packages from [Github](https://github.com/icidasset/diffuse). 11 + 12 + 13 + ### Music layer 14 + 15 + This layer connects to the services on which your music is stored, no data is written to these services. You can combine all of the following: 16 + 17 + - [Amazon S3](https://aws.amazon.com/s3/) 18 + - [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) 19 + - [Azure File Storage](https://azure.microsoft.com/en-us/services/storage/files/) 20 + - [Dropbox](https://dropbox.com/) 21 + - [Google Drive](https://drive.google.com/) 22 + - [IPFS](https://ipfs.io/) <small>(supports DNSLink & IPNS)</small> 23 + - [WebDAV](https://en.wikipedia.org/wiki/WebDAV) 24 + 25 + 26 + ### User layer 27 + 28 + This layer will use a single service on which to store your data. Your data being your settings, favourites, playlists, etc. You can choose between these services: 29 + 30 + - [Dropbox](https://www.dropbox.com/) 31 + - [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) <small>(Browser)</small> 32 + - [IPFS](https://ipfs.io/) <small>(using MFS)</small> 33 + - [RemoteStorage](https://remotestorage.io/) 34 + 35 + 36 + 37 + <div id="How" /> 38 + 39 + ## How does it work? 40 + 41 + Diffuse locates all the music files on the given services, extracts the metadata and then stores it via the previously-explained user layer. 42 + 43 + 44 + ### Supported File Formats 45 + 46 + - MP3 47 + - MP4/M4A 48 + - FLAC 49 + - OGG 50 + - WAV 51 + - WEBM 52 + 53 + <small><em>Note, support may vary depending on your <a href="https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers#Index_of_media_container_formats_file_types">browser</a>.</em></small> 54 + 55 + 56 + 57 + <div id="UI" /> 58 + 59 + ## UI 60 + 61 + There are a few "hidden" features: 62 + 63 + - **Tracks have a context menu** which can be opened by either right clicking, 64 + or holding it (ie. a long tap). Use the ALT key whilst right clicking 65 + on a track to show the alternative track-context menu with more specialized options. 66 + - **You can reorder items** in the queue or a playlist with drag-and-drop. 67 + Select the item you want to move by tapping on it, then tap and hold to move it around. 68 + - You can select multiple tracks using the SHIFT key and then add that selection 69 + to the queue or a playlist using the context menu. 70 + - Click on the now-playing bit to scroll to that track. 71 + - Double tap on a EQ setting to reset it to its default value. 72 + 73 + ### Playlists 74 + 75 + To add something to a playlist, and create that playlist if it doesn't exist yet, you open the context menu of a track. To move tracks around in a playlist, first select the track, then drag it. 76 + 77 + ### Search 78 + 79 + ```shell 80 + # Show me every track where the title, artist or album contains the term 'Parkway' and the term 'Drive'. Terms are separated by spaces (eg. "Killing with a smile" has four terms). 81 + Parkway Drive 82 + 83 + # Show me every track of which the artist's name contains 'park'. 84 + artist:park* 85 + 86 + # Show me every track from Parkway Drive's "Deep Blue" album. 87 + artist:Parkway Drive album:Deep Blue 88 + 89 + # Show me every track from Parkway Drive but not their "Atlas" album. 90 + artist:Parkway Drive - album:Atlas 91 + ``` 92 + 93 + ### Keyboard 94 + 95 + The app should be usable with only the keyboard, there are various keyboard shortcuts: 96 + 97 + ``` 98 + L - Select playlist using autocompletion 99 + N - Scroll to currently-playing track 100 + P - Play / Pause 101 + R - Toggle Repeat 102 + S - Toggle Shuffle 103 + 104 + { / } - Previous / Next 105 + < / > - Seek forwards / Seek backwards 106 + 107 + Alternatively you can use the media-control keys, 108 + if your browser supports it. 109 + 110 + ESC - Close overlay, close context-menu, deselect album cover, etc. 111 + 112 + 1 - Tracks 113 + 2 - Playlists 114 + 3 - Queue 115 + 4 - EQ 116 + 117 + 8 - Sources 118 + 9 - Settings 119 + ``` 120 + 121 + 122 + 123 + <div id="QA" /> 124 + 125 + ## Q&A 126 + 127 + ### I used version one, where's my data? 128 + 129 + There's a small link, or button if you will, on the "Settings → Import / Export" 130 + page that will allow you to import data from version 1 of the app. Note that this 131 + will need to reflect the authentication/storage method you chose in version 1.
+10 -10
src/Static/About/Layout.html
··· 9 9 <meta name="viewport" content="width=device-width, initial-scale=1" /> 10 10 11 11 <!-- Favicons & Mobile --> 12 - <link rel="apple-touch-icon" sizes="180x180" href="../apple-touch-icon.png" /> 13 - <link rel="icon" type="image/png" sizes="32x32" href="../favicon-32x32.png" /> 14 - <link rel="icon" type="image/png" sizes="16x16" href="../favicon-16x16.png" /> 15 - <link rel="manifest" href="../site.webmanifest" /> 16 - <link rel="mask-icon" href="../safari-pinned-tab.svg" color="#8a90a9" /> 12 + <link rel="apple-touch-icon" sizes="180x180" href="{{pathToRoot}}apple-touch-icon.png" /> 13 + <link rel="icon" type="image/png" sizes="32x32" href="{{pathToRoot}}favicon-32x32.png" /> 14 + <link rel="icon" type="image/png" sizes="16x16" href="{{pathToRoot}}favicon-16x16.png" /> 15 + <link rel="manifest" href="{{pathToRoot}}site.webmanifest" /> 16 + <link rel="mask-icon" href="{{pathToRoot}}safari-pinned-tab.svg" color="#8a90a9" /> 17 17 <meta name="msapplication-TileColor" content="#8a90a9" /> 18 18 <meta name="theme-color" content="#8a90a9" /> 19 19 20 20 <!-- Styles --> 21 - <link rel="stylesheet" href="../about.css" /> 21 + <link rel="stylesheet" href="{{pathToRoot}}about.css" /> 22 22 23 23 </head> 24 24 <body class="bg-white font-body my-16 px-4 text-base01 dark:bg-darkest-hour dark:text-gray-600"> 25 25 26 26 27 27 <main class="max-w-2xl mx-auto"> 28 - <a class="inline-block logo" href="../"> 29 - <img class="block dark:hidden" src="../images/diffuse-dark.svg" /> 30 - <img class="hidden dark:block" src="../images/diffuse-light.svg" /> 28 + <a class="inline-block logo" href="{{pathToRoot}}"> 29 + <img class="block dark:hidden" src="{{pathToRoot}}images/diffuse-dark.svg" /> 30 + <img class="hidden dark:block" src="{{pathToRoot}}images/diffuse-light.svg" /> 31 31 <h1>Diffuse</h1> 32 32 </a> 33 33 ··· 38 38 <!-- Service worker --> 39 39 <script> 40 40 if ("serviceWorker" in navigator) { 41 - navigator.serviceWorker.register("../service-worker.js") 41 + navigator.serviceWorker.register("{{pathToRoot}}service-worker.js") 42 42 } 43 43 </script> 44 44
+4 -2
system/Build/Main.hs
··· 105 105 106 106 flow x (AboutPages, dict) = 107 107 dict 108 + |> map lowerCasePath 109 + |> renameExt ".md" ".html" 110 + |> permalink "index" 111 + |> prefixDirname "about/" 108 112 |> renderContent markdownRenderer 109 113 |> renderContent (layoutRenderer $ x !~> "aboutLayout") 110 - |> rename "About.md" "index.html" 111 - |> prefixDirname "about/" 112 114 113 115 114 116
+15 -8
system/Build/Renderers.hs
··· 14 14 15 15 layoutRenderer :: Text -> Definition -> Maybe ByteString 16 16 layoutRenderer layout def = 17 - content def 18 - |> fmap Text.decodeUtf8 19 - |> fmap (\text -> Text.replace "<placeholder />" text layout) 20 - |> fmap Text.encodeUtf8 17 + let 18 + layoutWithoutVariables = 19 + Text.replace 20 + "{{pathToRoot}}" 21 + (Text.pack <| pathToRoot def) 22 + layout 23 + in 24 + content def 25 + |> fmap Text.decodeUtf8 26 + |> fmap (\text -> Text.replace "<placeholder />" text layoutWithoutVariables) 27 + |> fmap Text.encodeUtf8 21 28 22 29 23 30 ··· 26 33 27 34 markdownRenderer :: Definition -> Maybe ByteString 28 35 markdownRenderer def = 29 - content def 30 - |> fmap Text.decodeUtf8 31 - |> fmap (CMark.commonmarkToHtml [ CMark.optSmart ]) 32 - |> fmap Text.encodeUtf8 36 + content def 37 + |> fmap Text.decodeUtf8 38 + |> fmap (CMark.commonmarkToHtml [ CMark.optSmart, CMark.optUnsafe ]) 39 + |> fmap Text.encodeUtf8