├── LICENSE ├── README.md ├── bandwagon-album ├── checkout.html ├── javascript │ └── playlist.js ├── oembed.html ├── stylesheet.html ├── template.hjson ├── unpublish.html ├── upload.html └── view.html ├── bandwagon-common └── template.hjson ├── bandwagon-event ├── edit.html ├── stylesheet.html ├── template.hjson └── view.html ├── bandwagon-news ├── edit.html ├── template.hjson └── view.html ├── bandwagon-outbox ├── albums.html ├── coming-soon.html ├── events.html ├── header.html ├── news.html ├── outbox.html ├── stylesheet.html ├── template.hjson ├── wizard-1.html ├── wizard-2.html └── wizard-3.html ├── bandwagon-search-albums ├── json.html ├── template.hjson ├── view-results.html ├── view-sidebar.html └── view-tags.html ├── bandwagon-search-events ├── embed-test.html ├── embed.html ├── template.hjson ├── view-results.html └── view-sidebar.html ├── bandwagon-search-news ├── template.hjson ├── view-results.html └── view-sidebar.html ├── bandwagon-search-tracks ├── json.html ├── template.hjson ├── view-results.html ├── view-sidebar.html └── view-tags.html ├── bandwagon-search-users ├── template.hjson ├── view-results.html └── view-sidebar.html ├── bandwagon-search ├── hyperscript │ ├── listbox._hs │ ├── popUp._hs │ ├── searchSuggestion._hs │ ├── searchTag._hs │ ├── searchWidget._hs │ └── utils._hs ├── javascript │ └── parseTags.js ├── search-related.html ├── template.hjson ├── view-results.html ├── view-suggestions.html ├── view-widget.html └── view.html └── bandwagon-song ├── oembed.html ├── template.hjson └── view.html /README.md: -------------------------------------------------------------------------------- 1 | # Bandwagon - Social Web for Musicians and Bands 2 | Bandwagon is a social music sharing service for the Fediverse. Built for bands, Bandwagon is a customizable home page for all your songs, show dates, photos, and announcements. 3 | 4 | ## Build with Emissary 5 | Bandwagon is a small social app build on [Emissary, the social web toolkit](https://emissary.dev). You must [install Emissary](https://emissary.dev/installation) on a server first, before using Bandwagon. 6 | 7 | ### Installing 8 | To install Bandwagon on your own Emissary server directly from this repository, just open your Emissary setup console, navigate to Server Settings > Packages, and add a new Git adapter that points to: https://github.com/EmissarySocial/app-bandwagon.git 9 | 10 | ### Customizing 11 | Bandwagon consists of a new custom profile page (or, outbox) and templates for a handful of new content types that each band can post: albums, show dates, photos, and announcements. After installing Bandwagon, each musician can customize the colors, images, and labels used in their own profile. 12 | -------------------------------------------------------------------------------- /bandwagon-album/checkout.html: -------------------------------------------------------------------------------- 1 | {{- $userID := .AttributedTo.UserID.Hex -}} 2 | {{- $permalink := .Permalink -}} 3 | {{- $checkoutLabel := first (.Data "checkout label") "Buy Now" -}} 4 |

{{$checkoutLabel}}

5 | 6 |
7 | {{- range $index, $product := .Products.Slice -}} 8 | 9 |
10 |
{{$product.Name}}
11 |
{{$product.Description}}
12 |
13 |
14 |
15 | {{- end -}} 16 |
17 | 18 |
19 | 20 |
-------------------------------------------------------------------------------- /bandwagon-album/javascript/playlist.js: -------------------------------------------------------------------------------- 1 | var playlist 2 | var playlistIndex = -1 3 | 4 | // Play loads and plays the song at the current playlistIndex 5 | function Play() { 6 | 7 | if (playlistIndex < 0) { 8 | return 9 | } 10 | 11 | if (playlistIndex >= playlist.length) { 12 | return 13 | } 14 | 15 | var audio = htmx.find("audio") 16 | var controls = htmx.find("#media-controls") 17 | var song = playlist[playlistIndex] 18 | 19 | htmx.find("#source-mp3").src = song.url + ".mp3?bitrate=128&v=2" 20 | htmx.find("#source-ogg").src = song.url + ".opus?bitrate=128&v=2" 21 | 22 | audio.load() 23 | audio.play() 24 | 25 | htmx.addClass(controls, "PLAYING") 26 | htmx.takeClass(htmx.find("#track-" + playlistIndex), "PLAYING") 27 | } 28 | 29 | // Pause pauses the audio control 30 | function Pause() { 31 | var controls = htmx.find("#media-controls") 32 | var audio = htmx.find("audio") 33 | var track = htmx.find("#track-" + playlistIndex) 34 | 35 | audio.pause() 36 | htmx.removeClass(controls, "PLAYING") 37 | htmx.removeClass(track, "PLAYING") 38 | } 39 | 40 | // PlayFirst jumps to the first song in the playlist and plays it 41 | function PlayFirst() { 42 | if (playlist.length > 0) { 43 | playlistIndex = 0 44 | Play() 45 | } 46 | } 47 | 48 | // PlayPrevious plays the previous song in the playlist and plays it 49 | function PlayPrevious() { 50 | if (playlist.length > 0) { 51 | playlistIndex-- 52 | if (playlistIndex < 0) { 53 | playlistIndex = playlist.length - 1 54 | } 55 | Play() 56 | } 57 | } 58 | 59 | // PlayNext plays the next song in the playlist and plays it 60 | function PlayNext(loop) { 61 | if (playlist.length > 0 ) { 62 | playlistIndex++ 63 | if (playlistIndex >= playlist.length) { 64 | 65 | if (loop == true) { 66 | playlistIndex = 0 67 | } else { 68 | return 69 | } 70 | } 71 | Play() 72 | } 73 | } 74 | 75 | function Toggle(trackNumber) { 76 | 77 | var audio = htmx.find("audio") 78 | 79 | // Do not overflow the playlist. 80 | if (trackNumber >= playlist.length) { 81 | return 82 | } 83 | 84 | // If the audio is already paused, or is playing a different track, then play this track 85 | if ((audio.paused) || (playlistIndex != trackNumber)) { 86 | playlistIndex = trackNumber 87 | Play() 88 | } else { 89 | Pause() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /bandwagon-album/oembed.html: -------------------------------------------------------------------------------- 1 | {{- $albumColors := .Data "color" -}} 2 | {{- $bodyColor := first $albumColors.body "#eeeeee" -}} 3 | {{- $pageColor := parseColor (first $albumColors.page "#ffffff") -}} 4 | {{- $buttonColor := parseColor (first $albumColors.button "#0f62fe") -}} 5 | {{- $titleColor := $pageColor.Text -}} 6 | {{- $textColor := $pageColor.TextExt -}} 7 | 8 |
9 |
10 | 11 | 12 | {{- if ne "" .IconURL -}} 13 | 16 | {{- end -}} 17 | 74 | 75 |
18 |
{{.Label}}
19 |
20 | 21 | {{.AttributedTo.Name}} 22 | 23 | {{ if ne nil (.Data "year") }} 24 | · 25 | {{.Data "year"}} 26 | {{- end }} 27 |
28 | 54 | 55 | {{- if ne "" .Summary -}} 56 |
57 | {{.Summary}} 58 |
59 | {{- end -}} 60 | 61 |
62 | {{- $license := .DataString "license"}} 63 | {{- if eq "COPYRIGHT" $license -}} 64 | © 65 | Copyright. All Rights Reserved. 66 | {{- else if ne "" $license -}} 67 | {{- $licenses := .Dataset "bandwagon-album.licenses" -}} 68 | {{- $licenseInfo := $licenses.Value $license -}} 69 | {{icon $licenseInfo.Icon}} 70 | {{$licenseInfo.Label}} 71 | {{- end -}} 72 |
73 |
76 |
77 |
-------------------------------------------------------------------------------- /bandwagon-album/stylesheet.html: -------------------------------------------------------------------------------- 1 | {{- $artist := .ParentOutbox "view" -}} 2 | {{- $albumColors := .Data "color" -}} 3 | 4 | {{- $bodyColor := first $albumColors.body ($artist.Data "color-body") -}} 5 | {{- $pageColor := first $albumColors.page ($artist.Data "color-page") -}} 6 | {{- $buttonColor := first $albumColors.button ($artist.Data "color-button") -}} 7 | 8 | body { 9 | 10 | {{ if ne nil $bodyColor -}} 11 | --body-background: {{$bodyColor}}; 12 | {{- end }} 13 | 14 | {{ if ne nil $pageColor -}} 15 | {{- $pageColorParsed := parseColor $pageColor -}} 16 | --page-background: {{$pageColorParsed}}; 17 | --page-border: {{$pageColorParsed.Darken 10}}; 18 | --heading-color: {{$pageColorParsed.Text}}; 19 | --text-color: {{$pageColorParsed.TextExt}}; 20 | 21 | {{if $pageColorParsed.IsLight }} 22 | --white: rgb(255, 255, 255); 23 | --gray00: rgb(255, 255, 255); 24 | --gray01: rgba(0, 0, 0, 0.01); 25 | --gray02: rgba(0, 0, 0, 0.02); 26 | --gray03: rgba(0, 0, 0, 0.03); 27 | --gray04: rgba(0, 0, 0, 0.04); 28 | --gray05: rgba(0, 0, 0, 0.05); 29 | --gray10: rgba(0, 0, 0, 0.10); 30 | --gray15: rgba(0, 0, 0, 0.15); 31 | --gray20: rgba(0, 0, 0, 0.20); 32 | --gray30: rgba(0, 0, 0, 0.30); 33 | --gray40: rgba(0, 0, 0, 0.40); 34 | --gray50: rgba(0, 0, 0, 0.50); 35 | --gray60: rgba(0, 0, 0, 0.60); 36 | --gray70: rgba(0, 0, 0, 0.70); 37 | --gray80: rgba(0, 0, 0, 0.80); 38 | --gray90: rgba(0, 0, 0, 0.90); 39 | --gray100: rgb(0, 0, 0); 40 | --black: rgb(0, 0, 0); 41 | {{- else }} 42 | --white: rgb(0, 0, 0) ; 43 | --gray00: rgb(0, 0, 0) ; 44 | --gray01: rgba(255, 255, 255, 0.01); 45 | --gray02: rgba(255, 255, 255, 0.02); 46 | --gray03: rgba(255, 255, 255, 0.03); 47 | --gray04: rgba(255, 255, 255, 0.04); 48 | --gray05: rgba(255, 255, 255, 0.05); 49 | --gray10: rgba(255, 255, 255, 0.10); 50 | --gray15: rgba(255, 255, 255, 0.15); 51 | --gray20: rgba(255, 255, 255, 0.20); 52 | --gray30: rgba(255, 255, 255, 0.30); 53 | --gray40: rgba(255, 255, 255, 0.40); 54 | --gray50: rgba(255, 255, 255, 0.50); 55 | --gray60: rgba(255, 255, 255, 0.60); 56 | --gray70: rgba(255, 255, 255, 0.70); 57 | --gray80: rgba(255, 255, 255, 0.80); 58 | --gray90: rgba(255, 255, 255, 0.90); 59 | --gray100: rgb(255, 255, 255); 60 | --black: rgb(255, 255, 255); 61 | {{- end -}} 62 | 63 | {{- end }} 64 | 65 | {{ if ne nil $buttonColor -}} 66 | {{- $buttonColorParsed := parseColor $buttonColor -}} 67 | {{- $buttonText := $buttonColorParsed.Text }} 68 | {{- $buttonColorHover := ($buttonColorParsed.Lighten 10) -}} 69 | {{- $buttonTextHover := $buttonColorHover.Text }} 70 | 71 | --button-primary-background: {{$buttonColor}}; 72 | --button-primary-background-hover: {{$buttonColorHover}}; 73 | --button-primary-color: {{$buttonText}}; 74 | --button-primary-color-hover: {{$buttonTextHover}}; 75 | --link-color: {{$buttonColor}}; 76 | --link-color-hover: {{$buttonColorHover}}; 77 | {{- end }} 78 | 79 | } 80 | 81 | #media-controls:not(.PLAYING) > .playing-show, 82 | #media-controls.PLAYING > .playing-hide, 83 | .track:not(.PLAYING) .playing-show, 84 | .track.PLAYING .playing-hide, 85 | .track:not(.PLAYING) .playing-hide.hover-hide 86 | .track:not(.PLAYING):hover .playing-hide.hover-show 87 | { 88 | display: none; 89 | } 90 | 91 | #media-controls.PLAYING > .playing-show, 92 | #media-controls:not(.PLAYING) > .playing-hide, 93 | .track.PLAYING playing-show, 94 | .track:not(.PLAYING) .playing-hide, 95 | .track:not(.PLAYING) .playing-hide.hover-hide 96 | .track:not(.PLAYING):hover .playing-hide.hover-show, 97 | { 98 | display: initial; 99 | } 100 | 101 | .track.PLAYING { 102 | background-color: var(--gray10); 103 | } 104 | 105 | {{$artist.Data "stylesheet"}} -------------------------------------------------------------------------------- /bandwagon-album/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-album 3 | templateRole: album 4 | model: Stream 5 | containedBy: ["outbox"] 6 | extends: ["base-intent", "bandwagon-common"] 7 | icon: album 8 | label: Album (Default) 9 | description: Contains one or more songs 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string", required: true} 14 | summary: {type:"string", format:"html", maxLength:2048} 15 | iconUrl: {type:"string", format:"url"} 16 | syndication: {type:"array", items:{type:"string"}} 17 | data: { 18 | type:object 19 | properties: { 20 | checkoutLabel: {type:"string", maxLength:255} 21 | license: {type:"string"} 22 | imageFilename: {type:"string", maxLength:255} 23 | tags: {type:"string"} 24 | releaseDate:{type:"string", format:"date"} 25 | links: {type:"object", wildcard: {type:"string", format:"uri"}} 26 | color: {type:"object", properties: { 27 | body: {type:"string", format:"color"}, 28 | page: {type:"string", format:"color"}, 29 | button: {type:"string", format:"color"} 30 | }} 31 | } 32 | } 33 | } 34 | } 35 | 36 | accessRoles: { 37 | buyer: { 38 | label: Buyer 39 | description: Buyers have purchased this album and have lifetime access to it. 40 | subscription: true 41 | } 42 | subscriber: { 43 | label: Subscriber 44 | description: Subscribers have full access so long as their subscription is valid. 45 | subscription: true 46 | } 47 | renter: { 48 | label: Renter 49 | description: IDK, man. I'm just testing subscription stuff. 50 | subscription: true 51 | } 52 | } 53 | 54 | socialRole: Album 55 | socialRules: [ 56 | 57 | {target:"@context", append:"https://funkwhale.audio/ns"} 58 | {target:"content", expression:"{{ markdown .Summary }}"} 59 | {target:"license", path:"data.license"} 60 | {target:"links", forEach:"data.links", rules:[ 61 | {target:"label", path:"key"} 62 | {target:"rel", value:"alternate"} 63 | {target:"href", path:"value"} 64 | ]} 65 | {target:"library", expression:"{{.URL}}/pub/children"} 66 | {target:"artists", value:[]} 67 | {target:"artists", append:{type:"Artist"}} 68 | {target:"artists.0.id", path:"attributedTo.profileUrl"} 69 | {target:"artists.0.name", path:"attributedTo.name"} 70 | {target:"tracks", expression:"{{.URL}}/pub/children"} 71 | ] 72 | 73 | tagPaths: ["data.tags"] 74 | 75 | search: { 76 | type: "Album" 77 | iconUrl:"" 78 | summary:"" 79 | text: "{{.Label}} {{.AttributedTo.Name}}" 80 | tags: "LIC:{{index .Data `license`}}" 81 | } 82 | 83 | states: { 84 | unpublished: { 85 | label:"Default State" 86 | description:"Visible only to Authors and Owners" 87 | } 88 | published: { 89 | label:"Published" 90 | description:"Visible to all people with permissions" 91 | } 92 | } 93 | bundles: { 94 | javascript: {contentType: "text/javascript"} 95 | } 96 | 97 | actions: { 98 | create: { 99 | roles:["author"] 100 | steps: [ 101 | {do:"as-modal", steps: [ 102 | {do:"set-data", from-url:["isFeatured"]} 103 | { 104 | do: edit 105 | form: { 106 | label: + Add an Album 107 | type:layout-tabs 108 | children: [ 109 | { 110 | type:layout-vertical 111 | label: General 112 | children: [ 113 | {type:"text", path:"label", label:"Album Name"} 114 | {type:"select", path:"data.license", label:"License", options:{required:true, provider:"bandwagon-album.licenses"}} 115 | {type:"date", path:"data.releaseDate", label:"Release Date"} 116 | {type:"upload", path:"iconUrl", label:"Album Art", options:{accept:"image/*", filename:"data.imageFilename", delete:"/{{.StreamID}}/delete-icon", rules:{width:800, height:800, types:["webp"]}}} 117 | {type:"toggle", path:"isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured?"}} 118 | ] 119 | } 120 | { 121 | type:layout-vertical 122 | label: Metadata 123 | children: [ 124 | {type:"textarea", path:"summary", label:"Sidebar Notes", description:"Notes appear on the side of the album page. Markdown is allowed.", options:{rows:8, showLimit:true}} 125 | {type:"textarea", path:"data.tags", label:"Tags", description:"Enter #Hashtags separated by spaces."} 126 | ] 127 | } 128 | { 129 | type:layout-vertical 130 | label: Links 131 | children: [ 132 | {type:"text", path:"data.links.AMAZON", options:{placeholder:"Amazon Music"}} 133 | {type:"text", path:"data.links.APPLE", options:{placeholder:"Apple Music"}} 134 | {type:"text", path:"data.links.BANDCAMP", options:{placeholder:"Bandcamp"}} 135 | {type:"text", path:"data.links.GOOGLE", options:{placeholder:"Google Play"}} 136 | {type:"text", path:"data.links.SOUNDCLOUD", options:{placeholder:"Soundcloud"}} 137 | {type:"text", path:"data.links.SPOTIFY", options:{placeholder:"Spotify"}} 138 | {type:"text", path:"data.links.TIDAL", options:{placeholder:"Tidal"}} 139 | {type:"text", path:"data.links.YOUTUBE", options:{placeholder:"YouTube Music"}} 140 | {type:"text", path:"data.links.OTHER1", options:{placeholder:"Other"}} 141 | {type:"text", path:"data.links.OTHER2", options:{placeholder:"Other"}} 142 | {type:"text", path:"data.links.OTHER3", options:{placeholder:"Other"}} 143 | ] 144 | } 145 | { 146 | type:layout-vertical 147 | label: Colors 148 | children: [ 149 | {type:"colorpicker", path:"data.color.body", label:"Window Background"} 150 | {type:"colorpicker", path:"data.color.page", label:"Page Background"} 151 | {type:"colorpicker", path:"data.color.button", label:"Links and Buttons"} 152 | ] 153 | } 154 | ] 155 | } 156 | } 157 | ]} 158 | {do:"upload-attachments", category:"image", fieldname:"iconUrl", download-path:"iconUrl", accept-type:"image/*", maximum:1, rules:{width:800, height:800, types:["webp"]}} 159 | {do:"process-tags", paths:"data.tags"} 160 | {do:"save"} 161 | {do:"forward-to", url: "/{{.StreamID}}"} 162 | ] 163 | } 164 | view: { 165 | roles: ["editor", "owner"] 166 | stateRoles: { 167 | published: ["anonymous"] 168 | } 169 | do: "view-html" 170 | } 171 | edit: { 172 | roles: ["author"] 173 | steps: [ 174 | {do:"as-modal", steps: [ 175 | { 176 | do: edit 177 | options: ["delete:/{{.StreamID}}/delete"] 178 | form: { 179 | label: Edit Album 180 | type:layout-tabs 181 | children: [ 182 | { 183 | type:layout-vertical 184 | label: General 185 | children: [ 186 | {type:"text", path:"label", label:"Album Name"} 187 | {type:"select", path:"data.license", label:"License", options:{required:true, provider:"bandwagon-album.licenses"}} 188 | {type:"date", path:"data.releaseDate", label:"Release Date"} 189 | {type:"upload", path:"iconUrl", label:"Album Art", options:{accept:"image/*", filename:"data.imageFilename", delete:"/{{.StreamID}}/delete-icon", rules:{width:800, height:800, types:["webp"]}}} 190 | {type:"toggle", path:"isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured?"}} 191 | ] 192 | } 193 | { 194 | type:layout-vertical 195 | label: Metadata 196 | children: [ 197 | {type:"textarea", path:"summary", label:"Sidebar Notes", description:"Notes appear on the side of the album page. Markdown is allowed.", options:{rows:8, showLimit:true}} 198 | {type:"textarea", path:"data.tags", label:"Tags", description:"Enter #Hashtags separated by spaces"} 199 | ] 200 | } 201 | { 202 | type:layout-vertical 203 | label: Links 204 | children: [ 205 | {type:"text", path:"data.links.AMAZON", options:{placeholder:"Amazon Music"}} 206 | {type:"text", path:"data.links.APPLE", options:{placeholder:"Apple Music"}} 207 | {type:"text", path:"data.links.BANDCAMP", options:{placeholder:"Bandcamp"}} 208 | {type:"text", path:"data.links.GOOGLE", options:{placeholder:"Google Play"}} 209 | {type:"text", path:"data.links.SOUNDCLOUD", options:{placeholder:"Soundcloud"}} 210 | {type:"text", path:"data.links.SPOTIFY", options:{placeholder:"Spotify"}} 211 | {type:"text", path:"data.links.TIDAL", options:{placeholder:"Tidal"}} 212 | {type:"text", path:"data.links.YOUTUBE", options:{placeholder:"YouTube Music"}} 213 | {type:"text", path:"data.links.OTHER1", options:{placeholder:"Other"}} 214 | {type:"text", path:"data.links.OTHER2", options:{placeholder:"Other"}} 215 | {type:"text", path:"data.links.OTHER3", options:{placeholder:"Other"}} 216 | ] 217 | } 218 | { 219 | type:layout-vertical 220 | label: Colors 221 | children: [ 222 | {type:"colorpicker", path:"data.color.body", label:"Window Background"} 223 | {type:"colorpicker", path:"data.color.page", label:"Page Background"} 224 | {type:"colorpicker", path:"data.color.button", label:"Links and Buttons"} 225 | ] 226 | } 227 | { 228 | type:"layout-vertical" 229 | label:"Distribution" 230 | children:[ 231 | {type:"multiselect", path:"syndication", label:"Distribute to these streaming services", description:"These services will be notified when this album is published.", options:{provider:"syndication-targets"}} 232 | ] 233 | } 234 | ] 235 | } 236 | } 237 | ]} 238 | {do:"upload-attachments", category:"image", fieldname:"iconUrl", download-path:"iconUrl", accept-type:"image/*", maximum:1} 239 | {do:"process-tags", paths:"data.tags"} 240 | {do:"search-index"} 241 | 242 | {do:"if", condition: "{{.IsPublished}}", 243 | then: [ 244 | {do:"save-and-publish"} 245 | ], 246 | else: [ 247 | {do:"save"} 248 | ] 249 | } 250 | {do:"refresh-page"} 251 | ] 252 | } 253 | 254 | checkout: { 255 | roles: ["anonymous"] 256 | steps: [ 257 | {do:"if", condition:"{{eq 1 .Products.Count }}", then:[ 258 | {do:"redirect-to", url:"/.checkout?productId={{.Products.Slice.First.ProductID.Hex}}&return={{.Permalink}}"} 259 | ], else:[ 260 | {do:"as-modal", background:"view", steps:[ 261 | {do:"view-html"} 262 | ]} 263 | ]} 264 | ] 265 | } 266 | 267 | /* 268 | edit-products: { 269 | roles: ["owner"] 270 | steps: [ 271 | {do:"as-modal", steps: [ 272 | {do:"set-products", title:"Purchase Options"} 273 | {do:"save", message:"Subscription settings updated by {{.Author}}"} 274 | ]} 275 | ] 276 | } 277 | */ 278 | 279 | delete: { 280 | roles: ["author"] 281 | steps: [ 282 | {do:"delete"} 283 | {do:"search-index"} 284 | {do:"forward-to", url: "/@{{.AttributedTo.UserID.Hex}}"} 285 | ] 286 | } 287 | 288 | delete-icon: { 289 | roles: ["author"] 290 | steps: [ 291 | {do:"delete-attachments"} 292 | {do:"set-data", values:{"iconUrl": ""}} 293 | {do:"save"} 294 | ] 295 | } 296 | 297 | add-song: { 298 | roles: ["author"] 299 | steps: [ 300 | { 301 | do: add-stream 302 | style: inline 303 | location: child 304 | title: + Add a Song 305 | type:bandwagon-song 306 | with-data:{ 307 | "data.artist":"{{.AttributedTo.Name}}" 308 | "data.composer":"{{.AttributedTo.Name}}" 309 | "data.tags":"{{.Data `tags`}}" 310 | "data.license": "{{.Data `license`}}" 311 | } 312 | } 313 | {do:"refresh-page"} 314 | ] 315 | } 316 | 317 | sort-tracks: { 318 | roles: ["author"] 319 | steps: [ 320 | {do:"sort", model:"Stream", keys:"_id", values:"rank"} 321 | {do:"refresh-page"} 322 | ] 323 | } 324 | 325 | links: { 326 | steps:[ 327 | {do:"as-modal", steps:[ 328 | {do:"view-html"} 329 | ]} 330 | ] 331 | } 332 | 333 | stylesheet: { 334 | steps: [ 335 | {do:"cache-url"} 336 | {do:"view-css"} 337 | ] 338 | } 339 | 340 | upload: { 341 | steps: [ 342 | {do:"as-modal", steps:[ 343 | {do:"view-html"} 344 | {do:"edit", form:{ 345 | type:"layout-vertical" 346 | children:[ 347 | {type:"text", path:"attachmentLabel", label:"Label"} 348 | {type:"textarea", path:"attachmentDescription", label:"Description", description:"A few words to show underneath the filename."} 349 | {type:"upload", path:"file", label:"Attachment"} 350 | ] 351 | }, options:["submit-label:Upload Attachment"]} 352 | {do:"upload-attachments", category:"other", maximum:8, fieldname:"file", label-fieldname:"attachmentLabel", description-fieldname:"attachmentDescription"} 353 | {do:"refresh-page"} 354 | ]} 355 | ] 356 | } 357 | 358 | edit-upload: { 359 | steps: [ 360 | {do:"as-modal", steps:[ 361 | {do:"with-attachment", steps:[ 362 | { 363 | do:"edit", 364 | options:[ 365 | "endpoint:/{{.ObjectID}}/edit-upload?attachmentId={{.AttachmentID}}", 366 | "delete:/{{.ObjectID}}/delete-upload?attachmentId={{.AttachmentID}}" 367 | ] 368 | form:{ 369 | type:"layout-vertical" 370 | label:"Edit Attachment", 371 | children:[ 372 | {type:"text", path:"label", label:"Label"} 373 | {type:"textarea", path:"description", label:"Description"} 374 | ] 375 | } 376 | } 377 | {do:"save"} 378 | {do:"refresh-page"} 379 | ]} 380 | ]} 381 | ] 382 | } 383 | 384 | delete-upload: { 385 | steps: [ 386 | {do:"as-confirmation", title:"Delete this Attachment?", message:"Are you sure you want to delete this attachment? There is NO UNDO.", submit:"DELETE"} 387 | {do:"delete-attachments"} 388 | {do:"refresh-page"} 389 | ] 390 | } 391 | 392 | publish: { 393 | roles: ["author"] 394 | steps: [ 395 | {do:"as-modal", steps:[ 396 | {do:"if", condition:"{{eq `true` (.QueryParam `republish`)}}", 397 | then:[ 398 | { 399 | do: edit 400 | form: { 401 | type: layout-vertical 402 | label: Re-Publish this Album? 403 | description: "If you've made changes to this album, you can send updates to all distribution channels.", 404 | children:[ 405 | {type:"select", path:"data.license", label:"License", options:{required:true, provider:"bandwagon-album.licenses"}} 406 | {type:"select", path:"isFeatured", label:"Feature on Home Page", options:{enum:[{value:"false", label:"NO. Do not show on my home page"}, {value:"true", label:"YES. Show on my home page"}]}} 407 | {type:"multiselect", path:"syndication", label:"Distribute to Streaming Stations", description:"Share this album with indie streaming services", options:{provider:"syndication-targets"}} 408 | ] 409 | }, 410 | options:["submit-label:Publish Album"] 411 | } 412 | ], 413 | else:[ 414 | { 415 | do: edit 416 | form: { 417 | type: layout-vertical 418 | label: Publish this Album? 419 | children:[ 420 | {type:"select", path:"data.license", label:"License", options:{required:true, provider:"bandwagon-album.licenses"}} 421 | {type:"select", path:"isFeatured", label:"Feature on Home Page", options:{enum:[{value:"false", label:"NO. Do not show on my home page"}, {value:"true", label:"YES. Show on my home page"}]}} 422 | {type:"multiselect", path:"syndication", label:"Distribute to Streaming Stations", description:"Share this album with indie streaming services.", options:{provider:"syndication-targets"}} 423 | ] 424 | }, 425 | options:["submit-label:Publish Album"] 426 | } 427 | ]} 428 | {do:"set-state", state:"published"} 429 | {do:"save-and-publish", outbox:"true", republish:true} 430 | {do:"search-index"} 431 | {do:"make-archive", token:"FULL-ALBUM", depth:1, json:true, attachments:true, metadata: [ 432 | 433 | // metadata for album attachments 434 | [], 435 | 436 | // metadata for song attachments 437 | [ 438 | {target:"cover", path:"parent.iconUrl"}, 439 | {target:"comment", path:"parent.attributedTo.profileUrl"} 440 | {target:"title", path:"stream.label"}, 441 | {target:"author", path:"parent.attributedTo.name"} 442 | {target:"artist", path:"parent.attributedTo.name"} 443 | {target:"album_artist", path:"parent.attributedTo.name"}, 444 | {target:"album", path:"parent.label"}, 445 | {target:"composer", path:"stream.data.composer"}, 446 | {target:"producer", path:"stream.data.producer"}, 447 | {target:"publisher", path:"stream.data.publisher"}, 448 | {target:"year", path:"parent.data.year"}, 449 | {target:"track", path:"stream.rank"}, 450 | {target:"genre", path:"stream.data.genre"} 451 | {target:"copyright", path:"parent.data.license"}, 452 | {target:"lyrics", path:"stream.data.lyrics"}, 453 | {target:"TISRC", path:"stream.data.isrc"}, 454 | ] 455 | ]} 456 | {do:"refresh-page"} 457 | ]} 458 | ] 459 | } 460 | 461 | unpublish: { 462 | roles: ["author"] 463 | steps: [ 464 | {do:"as-modal", steps:[ 465 | {do:"view-html"} 466 | /*{do:"as-confirmation", icon:"check", title:"Published to Internet", message:"This album has been published and is visible to everyone online.

You can un-publish it if you want to hide it. You'll still be able to edit and make changes to it, but others will not see it.", submit:"Un-Publish Album"} */ 467 | {do:"set-state", state:"unpublished"} 468 | {do:"unpublish", outbox:"true"} 469 | {do:"search-index"} 470 | {do:"delete-archive", token:"FULL-ALBUM"} 471 | {do:"refresh-page"} 472 | ]} 473 | ] 474 | } 475 | 476 | icon: { 477 | steps: [ 478 | {do:"redirect-to", url:"{{.IconURL}}"} 479 | ] 480 | } 481 | 482 | zip9348103752: { 483 | steps: [ 484 | {do:"get-archive", token:"FULL-ALBUM", depth:1, json:true, attachments:true, metadata: [ 485 | 486 | // metadata for album attachments 487 | [], 488 | 489 | // metadata for song attachments 490 | [ 491 | {target:"cover", path:"parent.iconUrl"}, 492 | {target:"comment", path:"parent.attributedTo.profileUrl"} 493 | {target:"title", path:"stream.label"}, 494 | {target:"author", path:"parent.attributedTo.name"} 495 | {target:"artist", path:"parent.attributedTo.name"} 496 | {target:"album_artist", path:"parent.attributedTo.name"}, 497 | {target:"album", path:"parent.label"}, 498 | {target:"composer", path:"stream.data.composer"}, 499 | {target:"producer", path:"stream.data.producer"}, 500 | {target:"publisher", path:"stream.data.publisher"}, 501 | {target:"year", path:"parent.data.year"}, 502 | {target:"track", path:"stream.rank"}, 503 | {target:"genre", path:"stream.data.genre"} 504 | {target:"copyright", path:"parent.data.license"}, 505 | {target:"lyrics", path:"stream.data.lyrics"}, 506 | {target:"TISRC", path:"stream.data.isrc"}, 507 | ] 508 | ]} 509 | ] 510 | } 511 | } 512 | } -------------------------------------------------------------------------------- /bandwagon-album/unpublish.html: -------------------------------------------------------------------------------- 1 |

{{icon "check"}} Published Album

2 | 3 |
4 | This album has been published and is visible to everyone online. 5 |
6 |
7 | If you've made changes, you can re-publish to streaming partners to send those updates to them. 8 |
9 |
10 | You can un-publish it if you want to hide it. You'll still be able to edit and make changes to it, but others will not see it. 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /bandwagon-album/upload.html: -------------------------------------------------------------------------------- 1 |
2 |

{{icon "upload"}} Attach a File

3 |

Attach files to this album, like liner notes or album artwork. Attached files are public, and downloadable by anyone online.

4 |
-------------------------------------------------------------------------------- /bandwagon-album/view.html: -------------------------------------------------------------------------------- 1 | {{- $streamID := .StreamID -}} 2 | {{- $tags := .Tags -}} 3 | {{- $songs := .Children.Top60.ByRank.Slice -}} 4 | {{- $canEdit := .UserCan "edit" -}} 5 | {{- $isPlayable := false -}} 6 | {{- $links := .Data "links" -}} 7 | {{- range $index, $song := $songs -}} 8 | {{- if ne nil $song.Data.attachmentId -}} 9 | {{- $isPlayable = true -}} 10 | {{- end -}} 11 | {{- end -}} 12 | {{- $checkoutLabel := first (.Data "checkout label") "Buy Now" -}} 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{- $year := .Data "year" -}} 34 | {{- range $index, $song := $songs -}} 35 | 36 | 37 | {{- end -}} 38 | 39 | {{- if not .IsPublished -}} 40 | {{- if $songs.NotEmpty -}} 41 |
42 | {{icon "check-circle"}} 43 | 44 | This album IS READY TO PUBLISH on the social web. 45 | Click here to publish it now 46 |
47 | {{- end -}} 48 | {{- end -}} 49 |
50 | 51 | 52 |
53 |
54 | {{- if ne "" .IconURL -}} 55 | 56 | {{- else if $canEdit -}} 57 |
58 | Click here to add album art 59 |
60 | {{- end -}} 61 |
62 |
63 |
64 |

65 | Album Name: 66 | {{.Label}} 67 |

68 | 73 | 74 | {{- if .IsPublished -}} 75 | 76 |
77 | 78 |
79 | {{- if $isPlayable -}} 80 | 81 | 82 | 83 | 84 | {{- else if and $songs.NotEmpty $canEdit -}} 85 | 86 |
Upload Songs to Enable Playback
87 | {{- end -}} 88 | 89 | 94 |
95 | 96 |
97 | 98 | 99 | 100 |
101 | {{- end -}} 102 |
103 |
104 | 105 | 106 |
107 | 108 | 109 |
110 | 111 | {{- if $songs.NotEmpty -}} 112 | 113 |
114 |
115 | {{- range $index, $song := $songs -}} 116 | {{- $isplayable := ne nil $song.Data.attachmentId -}} 117 |
118 |
125 | 126 | 127 | {{- if and $canEdit (gt $songs.Length 1) -}} 128 |
129 | {{icon "drag-handle"}} 130 |
131 | {{- end -}} 132 | 133 | 150 |
151 | {{$song.Label}} 152 | {{ if eq true $song.Data.explicit }}{{icon "explicit-fill"}}{{ end }} 153 |
154 |
155 |
156 | Track Info 157 | {{- if $canEdit -}} 158 | 159 | {{- end -}} 160 |
161 |
162 | 163 | {{- end -}} 164 | 165 | {{- if $canEdit -}} 166 | 175 | {{- end -}} 176 | 177 |
178 |
179 | 180 | {{- else if $canEdit -}} 181 | 182 |
183 |
184 | 185 |
186 | 189 |
You'll need to add at least one song to this album before you can publish it.
190 |
191 |
192 |
193 | {{- end -}} 194 | 195 |
196 | 197 |
198 |
199 | 200 | {{- if $tags.NotEmpty -}} 201 |
202 | {{- range $index, $tag := $tags -}} 203 | {{- if eq "Hashtag" $tag.Type}} 204 | #{{$tag.Name}} 205 | {{- end -}} 206 | {{- end -}} 207 |
208 | {{- end -}} 209 | 210 | {{- if ne "" .Summary -}} 211 |
212 |

Album Notes

213 | {{.Summary | markdown}} 214 |
215 | {{- end -}} 216 | 217 | {{- if $links.NotEmpty -}} 218 | 274 | {{- end -}} 275 | 276 | {{- $uploads := .AttachmentsByCategory "other" -}} 277 | {{- if or $uploads.NotEmpty $canEdit -}} 278 |
279 |
280 |

Other Files

281 |
282 | 283 | {{- if $canEdit -}} 284 |
285 |
Attachments like liner notes or album art that anyone can access.
286 | {{icon "upload"}} Attach a File 287 |
288 | {{- end -}} 289 | 290 | {{- if $uploads.NotEmpty -}} 291 |
292 | {{- range $index, $upload := $uploads -}} 293 |
294 |
295 | {{.Label}}
296 | {{.Description}} 297 |
298 | {{- if $canEdit -}} 299 |
300 | 301 |
302 | {{- end -}} 303 |
304 | {{- end -}} 305 |
306 | {{- end -}} 307 |
308 | {{- end -}} 309 | 310 |
311 | {{- $license := .DataString "license"}} 312 | {{- if eq "COPYRIGHT" $license -}} 313 |

License

314 |
315 | © Copyright 316 | {{- if ne nil (.Data "year") }} 317 | {{.Data "year"}}. 318 | {{- else -}} 319 | . 320 | {{- end }} 321 | All Rights Reserved. 322 |
323 | {{- else if ne "" $license -}} 324 | {{- $licenses := .Dataset "bandwagon-album.licenses" -}} 325 | {{- $licenseInfo := $licenses.Value $license -}} 326 |

License

327 | 336 | {{- end -}} 337 |
338 | 339 |
340 | 341 |
342 | 343 | {{- if $songs.NotEmpty -}} 344 | 348 | 349 | 350 | 362 | 363 | {{- if $canEdit -}} 364 | 365 | 366 | 367 | 374 | 375 | 386 | {{- end -}} 387 | 388 | {{- end -}} 389 | 390 |
391 | {{- if $canEdit -}} 392 | 393 | 396 | 397 | {{- if .IsPublished -}} 398 | 399 | {{- else if $songs.NotEmpty -}} 400 | 401 | {{- end -}} 402 | {{- end -}} 403 | 404 | {{- if .UserCan "zip" -}} 405 | Download 406 | {{- end -}} 407 |
408 | 409 |
410 | 411 | {{- if $canEdit -}} 412 |
413 |
414 |
415 | 416 |
417 |
418 |
419 |
420 | {{- end -}} 421 |
-------------------------------------------------------------------------------- /bandwagon-common/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-common 3 | templateRole: album 4 | containedBy: [] 5 | extends: [] 6 | label: Bandwagon Common 7 | description: Common elements for Bandwagon apps 8 | 9 | datasets: { 10 | "licenses": [ 11 | {value:"COPYRIGHT", label:"All Rights Reserved", icon:"copyright"}, 12 | {value:"CC-BY", label:"Creative Commons - Attribution", href:"https://creativecommons.org/licenses/by/4.0/", icon:"cc-by"} 13 | {value:"CC-BY-NC", label:"Creative Commons - Attribution - Non Commercial", href:"https://creativecommons.org/licenses/by-nc/4.0/", icon:"cc-by-nc"} 14 | {value:"CC-BY-NC-ND", label:"Creative Commons - Attribution - Non Commercial - No Derivatives", href:"https://creativecommons.org/licenses/by-nc-nd/4.0/", icon:"cc-by-nc-nd"} 15 | {value:"CC-BY-NC-SA", label:"Creative Commons - Attribution - Non Commercial - Share Alike", href:"https://creativecommons.org/licenses/by-nc-sa/4.0/", icon:"cc-by-nc-sa"} 16 | {value:"CC-BY-ND", label:"Creative Commons - Attribution - No Derivatives ", href:"https://creativecommons.org/licenses/by-nd/4.0/", icon:"cc-by-nd"} 17 | {value:"CC-BY-SA", label:"Creative Commons - Attribution - Share Alike", href:"https://creativecommons.org/licenses/by-sa/4.0/", icon:"cc-by-sa"} 18 | {value:"CC0", label:"Creative Commons Zero - No Rights Reserved", href:"https://creativecommons.org/public-domain/cc0/", icon:"cc0"}, 19 | {value:"PUBLIC-DOMAIN", label:"Public Domain", href:"https://creativecommons.org/publicdomain/mark/1.0/", icon:"public-domain"} 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /bandwagon-event/edit.html: -------------------------------------------------------------------------------- 1 |

{{icon "calendar"}} {{.Label}}

-------------------------------------------------------------------------------- /bandwagon-event/stylesheet.html: -------------------------------------------------------------------------------- 1 | {{- $artist := .ParentOutbox "view" -}} 2 | {{- $eventColors := .Data "color" -}} 3 | 4 | {{- $bodyColor := $eventColors.body -}} 5 | {{- $pageColor := $eventColors.page -}} 6 | {{- $buttonColor := $eventColors.button -}} 7 | 8 | body { 9 | 10 | {{ if ne nil $bodyColor -}} 11 | --body-background: {{$bodyColor}}; 12 | {{- end }} 13 | 14 | {{ if ne nil $pageColor -}} 15 | {{- $pageColorParsed := parseColor $pageColor -}} 16 | --page-background: {{$pageColorParsed}}; 17 | --page-border: {{$pageColorParsed.Darken 10}}; 18 | --heading-color: {{$pageColorParsed.Text}}; 19 | --text-color: {{$pageColorParsed.TextExt}}; 20 | 21 | {{if $pageColorParsed.IsLight }} 22 | --white: rgb(255, 255, 255); 23 | --gray00: rgb(255, 255, 255); 24 | --gray01: rgba(0, 0, 0, 0.01); 25 | --gray02: rgba(0, 0, 0, 0.02); 26 | --gray03: rgba(0, 0, 0, 0.03); 27 | --gray04: rgba(0, 0, 0, 0.04); 28 | --gray05: rgba(0, 0, 0, 0.05); 29 | --gray10: rgba(0, 0, 0, 0.10); 30 | --gray15: rgba(0, 0, 0, 0.15); 31 | --gray20: rgba(0, 0, 0, 0.20); 32 | --gray30: rgba(0, 0, 0, 0.30); 33 | --gray40: rgba(0, 0, 0, 0.40); 34 | --gray50: rgba(0, 0, 0, 0.50); 35 | --gray60: rgba(0, 0, 0, 0.60); 36 | --gray70: rgba(0, 0, 0, 0.70); 37 | --gray80: rgba(0, 0, 0, 0.80); 38 | --gray90: rgba(0, 0, 0, 0.90); 39 | --gray100: rgb(0, 0, 0); 40 | --black: rgb(0, 0, 0); 41 | {{- else }} 42 | --white: rgb(0, 0, 0) ; 43 | --gray00: rgb(0, 0, 0) ; 44 | --gray01: rgba(255, 255, 255, 0.01); 45 | --gray02: rgba(255, 255, 255, 0.02); 46 | --gray03: rgba(255, 255, 255, 0.03); 47 | --gray04: rgba(255, 255, 255, 0.04); 48 | --gray05: rgba(255, 255, 255, 0.05); 49 | --gray10: rgba(255, 255, 255, 0.10); 50 | --gray15: rgba(255, 255, 255, 0.15); 51 | --gray20: rgba(255, 255, 255, 0.20); 52 | --gray30: rgba(255, 255, 255, 0.30); 53 | --gray40: rgba(255, 255, 255, 0.40); 54 | --gray50: rgba(255, 255, 255, 0.50); 55 | --gray60: rgba(255, 255, 255, 0.60); 56 | --gray70: rgba(255, 255, 255, 0.70); 57 | --gray80: rgba(255, 255, 255, 0.80); 58 | --gray90: rgba(255, 255, 255, 0.90); 59 | --gray100: rgb(255, 255, 255); 60 | --black: rgb(255, 255, 255); 61 | {{- end -}} 62 | 63 | {{- end }} 64 | 65 | {{ if ne nil $buttonColor -}} 66 | {{- $buttonColorParsed := parseColor $buttonColor -}} 67 | {{- $buttonText := $buttonColorParsed.Text }} 68 | {{- $buttonColorHover := ($buttonColorParsed.Lighten 10) -}} 69 | {{- $buttonTextHover := $buttonColorHover.Text }} 70 | 71 | --button-primary-background: {{$buttonColor}}; 72 | --button-primary-background-hover: {{$buttonColorHover}}; 73 | --button-primary-color: {{$buttonText}}; 74 | --button-primary-color-hover: {{$buttonTextHover}}; 75 | --link-color: {{$buttonColor}}; 76 | --link-color-hover: {{$buttonColorHover}}; 77 | {{- end }} 78 | 79 | } 80 | 81 | {{ $artist.Data "stylesheet"}} -------------------------------------------------------------------------------- /bandwagon-event/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-event 3 | templateRole: event 4 | model: Stream 5 | containedBy: ["outbox"] 6 | extends: ["base-intent"] 7 | icon: calendar 8 | label: Event 9 | description: Contains event details 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string", required:true} 14 | summary: {type:"string"} 15 | iconUrl: {type:"string", format:"url"} 16 | startDate: {type:"object", properties: { 17 | date: {type:"string", format:"date"} 18 | time: {type:"string", format:"time"} 19 | }} 20 | places: { 21 | type:"array", 22 | items: { 23 | type:"object" 24 | properties: { 25 | "name": {type:"string"} 26 | "fullAddress": {type:"string"} 27 | "latitude": {type:"number"} 28 | "longitude": {type:"number"} 29 | } 30 | } 31 | } 32 | data: { 33 | type:object 34 | properties: { 35 | tags: {type:"string"} 36 | bannerUrl: {type:"string", format:"url"} 37 | flyerUrl: {type:"string", format:"url"} 38 | city: {type:"string"} 39 | venue: {type:"string"} 40 | websiteLabel: {type:"string"} 41 | website: {type:"string", format:"url"} 42 | color: {type:"object", properties: { 43 | body: {type:"string", format:"color"}, 44 | page: {type:"string", format:"color"}, 45 | button: {type:"string", format:"color"} 46 | }} 47 | } 48 | } 49 | } 50 | } 51 | 52 | socialRole: Event 53 | socialRules: [ 54 | {target:"type", value:["Event","Article"]} 55 | {target:"name", expression:"{{.Data.city}} {{.Data.date}}"}, 56 | {target:"summary", expression:"{{.Data.venue}}"}, 57 | ] 58 | 59 | search: { 60 | type: "Event" 61 | iconUrl:"{{.IconURL}}" 62 | text: "{{.Label}} {{.AttributedTo.Name}} {{index .Data `year`}}" 63 | } 64 | 65 | tagPaths: ["data.tags"] 66 | 67 | actions: { 68 | create: { 69 | roles:["author"] 70 | steps: [ 71 | {do:"as-modal", steps: [ 72 | {do:"set-data", from-url:["isFeatured"]} 73 | { 74 | do:edit 75 | form: { 76 | label:"+ Add a Show" 77 | type:layout-tabs 78 | children: [ 79 | { 80 | label: Show Date 81 | type:layout-vertical 82 | children: [ 83 | {type:"text", path:"label", label:"Name"} 84 | {type:"text", path:"data.tags", label:"Tags", description:"Enter #Hashtags separated by spaces."} 85 | {type:"date", path:"startDate.date", label:"Date"} 86 | {type:"time", path:"startDate.time", label:"Start Time"} 87 | {type:"toggle", path:"isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured"}} 88 | ] 89 | } 90 | { 91 | label: Description 92 | type:layout-vertical 93 | children: [ 94 | {type:"textarea", path:"summary", description:"Shows on event details. Markdown is okay.", options:{rows:10}} 95 | ] 96 | } 97 | { 98 | label: Venue 99 | type:layout-vertical 100 | children: [ 101 | {type:"text", path:"place.0.name", label:"Venue Name", description:"Just a label. Shows on event cards"} 102 | {type:"text", path:"data.city", label:"City/State", description:"Just a label. Shows on event cards"} 103 | {type:"textarea", path:"places.0.fullAddress", label:"Physical Address", description:"Use an accurate address. Used to generate maps."} 104 | ] 105 | } 106 | { 107 | label: Tickets 108 | type:layout-vertical 109 | children: [ 110 | {type:"text", path:"data.website", label:"Ticketing Website", description: "Website where fans can buy tickets"} 111 | {type:"text", path:"data.websiteLabel", label:"Button Text", description:"The label to display on your website", options:{placeholder:"Get Tickets"}} 112 | ] 113 | } 114 | { 115 | type:layout-vertical 116 | label: Colors 117 | children: [ 118 | {type:"colorpicker", path:"data.color.body", label:"Window Background"} 119 | {type:"colorpicker", path:"data.color.page", label:"Page Background"} 120 | {type:"colorpicker", path:"data.color.button", label:"Links and Buttons"} 121 | ] 122 | } 123 | { 124 | label: Uploads 125 | type:layout-vertical 126 | children: [ 127 | {type:"upload", path:"iconUrl", label:"Card Image", description:"Shows on event cards. Square recommended.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-icon"}} 128 | {type:"upload", path:"data.bannerUrl", label:"Banner Image", description:"Shows on top of event page. Optional.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-image"}} 129 | {type:"upload", path:"data.flyerUrl", label:"Flyer", options:{delete:"/{{.StreamID}}/delete-flyer"}} 130 | ] 131 | } 132 | ] 133 | } 134 | } 135 | ]} 136 | 137 | {do:"upload-attachments", category:"icon", fieldname:"iconUrl", download-path:"iconUrl", rules:{width:800, height:800, types:["webp"]}} 138 | {do:"upload-attachments", category:"image", fieldname:"data.bannerUrl", download-path:"data.bannerUrl", rules:{}} 139 | {do:"upload-attachments", category:"flyer", fieldname:"data.flyerUrl", download-path:"data.flyerUrl", rules:{}} 140 | 141 | {do:"process-tags", paths:"data.tags"} 142 | {do:"save", outbox:"true"} 143 | {do:"forward-to", url:"/{{.StreamID}}"} 144 | ] 145 | } 146 | view: {do:"view-html"} 147 | edit: { 148 | roles: ["author"] 149 | steps: [ 150 | {do:"as-modal", steps: [ 151 | {do:"view-html"} 152 | { 153 | do:edit 154 | options:["delete:/{{.StreamID}}/delete"] 155 | form: { 156 | type:layout-tabs 157 | children: [ 158 | { 159 | label: Show Date 160 | type:layout-vertical 161 | children: [ 162 | {type:"text", path:"label", label:"Name"} 163 | {type:"text", path:"data.tags", label:"Tags", description:"Enter #Hashtags separated by spaces."} 164 | {type:"date", path:"startDate.date", label:"Date"} 165 | {type:"time", path:"startDate.time", label:"Start Time"} 166 | {type:"toggle", path:"isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured"}} 167 | ] 168 | } 169 | { 170 | label: Description 171 | type:layout-vertical 172 | children: [ 173 | {type:"textarea", path:"summary", description:"Shows on event details. Markdown is okay.", options:{rows:10}} 174 | ] 175 | } 176 | { 177 | label: Venue 178 | type:layout-vertical 179 | children: [ 180 | {type:"text", path:"places.0.name", label:"Venue Name", description:"Just a label. Shows on event cards"} 181 | {type:"text", path:"data.city", label:"City/State", description:"Just a label. Shows on event cards"} 182 | {type:"textarea", path:"places.0.fullAddress", label:"Physical Address", description:"Use an accurate address. Used to generate maps."} 183 | ] 184 | } 185 | { 186 | label: Tickets 187 | type:layout-vertical 188 | children: [ 189 | {type:"text", path:"data.website", label:"Ticketing Website", description: "Website where fans can buy tickets"} 190 | {type:"text", path:"data.websiteLabel", label:"Button Text", description:"The label to display on your website", options:{placeholder:"Get Tickets"}} 191 | ] 192 | } 193 | { 194 | type:layout-vertical 195 | label: Colors 196 | children: [ 197 | {type:"colorpicker", path:"data.color.body", label:"Window Background"} 198 | {type:"colorpicker", path:"data.color.page", label:"Page Background"} 199 | {type:"colorpicker", path:"data.color.button", label:"Links and Buttons"} 200 | ] 201 | } 202 | { 203 | label: Uploads 204 | type:layout-vertical 205 | children: [ 206 | {type:"upload", path:"iconUrl", label:"Card Image", description:"Shows on event cards. Square recommended.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-icon"}} 207 | {type:"upload", path:"data.bannerUrl", label:"Banner Image", description:"Shows on top of event page. Optional.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-image"}} 208 | {type:"upload", path:"data.flyerUrl", label:"Flyer", options:{delete:"/{{.StreamID}}/delete-flyer"}} 209 | ] 210 | } 211 | ] 212 | } 213 | } 214 | ]} 215 | 216 | {do:"upload-attachments", category:"icon", fieldname:"iconUrl", download-path:"iconUrl", rules:{width:800, height:800, types:["webp"]}} 217 | {do:"upload-attachments", category:"image", fieldname:"data.bannerUrl", download-path:"data.bannerUrl", rules:{}} 218 | {do:"upload-attachments", category:"flyer", fieldname:"data.flyerUrl", download-path:"data.flyerUrl", rules:{}} 219 | 220 | {do:"process-tags", paths:"data.tags"} 221 | {do:"if", condition: "{{.IsPublished}}", 222 | then: [ 223 | {do:"save-and-publish"} 224 | {do:"search-index"} 225 | ], 226 | else: [ 227 | {do:"save"} 228 | ] 229 | } 230 | {do:"refresh-page"} 231 | ] 232 | } 233 | 234 | publish: { 235 | roles: ["author"] 236 | steps: [ 237 | {do:"as-modal", steps:[ 238 | { 239 | do: edit 240 | form: { 241 | type: layout-vertical 242 | label: Publish this Show? 243 | children:[ 244 | {type:"toggle", path:"isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured"}} 245 | ] 246 | }, 247 | options:["submit-label:Publish Show"] 248 | } 249 | {do:"set-state", state:"published"} 250 | {do:"save-and-publish", outbox:"true"} 251 | {do:"search-index"} 252 | {do:"refresh-page"} 253 | ]} 254 | ] 255 | } 256 | 257 | unpublish: { 258 | roles: ["author"] 259 | steps: [ 260 | {do:"as-confirmation", title:"Un-Publish this Show?", message:"Un-Publishing this show will hide it everyone online. You'll still be able to edit and make changes to it, but others will not see it.", submit:"Un-Publish from Website"} 261 | {do:"set-state", state:"unpublished"} 262 | {do:"unpublish", outbox:"true"} 263 | {do:"search-index"} 264 | {do:"refresh-page"} 265 | ] 266 | } 267 | 268 | stylesheet: { 269 | steps:[ 270 | {do:"view-css"} 271 | ] 272 | } 273 | delete: { 274 | roles: ["author"] 275 | steps: [ 276 | {do:"delete"} 277 | {do:"unpublish", outbox:"true"} 278 | {do:"refresh-page"} 279 | ] 280 | } 281 | 282 | delete-icon: { 283 | roles: ["author"] 284 | steps: [ 285 | {do:"delete-attachments", fieldname:"iconUrl"} 286 | {do:"save"} 287 | ] 288 | } 289 | delete-banner: { 290 | roles: ["author"] 291 | steps: [ 292 | {do:"delete-attachments", fieldname:"data.bannerUrl"} 293 | {do:"save"} 294 | ] 295 | } 296 | delete-flyer: { 297 | roles: ["author"] 298 | steps: [ 299 | {do:"delete-attachments", fieldname:"data.flyerUrl"} 300 | {do:"save"} 301 | ] 302 | } 303 | } 304 | } -------------------------------------------------------------------------------- /bandwagon-event/view.html: -------------------------------------------------------------------------------- 1 | {{- $canEdit := .UserCan "edit" -}} 2 | {{- $ticketURL := .Data "website" -}} 3 | {{- $bannerURL := .Data "bannerUrl" -}} 4 | {{- $flyerURL := .Data "flyerUrl" -}} 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{- if not .IsPublished -}} 19 |
20 | {{icon "alert-fill"}} 21 | This show HAS NOT BEEN published yet and won't be seen by others. 22 | Click here to publish it 23 |
24 | {{- end -}} 25 | 26 | {{- if ne "" $bannerURL -}} 27 | 28 | {{- end -}} 29 | 30 |
31 | 32 |
33 | {{- if ne "" .IconURL -}} 34 |
35 | 36 |
37 | {{- end -}} 38 |
39 |
40 |

{{.Label}}

41 | 42 |

43 | {{- .StartDate | longDate -}} 44 | at {{ .StartDate | shortTime -}} 45 |

46 | 47 |
48 |
49 | {{- if ne "" $ticketURL -}} 50 | {{- $ticketLabel := .Data "websiteLabel" -}} 51 | {{- $ticketLabel := first $ticketLabel "Get Tickets" -}} 52 | {{ $ticketLabel }} 53 | {{- end -}} 54 | 55 | 56 | 57 | {{- if ne nil $flyerURL -}} 58 | Download 59 | {{- end -}} 60 | 61 |
62 | 63 | {{- if ne "" .Summary -}} 64 |
65 |
{{.Summary | markdown}}
66 | {{- end -}} 67 | 68 | {{- if .Tags.NotEmpty -}} 69 |
70 |
71 | {{- range $index, $tag := .Tags -}} 72 | #{{$tag.Name}} 73 | {{- end -}} 74 |
75 | {{- end -}} 76 |
77 |
78 | 79 | {{- if .Places.NotEmpty -}} 80 | 81 | {{- $venue := .Places.First -}} 82 | 83 | {{- if $venue.NotEmpty -}} 84 | 85 | {{- $hasGeocode := $venue.HasGeocode -}} 86 | 87 |
88 |
89 | 90 |
91 | {{if $hasGeocode}} 92 |
93 |
94 | Maps © OpenStreetMap.   95 | Icon by Icons8 96 |
97 | 98 | {{- end -}} 99 |
100 |
101 |
102 | 103 |
104 |

{{$venue.Name}}

105 |
{{- $venue.FullAddress | text -}}
106 |
107 | 108 | {{if $hasGeocode}} 109 |
Open With:
110 | 115 | 116 |
117 | 118 | 119 |
120 | 121 | 122 | 123 | 157 | 158 | 159 | 160 | {{- end -}} 161 | 162 |
163 |
164 | {{- end -}} 165 | 166 | {{- end -}} 167 | 168 | {{- if $canEdit -}} 169 |
170 | 171 | {{- if .IsPublished -}} 172 | 173 | {{- else -}} 174 | 175 | {{- end -}} 176 |
177 | 178 |
179 |
180 |
181 | {{- end -}} 182 | 183 |
184 | 185 | 186 |
-------------------------------------------------------------------------------- /bandwagon-news/edit.html: -------------------------------------------------------------------------------- 1 |

{{.Label}}

2 | 3 |
Published: {{.PublishDate | shortDate}}
-------------------------------------------------------------------------------- /bandwagon-news/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-news 3 | model: Stream 4 | templateRole: Note 5 | label: News Item 6 | extends: ["base-intent"] 7 | containedBy: ["outbox"] 8 | schema: { 9 | type:object 10 | properties: { 11 | title: {type:"string"} 12 | summary: {type:"string"} 13 | content: {type:"object", properties:{ 14 | "type": {type:"string"} 15 | "raw": {type:"string"} 16 | "html": {type:"string"} 17 | }} 18 | data: {type:"object", properties:{ 19 | linkText: {type:"string"} 20 | bannerUrl: {type:"string", format:"url"} 21 | }} 22 | } 23 | } 24 | 25 | socialRole: Article 26 | socialRules: [ 27 | {target:"content", expression:"{{.SummaryOrContent}}"} 28 | ] 29 | 30 | search: { 31 | type:Article 32 | iconUrl:"" 33 | summary:"" 34 | text: "{{.Label}} {{.AttributedTo.Name}} {{.Summary}} {{.Content.HTML}}" 35 | } 36 | 37 | tagPaths: ["summary", "content.html"] 38 | 39 | actions: { 40 | 41 | create: { 42 | roles: ["author"] 43 | steps: [ 44 | {do:"as-modal", options:["class:large"], steps:[ 45 | {do:"set-data", from-url:["isFeatured"]} 46 | { 47 | do:edit 48 | form: { 49 | type:layout-tabs 50 | label:"+ Add News" 51 | children: [ 52 | { 53 | type:layout-vertical 54 | label: News 55 | children: [ 56 | {type:"text", path: "label", label: "Title"} 57 | {type:"textarea", path: "summary", label: "Summary", description:"Shows on list pages.", options:{rows:5}} 58 | {type:"text", path:"data.linkText", label:"Link Text", description:"Text for the link to the full article.", options:{placeholder:"Read More"}} 59 | {type:"toggle", path: "isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured"}} 60 | ] 61 | } 62 | { 63 | type:layout-vertical 64 | label:"Content" 65 | children:[ 66 | {type:"textarea", path: "content.raw", label: "Content", description:"Shows on news article page only. Markdown is okay.", options:{rows:12}} 67 | ] 68 | } 69 | { 70 | type:layout-vertical 71 | label:"Uploads" 72 | children:[ 73 | {type:"upload", path:"iconUrl", label:"Card Image", description:"Shows on event cards. Square recommended.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-icon"}} 74 | {type:"upload", path:"data.bannerUrl", label:"Banner Image", description:"Shows on top of event page. Optional.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-image"}} 75 | ] 76 | } 77 | ] 78 | } 79 | } 80 | 81 | {do:"upload-attachments", category:"icon", fieldname:"iconUrl", download-path:"iconUrl", rules:{width:800, height:800, types:["webp"]}} 82 | {do:"upload-attachments", category:"image", fieldname:"data.bannerUrl", download-path:"data.bannerUrl", rules:{}} 83 | 84 | {do:"edit-content", field:"content.raw", format:"MARKDOWN"} 85 | {do:"process-tags", paths:"data.tags"} 86 | {do:"save"} 87 | {do:"search-index"} 88 | {do:"forward-to", url:"/{{.StreamID}}"} 89 | ]} 90 | ] 91 | } 92 | 93 | edit: { 94 | roles: ["author"] 95 | steps: [ 96 | {do:"as-modal", options:["class:large"], steps:[ 97 | {do:"view-html"} 98 | { 99 | do:"edit" 100 | options: ["delete:/{{.StreamID}}/delete"] 101 | form: { 102 | type:layout-tabs 103 | children: [ 104 | { 105 | type:layout-vertical 106 | label: News 107 | children: [ 108 | {type:"text", path: "label", label: "Title"} 109 | {type:"textarea", path: "summary", label: "Summary", description:"Shows on list pages.", options:{rows:5}} 110 | {type:"text", path:"data.linkText", label:"Link Text", description:"Text for the link to the full article.", options:{placeholder:"Read More"}} 111 | {type:"toggle", path: "isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured"}} 112 | ] 113 | } 114 | { 115 | type:layout-vertical 116 | label:"Content" 117 | children:[ 118 | {type:"textarea", path: "content.raw", label: "Content", description:"Shows on news article page only. Markdown is okay.", options:{rows:12}} 119 | ] 120 | } 121 | { 122 | type:layout-vertical 123 | label:"Uploads" 124 | children:[ 125 | {type:"upload", path:"iconUrl", label:"Card Image", description:"Shows on event cards. Square recommended.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-icon"}} 126 | {type:"upload", path:"data.bannerUrl", label:"Banner Image", description:"Shows on top of event page. Optional.", options:{accept:"image/*", delete:"/{{.StreamID}}/delete-image"}} 127 | ] 128 | } 129 | ] 130 | } 131 | } 132 | 133 | {do:"upload-attachments", category:"icon", fieldname:"iconUrl", download-path:"iconUrl", rules:{width:800, height:800, types:["webp"]}} 134 | {do:"upload-attachments", category:"image", fieldname:"data.bannerUrl", download-path:"data.bannerUrl", rules:{}} 135 | 136 | {do:"edit-content", field:"content.raw", format:"MARKDOWN"} 137 | {do:"process-tags", paths:"data.tags"} 138 | {do:"if", condition: "{{.IsPublished}}", 139 | then: [ 140 | {do:"save-and-publish"} 141 | {do:"search-index"} 142 | ], 143 | else: [ 144 | {do:"save"} 145 | ] 146 | } 147 | {do:"refresh-page"} 148 | ]} 149 | ] 150 | } 151 | 152 | view: { 153 | steps:[ 154 | {do:"view-html"} 155 | ] 156 | } 157 | 158 | delete: { 159 | roles: ["author"] 160 | steps: [ 161 | {do:"delete"} 162 | {do:"unpublish", outbox:"true"} 163 | {do:"refresh-page"} 164 | ] 165 | } 166 | 167 | publish: { 168 | roles: ["author"] 169 | steps: [ 170 | {do:"as-modal", steps:[ 171 | { 172 | do: edit 173 | form: { 174 | type: layout-vertical 175 | label: Publish this News? 176 | children:[ 177 | {type:"toggle", path:"isFeatured", options:{true-text:"Featured (shows on home page)", false-text:"Featured"}} 178 | ] 179 | }, 180 | options:["submit-label:Publish"] 181 | } 182 | {do:"set-state", state:"published"} 183 | {do:"save-and-publish", outbox:"true"} 184 | {do:"search-index"} 185 | {do:"refresh-page"} 186 | ]} 187 | ] 188 | } 189 | 190 | unpublish: { 191 | roles: ["author"] 192 | steps: [ 193 | {do:"as-confirmation", title:"Un-Publish this News?", message:"Un-Publishing this news will hide it everyone online. You'll still be able to edit and make changes to it, but others will not see it.", submit:"Un-Publish from Website"} 194 | {do:"set-state", state:"unpublished"} 195 | {do:"unpublish", outbox:"true"} 196 | {do:"search-index"} 197 | {do:"refresh-page"} 198 | ] 199 | } 200 | 201 | } 202 | } -------------------------------------------------------------------------------- /bandwagon-news/view.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{- if not .IsPublished -}} 14 |
15 | {{icon "alert-fill"}} 16 | This news HAS NOT BEEN published yet and won't be seen by others. 17 | Click here to publish it 18 |
19 | {{- end -}} 20 | 21 |
22 | 23 | {{- $bannerURL := .Data "bannerUrl" -}} 24 | {{- if ne "" $bannerURL -}} 25 | 26 | {{- end -}} 27 | 28 |
29 | 30 |
31 |

{{.Label}}

32 |
33 | {{.AttributedTo.Name}} 34 |   35 | {{.PublishDate | shortDate}} 36 |
37 | 38 |
39 | 40 | 41 |
42 |
{{.ContentHTML}}
43 |
44 | 45 | {{- if .UserCan "edit" -}} 46 |
47 | 48 | {{- if .IsPublished -}} 49 | 50 | {{- else -}} 51 | 52 | {{- end -}} 53 |
54 | 55 |
56 |
57 |
58 | {{- end -}} 59 | 60 |
61 | 62 |
-------------------------------------------------------------------------------- /bandwagon-outbox/albums.html: -------------------------------------------------------------------------------- 1 | {{- $albums := (.Outbox.Where "templateId" "bandwagon-album").ByRankAlt.Slice -}} 2 | {{- $isMyself := .IsMyself -}} 3 | {{- $label := first (.Data "label-albums") "Albums" -}} 4 | {{- $description := .Data "description-albums" -}} 5 | 6 | 7 |
8 | 9 | {{- template "header" . -}} 10 | 11 |
12 | 13 |
14 | 15 | 16 | {{.DisplayName}} 17 | 18 | · {{$label}} 19 | 20 | {{- if $isMyself -}} 21 |
22 | 23 | 24 |
25 | {{- end -}} 26 | 27 |
28 | 29 | {{- if ne "" $description -}} 30 |
{{$description | markdown}}
31 | {{- end -}} 32 | 33 | {{- if $isMyself -}} 34 |
35 | {{- end -}} 36 | 37 |
38 | 39 |
40 | {{- range $albums -}} 41 | {{- if or .IsPublished $isMyself -}} 42 |
43 | 44 | 45 |
46 |
{{.Label}}
47 |
{{.Data.year}}
48 |
49 |
50 | {{- if not .IsPublished -}} 51 |
52 | UNPUBLISHED 53 |
54 | {{- end -}} 55 | 56 | {{- if $isMyself -}} 57 | 58 | 59 | 60 | {{- end -}} 61 | 62 |
63 | {{- end -}} 64 | 65 | {{- end -}} 66 | 67 |
68 | 69 |
70 | 71 | {{- if $isMyself -}} 72 |
73 | 74 | 75 | 76 | 86 | 87 | 107 | 108 | {{- end -}} 109 | 110 |
111 | 112 |
113 | 114 |
-------------------------------------------------------------------------------- /bandwagon-outbox/coming-soon.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Coming Soon

4 |

{{.DisplayName}}

5 | 6 |

This page is under construction. Please check back in the future.

7 | 8 |
-------------------------------------------------------------------------------- /bandwagon-outbox/events.html: -------------------------------------------------------------------------------- 1 | {{- $events := .Outbox.ByStartDate -}} 2 | {{- $events := $events.Where "templateId" "bandwagon-event" -}} 3 | {{- $showCurrent := ne "past" (.QueryParam "view") -}} 4 | 5 | {{- if $showCurrent -}} 6 | {{- $events = $events.WhereGT "startDate" today -}} 7 | {{- else -}} 8 | {{- $events = $events.WhereLT "startDate" today -}} 9 | {{- end -}} 10 | 11 | {{- $events := $events.Slice -}} 12 | {{- $root := . -}} 13 | {{- $isMyself := .IsMyself -}} 14 | {{- $label := first (.Data "label-events") "Shows" -}} 15 | {{- $description := .Data "description-events" -}} 16 | 17 |
18 | 19 | {{- template "header" . -}} 20 | 21 |
22 | 23 |
24 | 25 | 26 | {{.DisplayName}} 27 | 28 | · {{$label}} 29 | 30 | {{- if $isMyself -}} 31 |
32 | 33 | 34 |
35 | {{- end -}} 36 | 37 |
38 | 39 | {{- if ne "" $description -}} 40 |
{{$description | markdown}}
41 | {{- end -}} 42 | 43 |
44 | {{- if $showCurrent -}} 45 | Upcoming Shows 46 | Past 47 | {{- else -}} 48 | Upcoming 49 | Past Shows 50 | {{- end -}} 51 |
52 | 53 |
54 | 55 | {{- range $events -}} 56 | 57 | {{- $backgroundColor := .Data.color.page | string | parseColor -}} 58 | {{- $textColor := $backgroundColor.TextExt -}} 59 | {{- $venue := .Places.First -}} 60 | 61 | 89 | 90 | 91 | {{- if $isMyself -}} 92 |
93 | 94 |
95 | {{- end -}} 96 |
97 | 98 | {{- end -}} 99 | 100 |
101 | 102 |
103 | 104 |
105 | 106 | -------------------------------------------------------------------------------- /bandwagon-outbox/header.html: -------------------------------------------------------------------------------- 1 | {{- $isMyself := .IsMyself -}} 2 | {{- $showAlbums := eq "true" (.Data "show-albums") -}} 3 | {{- $showEvents := eq "true" (.Data "show-events") -}} 4 | {{- $showNews := eq "true" (.Data "show-news") -}} 5 | {{- $showShop := ne "" (.Data "shop-url") -}} 6 | {{- $hasBannerImage := ne "" .ImageURL -}} 7 | {{- $hasBannerColor := ne "" (.Data "color-banner") -}} 8 | 9 | 10 | 11 | 12 | {{- if or $hasBannerImage $hasBannerColor $isMyself -}} 13 |
14 | {{- if $isMyself -}} 15 |
 
16 |
17 | 18 |
19 | {{- end -}} 20 |
21 | {{- end -}} 22 | 23 | -------------------------------------------------------------------------------- /bandwagon-outbox/news.html: -------------------------------------------------------------------------------- 1 | {{- $posts := .Outbox.Top60.ByPublishDate.Reverse -}} 2 | {{- $posts := $posts.Where "templateId" "bandwagon-news" -}} 3 | {{- $posts := $posts.Slice -}} 4 | 5 | {{- $isMyself := .IsMyself -}} 6 | {{- $label := first (.Data "label-news") "News" -}} 7 | {{- $description := .Data "description-news" -}} 8 | 9 |
10 | 11 | {{- template "header" . -}} 12 | 13 |
14 | 15 |
16 | 17 | 18 | {{.DisplayName}} 19 | 20 | · {{$label}} 21 | 22 | {{- if $isMyself -}} 23 |
24 | 25 | 26 |
27 | {{- end -}} 28 | 29 |
30 | 31 | {{- if ne "" $description -}} 32 |
{{$description | markdown}}
33 | {{- end -}} 34 | 35 | {{- if $posts.NotEmpty -}} 36 |
37 | {{- range $posts -}} 38 | 55 | {{- end -}} 56 |
57 | 58 | {{- end -}} 59 | 60 |
61 | 62 |
63 | 64 |
-------------------------------------------------------------------------------- /bandwagon-outbox/outbox.html: -------------------------------------------------------------------------------- 1 | {{- $isMyself := .IsMyself -}} 2 | {{- $username := .Username -}} 3 | 4 | {{- if eq .StateID "LIVE" -}} 5 | 6 | {{- $albums := .Outbox.Featured.Where "templateId" "bandwagon-album" -}} 7 | {{- $albums := $albums.By "data.releaseDate" -}} 8 | {{- $albums := $albums.Reverse.Slice -}} 9 | 12 | 13 | 14 |
15 | 16 | {{.DisplayName}} 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 | {{- if not .IsPublic -}} 43 | {{- if $albums.NotEmpty -}} 44 |
45 | {{icon "check-circle"}} 46 | Your profile IS READY TO PUBLISH on the social web. 47 | Click here to publish it now 48 |
49 | {{- end -}} 50 | {{- end -}} 51 | 52 | {{- template "header" . -}} 53 | 54 |
55 | 56 | 57 |
58 |
59 | 60 |
61 |
62 |
63 |

{{.DisplayName}}

64 | {{- if $isMyself -}} 65 |
66 | 67 | 68 | 69 |
70 | {{- end -}} 71 |
72 | 73 |
74 | @{{.Username}}@{{.Hostname}} 75 | {{- if ne "" .Location }} 76 | {{icon "location"}} {{.Location}} 77 | {{- end -}} 78 |
79 | 80 | 90 | 91 |
92 | 100 |
101 |
102 | 103 | 110 | 111 | {{icon "rss"}} RSS 112 |
113 | 114 |
115 |
116 | 117 |
118 |
119 | 120 |
121 | 122 | 123 | {{- if or $albums.NotEmpty $isMyself -}} 124 | 125 | {{- $albumLabel := first (.Data "label-albums") "Albums" -}} 126 | 127 |
128 | 129 | {{$albumLabel}} 130 | All → 131 | 132 | 133 | {{- if $isMyself -}} 134 |
135 | 138 | 141 |
142 | {{- end -}} 143 | 144 |
145 |
146 | 147 | {{- if $albums.IsEmpty -}} 148 |
149 | Get started by uploading music to your bandwagon profile. Featured albums appear here. 150 |
151 | {{- end -}} 152 | 153 |
154 | {{- range $albums -}} 155 | 156 | {{- if or .IsPublished $isMyself -}} 157 | 158 | 182 | {{- end -}} 183 | {{- end -}} 184 | 185 | {{- if $albums.IsEmpty -}} 186 |
187 | 188 | 189 | 190 |
You can publish your profile after you've uploaded at least one album
191 |
192 | {{- end -}} 193 |
194 | {{- end -}} 195 | 196 | 197 | 198 | {{- $events := .Outbox.Featured.Where "templateId" "bandwagon-event" -}} 199 | {{- $events := $events.WhereGT "startDate" yesterday -}} 200 | {{- $events := $events.ByStartDate.Slice -}} 201 | 202 | {{- if or $events.NotEmpty $isMyself -}} 203 | 204 | {{- $eventLabel := first (.Data "label-events") "Shows" -}} 205 | 206 |
207 | 208 | {{$eventLabel}} 209 | All → 210 | 211 | 212 | {{- if $isMyself -}} 213 |
214 | 215 | 216 |
217 | {{- end -}} 218 | 219 |
220 | 221 | 262 | 263 | {{- if $events.IsEmpty -}} 264 |
265 | Publish showtimes, locations, and ticketing websites on your event calendar. Featured events appear here. 266 |
267 | {{- end -}} 268 | 269 | {{- end -}} 270 | 271 | 272 | 273 | {{- $news := .Outbox.Featured.Where "templateId" "bandwagon-news" -}} 274 | {{- $news := $news.ByPublishDate.Reverse.Slice -}} 275 | 276 | {{- if or $news.NotEmpty $isMyself -}} 277 | 278 | {{- $newsLabel := first (.Data "label-news") "News" -}} 279 | 280 |
281 | 282 | 283 | {{$newsLabel}} 284 | All → 285 | 286 | 287 | {{- if $isMyself -}} 288 |
289 | 290 | 291 |
292 | {{- end -}} 293 | 294 |
295 | 296 |
297 | {{- range $news -}} 298 | {{- if or .IsPublished $isMyself -}} 299 | 300 | 326 | {{- end -}} 327 | {{- end -}} 328 |
329 | 330 | {{- end -}} 331 | 332 | {{- if $news.IsEmpty -}} 333 |
334 | Publish updates to your followers. Featured items appear here. 335 |
336 | {{- end -}} 337 |
338 | 339 |
340 | 341 |
342 |
343 |

About {{.DisplayName}}

344 | {{- if $isMyself -}} 345 | 346 | {{- end -}} 347 |
348 |
{{.StatusMessage | markdown}}
349 | 350 |
351 | 352 |
353 | 354 |
355 | {{- if .Tags.NotEmpty -}} 356 |

Hashtags

357 | {{- range .Tags -}} 358 | #{{.Name}} 359 | {{- end -}} 360 | {{- end -}} 361 |
362 | 363 |
364 | 365 | {{- if $isMyself -}} 366 |
367 |
368 | 369 |
370 |
371 |
372 |
373 | {{- end -}} 374 | 375 |
376 | 377 |
378 | 379 | {{- else if not $isMyself -}} 380 | {{- template "coming-soon" . -}} 381 | {{- else if eq .StateID "WIZARD-STEP-3" -}} 382 | {{- template "wizard-3" . -}} 383 | {{- else if eq .StateID "WIZARD-STEP-2" -}} 384 | {{- template "wizard-2" . -}} 385 | {{- else -}} 386 | {{- template "wizard-1" . -}} 387 | {{- end -}} -------------------------------------------------------------------------------- /bandwagon-outbox/stylesheet.html: -------------------------------------------------------------------------------- 1 | body { 2 | 3 | {{ if ne "" (.Data "background-body" )}} 4 | background-image:url("{{.Data "background-body"}}"); 5 | background-repeat: no-repeat; 6 | background-size: cover; 7 | {{ end }} 8 | 9 | {{ if ne "" (.Data "color-body") -}} 10 | {{- $bodyColor := parseColor (.Data "color-body") -}} 11 | --body-background: {{.Data "color-body"}}; 12 | --page-border: {{($bodyColor.Darken 10)}}; 13 | {{- end }} 14 | 15 | {{ if ne "" (.Data "image-body" )}} 16 | 17 | {{ end }} 18 | 19 | {{ if ne "" (.Data "color-menu" )}} 20 | {{$menu := parseColor (.Data "color-menu")}} 21 | {{$menuHover := $menu.Contrast 15}} 22 | {{$menuSelected := $menu.Contrast 30}} 23 | 24 | --menu-background: {{$menu}}; 25 | --menu-text: {{$menu.TextExt}}; 26 | --menu-hover-background: {{$menuHover}}; 27 | --menu-hover-text: {{$menuHover.Text}}; 28 | --menu-selected-background: {{$menuSelected}}; 29 | --menu-selected-text: {{$menuSelected.Text}}; 30 | {{- end -}} 31 | 32 | {{ if ne "" (.Data "color-page") -}} 33 | {{- $pageColor := parseColor (.Data "color-page") -}} 34 | {{- $textColor := $pageColor.Text -}} 35 | --page-background: {{.Data "color-page"}}; 36 | --heading-color: {{$pageColor.Text}}; 37 | --text-color: {{$pageColor.TextExt}}; 38 | 39 | {{if $pageColor.IsLight }} 40 | --white: #ffffff; 41 | --gray00: #ffffff; 42 | --gray01: #fefefe; 43 | --gray02: #fdfdfd; 44 | --gray03: #fcfcfc; 45 | --gray04: #fbfbfb; 46 | --gray05: #fafafa; 47 | --gray10: #f4f4f4; 48 | --gray15: #eaeaea; 49 | --gray20: #e0e0e0; 50 | --gray30: #c6c6c6; 51 | --gray40: #a8a8a8; 52 | --gray50: #8d8d8d; 53 | --gray60: #6f6f6f; 54 | --gray70: #525252; 55 | --gray80: #393939; 56 | --gray90: #262626; 57 | --gray100: #000000; 58 | --black: #000000; 59 | {{- else }} 60 | --black: #ffffff; 61 | --gray100: #ffffff; 62 | --gray90: #f7f3f2; 63 | --gray80: #e5e0df; 64 | --gray70: #cac5c4; 65 | --gray60: #a8a8a8; 66 | --gray50: #8f8b8b; 67 | --gray40: #726e6e; 68 | --gray30: #565151; 69 | --gray20: #3c3838; 70 | --gray15: #312e2e; 71 | --gray10: #272525; 72 | --gray05: #1f1c1c; 73 | --gray04: #1e1e1b; 74 | --gray03: #1c1c19; 75 | --gray02: #1a1a17; 76 | --gray01: #181815; 77 | --gray00: #171714; 78 | --white: #000000; 79 | {{- end -}} 80 | 81 | {{- end }} 82 | 83 | {{ if ne "" (.Data "color-button") -}} 84 | {{- $buttonColor := parseColor (.Data "color-button") -}} 85 | {{- $buttonText := $buttonColor.Text }} 86 | {{- $buttonColorHover := ($buttonColor.Lighten 10) -}} 87 | {{- $buttonTextHover := $buttonColorHover.Text }} 88 | {{- $buttonColor := .Data "color-button" -}} 89 | 90 | --button-primary-background: {{$buttonColor}}; 91 | --button-primary-background-hover: {{$buttonColorHover}}; 92 | --button-primary-color: {{$buttonText}}; 93 | --button-primary-color-hover: {{$buttonTextHover}}; 94 | --link-color: {{$buttonColor}}; 95 | --link-color-hover: {{$buttonColorHover}}; 96 | {{- end }} 97 | 98 | } 99 | 100 | {{- $bannerHex := first (.Data "color-banner") "#a0a0a0" -}} 101 | {{- $bannerColor := parseColor $bannerHex -}} 102 | 103 | #profile-banner { 104 | background-color: {{$bannerColor}}; 105 | height:256px; 106 | background-image:url('{{.ImageURL}}'); 107 | background-size:cover; 108 | background-position:center; 109 | } 110 | 111 | #profile-menu { 112 | background-color: var(--menu-background); 113 | font-size: 1.25em; 114 | text-align: center; 115 | } 116 | 117 | #profile-menu > [role=menuitem] { 118 | display: inline-block; 119 | padding: var(--rhythm) calc(var(--rhythm) * 1.5) ; 120 | color: var(--menu-text); 121 | } 122 | 123 | #profile-menu > [role=menuitem]:hover { 124 | background-color: var(--menu-hover-background); 125 | color: var(--menu-hover-text); 126 | } 127 | 128 | #profile-menu > [role=menuitem][aria-selected=true], 129 | #profile-menu > [role=menuitem][aria-selected=true]:hover { 130 | background-color: var(--menu-selected-background); 131 | color: var(--menu-selected-text); 132 | } 133 | 134 | 135 | {{.Data "stylesheet" | css }} 136 | -------------------------------------------------------------------------------- /bandwagon-outbox/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-outbox 3 | templateRole: user-outbox 4 | model: User 5 | label: Bandwagon Outbox 6 | extends: ["user-outbox"] 7 | schema: { 8 | type: object 9 | properties: { 10 | statusMessage: {type:"string"} 11 | location: {type:"string"} 12 | data: { 13 | type: object 14 | wildcard: string 15 | properties: { 16 | tags: {type:"string"} 17 | show-albums: {type:"string"} 18 | show-events: {type:"string"} 19 | show-news: {type:"string"} 20 | shop-url: {type:"string", format:"url"} 21 | label-albums: {type:"string", default:"Albums"} 22 | label-events: {type:"string", default:"Events"} 23 | label-news: {type:"string", default:"News"} 24 | label-shop: {type:"string", default:"Shop"} 25 | description-albums: {type:"string"} 26 | description-events: {type:"string"} 27 | description-news: {type:"string"} 28 | background-body: {type:"string", format:"uri"} 29 | stylesheet: {type:"string"} 30 | color-body: {type:"string", format:"color"} 31 | color-banner: {type:"string", format:"color"} 32 | color-menu: {type:"string", format:"color"} 33 | color-page: {type:"string", format:"color"} 34 | color-button: {type:"string", format:"color"} 35 | } 36 | } 37 | } 38 | } 39 | 40 | tagPaths: ["data.tags"] 41 | 42 | actions: { 43 | 44 | create: { 45 | steps:[ 46 | { 47 | do:"set-data", 48 | values:{ 49 | "data.label-albums":"Albums" 50 | "data.label-events":"Events" 51 | "data.label-news":"News" 52 | "data.label-shop":"Shop" 53 | } 54 | } 55 | ] 56 | } 57 | 58 | view: {do:"view-html", file:"outbox"} 59 | albums: {do:"view-html"} 60 | events: {do:"view-html"} 61 | news: {do:"view-html"} 62 | 63 | add-album: { 64 | roles: ["self"] 65 | steps: [ 66 | { 67 | do: add-stream 68 | style: inline 69 | location: outbox 70 | template: bandwagon-album 71 | label: + Add Album 72 | set-permissions: {copy:true, editor:["self"]} 73 | with-data:{data.tags:"{{.Data `tags`}}"} 74 | } 75 | {do:"set-data", values:{"data.show-albums":"true"}} 76 | {do:"save"} 77 | {do:"refresh-page"} 78 | ] 79 | } 80 | 81 | add-event: { 82 | roles: ["self"] 83 | steps: [ 84 | { 85 | do: add-stream 86 | style: inline 87 | location: outbox 88 | template: bandwagon-event 89 | title: + Add a Song 90 | set-permissions: {copy:true, editor:["self"]} 91 | with-data:{data.tags:"{{.Data `tags`}}"} 92 | } 93 | {do:"set-data", values:{"data.show-events":"true"}} 94 | {do: "save"} 95 | {do: "refresh-page"} 96 | ] 97 | } 98 | 99 | add-news: { 100 | roles: ["self"] 101 | steps: [ 102 | { 103 | do: add-stream 104 | style: inline 105 | location: outbox 106 | template: bandwagon-news 107 | title: + Add News 108 | set-permissions: {copy:true, editor:["self"]} 109 | } 110 | {do:"set-data", values:{"data.show-news":"true"}} 111 | {do: "save"} 112 | {do: "refresh-page"} 113 | ] 114 | } 115 | 116 | edit: { 117 | roles: ["self"] 118 | steps: [ 119 | {do: "as-modal", background:"view", steps:[ 120 | { 121 | do: "edit" 122 | form:{ 123 | label: Edit Profile 124 | type:layout-tabs 125 | children: [ 126 | { 127 | type: layout-vertical 128 | label: Basics 129 | children: [ 130 | {type:"text", path:"displayName", label:"Name", description:"Displayed wherever someone sees your profile. (PUBLIC)"} 131 | {type:"textarea", path:"statusMessage", label:"About Me", description:"Displayed on your profile page. Markdown is allowed. (PUBLIC)", options:{rows:4, showLimit:true}} 132 | {type:"text", path:"location", label:"Location", description:"City, State, or whatever you want. (PUBLIC)"} 133 | {type:"text", path:"data.tags", label:"Hashtags", description:"Enter #Hashtags separated by spaces. (PUBLIC)"} 134 | ] 135 | } 136 | { 137 | type: layout-vertical 138 | label: Images 139 | children: [ 140 | {type:"upload", path:"iconUrl", label:"Avatar Image", description:"Displayed next to your name as an icon. (PUBLIC)", options:{accept:"image/*", delete:"/@me/delete-icon"}} 141 | {type:"upload", path:"imageUrl", label:"Banner Image", description:"Displays at the top of your profile page. (PUBLIC)", options:{accept:"image/*", delete:"/@me/delete-image"}} 142 | {type:"upload", path:"data.background-body", label:"Window Background Image", description:"Displays instead of the window's background color (PUBLIC)", options:{accept:"image/*", delete:"/@me/delete-background"}} 143 | ] 144 | } 145 | { 146 | label: Colors 147 | description: "Change the colors of your profile page to match your brand." 148 | type:layout-vertical 149 | children: [ 150 | {type:"colorpicker", path:"data.color-body", label:"Window Background"} 151 | {type:"colorpicker", path:"data.color-banner", label:"Banner Background"} 152 | {type:"colorpicker", path:"data.color-menu", label:"Menu Background"} 153 | {type:"colorpicker", path:"data.color-page", label:"Page Background"} 154 | {type:"colorpicker", path:"data.color-button", label:"Links and Buttons"} 155 | ] 156 | } 157 | { 158 | label: Style 159 | type:layout-vertical 160 | children: [ 161 | {type:"textarea", path:"data.stylesheet", label:"Custom CSS Stylesheet", description: "Experts only!   Read the CSS Guide →", options:{rows:12, style:"font-family:monospace; font-size:14px;"}} 162 | ] 163 | } 164 | { 165 | type:"layout-vertical" 166 | label: Security 167 | children: [ 168 | {type:"text", path:"username", label:"Username", description:"How others will identify you online. (PUBLIC)", options:{autocomplete:"off", validator:"/.validate/username"}} 169 | {type:"text", path:"emailAddress", label:"Email Address", description:"Used to sign in and support your account. Not shared. (PRIVATE)"} 170 | {type:"password", path:"new_password", label:"Change Password", description:"At least 8 characters. Please use a Password Manager. (PRIVATE)"} 171 | {type:"toggle", path:"isPublic", options:{text:"Public (visible to people)"}} 172 | {type:"toggle", path:"isIndexable", options:{text:"Indexable (request inclusion in search engines)"}} 173 | {type:"html", description:"
Delete my account
"} 174 | ] 175 | } 176 | ] 177 | } 178 | } 179 | {do:"upload-attachments", category:"icon", fieldname:"iconUrl", attachment-path:"iconId", rules:{width:400, height:400, types:["webp"]}} 180 | {do:"upload-attachments", category:"image", fieldname:"imageUrl", attachment-path:"imageId", rules:{types:["webp"]}} 181 | {do:"upload-attachments", category:"body-background", fieldname:"data.background-body", download-path:"data.background-body", rules:{types:["webp"]}} 182 | {do:"process-tags", paths:["data.tags"]} 183 | {do:"save"} 184 | {do:"set-password"} 185 | {do:"search-index"} 186 | {do:"refresh-page"} 187 | ]} 188 | ] 189 | } 190 | 191 | edit-status: { 192 | roles: ["self"] 193 | steps: [ 194 | {do: "as-modal", background:"view", steps:[ 195 | { 196 | do: "edit" 197 | form:{ 198 | label: Edit Status Message 199 | type: layout-vertical 200 | children: [ 201 | {type:"textarea", path:"statusMessage", description:"Displayed on your profile page. Markdown is allowed. (PUBLIC)", options:{rows:8, showLimit:true}} 202 | ] 203 | } 204 | } 205 | {do:"save"} 206 | {do:"search-index"} 207 | {do:"refresh-page"} 208 | ]} 209 | ] 210 | } 211 | 212 | edit-banner: { 213 | roles: ["self"] 214 | steps: [ 215 | {do: "as-modal", background:"view", steps:[ 216 | { 217 | do: "edit" 218 | form:{ 219 | label: Edit Banner 220 | description: Choose a color or an image to display a banner on your profile. Leave empty and it will be hidden to visitors. 221 | type:layout-vertical 222 | children: [ 223 | {type:"colorpicker", path:"data.color-banner", label:"Background Color", description:"Displays underneath your banner image."} 224 | {type:"upload", path:"imageUrl", label:"Banner Image", description:"Displays at the top of your profile page.", options:{accept:"image/*", delete:"/@me/delete-image"}} 225 | ] 226 | } 227 | } 228 | {do:"upload-attachments", category:"image", fieldname:"imageUrl", attachment-path:"imageId", rules:{types:["webp"]}} 229 | {do:"save"} 230 | {do:"search-index", if:"{{.IsIndexable}}"} 231 | {do:"refresh-page"} 232 | ]} 233 | ] 234 | } 235 | 236 | edit-albums: { 237 | roles: ["self"] 238 | steps: [ 239 | {do: "as-modal", background:"view", steps:[ 240 | { 241 | do: "edit" 242 | form:{ 243 | type: layout-vertical 244 | label: Edit Albums 245 | children: [ 246 | {type:"text", path:"data.label-albums", label:"Title Text", description:"Used on profile, and at top of 'Albums' page.", options:{placeholder:"Albums"}} 247 | {type:"textarea", path:"data.description-albums", label:"Top of Page Text", description:"Text to show at the top of this page. Markdown is allowed.", options:{rows:8}} 248 | ] 249 | } 250 | } 251 | {do:"save"} 252 | {do:"refresh-page"} 253 | ]} 254 | ] 255 | } 256 | 257 | 258 | edit-events: { 259 | roles: ["self"] 260 | steps: [ 261 | {do: "as-modal", background:"view", steps:[ 262 | { 263 | do: "edit" 264 | form:{ 265 | label: Edit Shows 266 | type: layout-vertical 267 | children: [ 268 | {type:"text", path:"data.label-events", label:"Title Text", description:"Used on profile and at the top of 'Shows' page.", options:{placeholder:"Shows"}} 269 | {type:"textarea", path:"data.description-events", label:"Top of Page Text", description:"Text to show at the top of this page. Markdown is allowed.", options:{rows:8}} 270 | ] 271 | } 272 | } 273 | {do:"save"} 274 | {do:"refresh-page"} 275 | ]} 276 | ] 277 | } 278 | 279 | 280 | edit-news: { 281 | roles: ["self"] 282 | steps: [ 283 | {do: "as-modal", background:"view", steps:[ 284 | { 285 | do: "edit" 286 | form:{ 287 | label: Edit News 288 | type: layout-vertical 289 | children: [ 290 | {type:"text", path:"data.label-news", label:"Title Text", description:"Used on profile and at the top of 'News' page.", options:{placeholder:"News"}} 291 | {type:"textarea", path:"data.description-news", label:"Top of Page Text", description:"Text to show at the top of the 'News' section. Markdown is allowed.", options:{rows:8}} 292 | ] 293 | } 294 | } 295 | {do:"save"} 296 | {do:"refresh-page"} 297 | ]} 298 | ] 299 | } 300 | 301 | edit-menu: { 302 | roles: ["self"] 303 | steps: [ 304 | {do: "as-modal", background:"view", steps:[ 305 | { 306 | do: "edit" 307 | form:{ 308 | label: Edit Menu 309 | type:layout-tabs 310 | children: [ 311 | { 312 | type: layout-vertical 313 | label: Albums 314 | children: [ 315 | {type:"toggle", path:"data.show-albums", options:{text:"Display 'Albums' in Menu Bar"}} 316 | {type:"text", path:"data.label-albums", label:"Menu Bar Text", options:{placeholder:"Albums"}} 317 | {type:"textarea", path:"data.description-albums", label:"Top of Page Text", description:"Text to show at the top of the 'Albums' section. Markdown is allowed.", options:{rows:8}} 318 | ] 319 | } 320 | { 321 | type: layout-vertical 322 | label: Shows 323 | children: [ 324 | {type:"toggle", path:"data.show-events", options:{text:"Display 'Shows' in Menu Bar"}} 325 | {type:"text", path:"data.label-events", label:"Menu Bar Text", options:{placeholder:"Shows"}} 326 | {type:"textarea", path:"data.description-events", label:"Top of Page Text", description:"Text to show at the top of the 'Events' section. Markdown is allowed.", options:{rows:8}} 327 | ] 328 | } 329 | { 330 | type: layout-vertical 331 | label: News 332 | children: [ 333 | {type:"toggle", path:"data.show-news", options:{text:"Display 'News' in Menu Bar"}} 334 | {type:"text", path:"data.label-news", label:"Menu Bar Text", options:{placeholder:"News"}} 335 | {type:"textarea", path:"data.description-news", label:"Top of Page Text", description:"Text to show at the top of the 'News' section. Markdown is allowed.", options:{rows:8}} 336 | ] 337 | } 338 | { 339 | type: layout-vertical 340 | label: Shop Link 341 | children: [ 342 | {type:"label", label:"The 'Shop' link is displayed automatically when you enter the shop URL below"} 343 | {type:"text", path:"data.label-shop", label:"Menu Bar Text", options:{placeholder:"Shop"}} 344 | {type:"text", path:"data.shop-url", label:"Shop URL"} 345 | ] 346 | } 347 | { 348 | type: layout-vertical 349 | label: Colors 350 | children: [ 351 | {type:"colorpicker", path:"data.color-menu", label:"Background Color"} 352 | ] 353 | } 354 | ] 355 | } 356 | } 357 | {do:"save"} 358 | {do:"refresh-page"} 359 | ]} 360 | ] 361 | } 362 | sort-featured: { 363 | roles: ["self"] 364 | steps: [ 365 | {do:"sort", model:"Stream", keys:"_id", values:"rank"} 366 | {do:"set-header", name:"Hx-Push-Url", value:"false"} 367 | ] 368 | } 369 | 370 | sort-children: { 371 | roles: ["self"] 372 | steps: [ 373 | {do:"sort", model:"Stream", keys:"_id", values:"rankAlt"} 374 | ] 375 | } 376 | 377 | header: { 378 | steps: [ 379 | {do:"view-html", file:"header"} 380 | ] 381 | } 382 | 383 | stylesheet: { 384 | steps: [ 385 | {do:"view-css"} 386 | ] 387 | } 388 | 389 | wizard-1: { 390 | roles: ["self"] 391 | steps: [ 392 | { 393 | do:edit 394 | form: { 395 | type:layout-vertical 396 | children: [ 397 | {type:"text", path:"displayName", label:"Artist/Band Name", description:"Displayed publicly. The name you want to be known by.", options:{autocomplete:"name", required:true}} 398 | {type:"text", path:"username", label:"Username", description:"Displayed publicly. How others will find you online.", options:{autocomplete:"username", required:true}} 399 | {type:"text", path:"emailAddress", label:"Email Address", description:"Used to sign in, and support your account. Not shared. Not displayed publicly.", options:{autocomplete:"email", required:true}} 400 | {type:"password", path:"new_password", label:"Choose Your Password", description:"At least 8 characters. Please use a Password Manager.", options:{autocomplete:"new-password", minLength:8, required:true}} 401 | ] 402 | } 403 | options: ["endpoint:/@{{.UserID}}/wizard-1", "submit-label:Continue >>", "cancel-button:hide"] 404 | }, 405 | {do:"set-state", state:"WIZARD-STEP-2"} 406 | {do:"save"} 407 | {do:"set-password"} 408 | {do:"set-header", name:"HX-Refresh", value:"true"} 409 | ] 410 | } 411 | 412 | wizard-2: { 413 | roles: ["self"] 414 | steps: [ 415 | { 416 | do:edit 417 | form: { 418 | type: layout-vertical 419 | children: [ 420 | {type:"textarea", path:"statusMessage", label:"About You", description:"A brief description of you and your music. Markdown is okay."} 421 | {type:"textarea", path:"data.tags", label:"Tags", description:"Enter #Hashtags separated by spaces."} 422 | {type:"text", path:"location", label:"Location", description:"City, State, or Country where you are based."} 423 | {type:"upload", path:"iconUrl", label:"Avatar Image", description:"Displayed publicly next to your name, often on other websites, too.", options:{accept:"image/*"}} 424 | {type:"upload", path:"imageUrl", label:"Banner", description:"Displays at the top of your profile page.", options:{accept:"image/*"}} 425 | ] 426 | } 427 | options: ["endpoint:/@{{.UserID}}/wizard-2", "submit-label:Continue >>", "cancel-button:hide"] 428 | }, 429 | {do:"upload-attachments", category:"icon", fieldname:"iconUrl", attachment-path:"iconId", rules:{width:400, height:400, types:["webp"]}} 430 | {do:"upload-attachments", category:"image", fieldname:"imageUrl", attachment-path:"imageId", rules:{types:["webp"]}} 431 | {do:"set-state", state:"WIZARD-STEP-3"} 432 | {do:"save"} 433 | {do:"set-header", name:"Hx-Refresh", value:"true"} 434 | ] 435 | } 436 | 437 | wizard-3: { 438 | roles: ["self"] 439 | steps: [ 440 | { 441 | do:edit 442 | form: { 443 | type:layout-vertical 444 | children: [ 445 | {type:"colorpicker", path:"data.color-body", label:"Window Background"} 446 | {type:"colorpicker", path:"data.color-menu", label:"Menu Background"} 447 | {type:"colorpicker", path:"data.color-page", label:"Page Background"} 448 | {type:"colorpicker", path:"data.color-button", label:"Links and Buttons"} 449 | 450 | ] 451 | } 452 | options: ["endpoint:/@{{.UserID}}/wizard-3", "submit-label:Continue to Album Upload", "cancel-button:hide"] 453 | }, 454 | {do:"set-state", state:"LIVE"} 455 | {do:"save"} 456 | {do:"search-index", if:"{{.IsIndexable}}"} 457 | {do:"set-header", name:"Hx-Refresh", value:"true"} 458 | ] 459 | } 460 | 461 | publish: { 462 | roles: ["self"] 463 | steps: [ 464 | {do:"as-modal", steps:[ 465 | {do:"edit", form:{ 466 | type:"layout-vertical" 467 | label:"Publish Your Profile" 468 | description:"This determines who can see your profile and interact with you online." 469 | children:[ 470 | {type:"toggle", path:"isPublic", options:{text:"Make My Profile Public (visible to people)"}} 471 | {type:"toggle", path:"isIndexable", options:{text:"Make My Profile Indexable (include in search results)"}} 472 | ]} 473 | } 474 | ]} 475 | {do:"save"} 476 | {do:"search-index", if:"{{.IsIndexable}}"} 477 | {do:"refresh-page"} 478 | ] 479 | } 480 | 481 | delete-icon: { 482 | roles: ["self"] 483 | steps: [ 484 | {do:"delete-attachments", field:"iconId"} 485 | {do:"save"} 486 | {do:"search-index", if:"{{.IsIndexable}}"} 487 | ] 488 | } 489 | 490 | delete-image: { 491 | roles: ["self"] 492 | steps: [ 493 | {do:"delete-attachments", field:"imageId"} 494 | {do:"save"} 495 | {do:"search-index", if:"{{.IsIndexable}}"} 496 | ] 497 | } 498 | 499 | delete-background: { 500 | roles: ["self"] 501 | steps: [ 502 | {do:"set-data", values:{"data.background-body":""}} 503 | {do:"delete-attachments", category:"body-background"} 504 | {do:"save"} 505 | ] 506 | } 507 | } 508 | } -------------------------------------------------------------------------------- /bandwagon-outbox/wizard-1.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
Getting Started: Step 1 of 3
6 |

Welcome to Bandwagon!

7 | 8 |
Please fill out a few quick questions to set up your profile..
9 | 10 | {{.View "wizard-1" }} 11 |
12 | 13 |
-------------------------------------------------------------------------------- /bandwagon-outbox/wizard-2.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
Getting Started: Step 2 of 3
6 |

A Bit About You

7 | 8 |
9 | This information will show on your public profile. 10 | Don't worry if you don't have it perfect, just yet. You can always change it later. 11 |
12 | 13 | {{.View "wizard-2" }} 14 |
15 | 16 |
-------------------------------------------------------------------------------- /bandwagon-outbox/wizard-3.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
Getting Started: Step 3 of 3
6 |

Your Brand Colors

7 |
8 | Default colors are boring. Customize the color scheme of your profile page, to fit your image and style. 9 | If you're not ready, no worries. You can change these settings later. 10 |
11 | 12 | {{.View "wizard-3" }} 13 |
14 | 15 |
-------------------------------------------------------------------------------- /bandwagon-search-albums/json.html: -------------------------------------------------------------------------------- 1 | {{- $host := .Host -}} 2 | {{- $token := .Token -}} 3 | {{- $syndication := .QueryParam "syndication" -}} 4 | {{- $syndicationSlice := array $syndication "ALL-PARTNERS" -}} 5 | 6 | {{- $streams := .Streams.Top600.ByPublishDate.Reverse -}} 7 | {{- $streams := $streams.Where "templateId" "bandwagon-album" -}} 8 | {{- $streams := $streams.WhereIN "syndication.values" $syndicationSlice -}} 9 | {{- $streams := $streams.Slice -}} 10 | 11 | { 12 | "syndication": "{{$syndication}}", 13 | "orderedItems": [ 14 | {{- range $index, $stream := $streams -}} 15 | {{- if gt $index 0 -}} 16 | , 17 | {{- end -}} 18 | { 19 | "url": "{{$host}}/{{$stream.StreamID}}", 20 | "export": "{{$host}}/{{$stream.StreamID}}/zip9348103752", 21 | "name": {{$stream.Label | json}}, 22 | "icon": {{$stream.IconURL | json}}, 23 | "artist": {{$stream.AttributedTo.ProfileURL | json}}, 24 | "publishDate": {{$stream.PublishDate}} 25 | } 26 | {{- end -}} 27 | ] 28 | } -------------------------------------------------------------------------------- /bandwagon-search-albums/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-search-albums 3 | templateRole: search 4 | extends: ["bandwagon-search", "bandwagon-common"] 5 | model: Stream 6 | containedBy: ["top"] 7 | icon: book 8 | label: Bandwagon Album Search 9 | description: Search Engine for Albums 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string"} 14 | summary: {type:"string"} 15 | data: { 16 | type:object 17 | properties: { 18 | searchText: {type:"string"} 19 | content: {type:"string"} 20 | } 21 | } 22 | } 23 | } 24 | 25 | actions: { 26 | view-albums: { 27 | steps: [ 28 | {do:"view-html"} 29 | ] 30 | } 31 | 32 | feed: { 33 | steps: [ 34 | {do:"view-feed", search-types:["Album"]} 35 | ] 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /bandwagon-search-albums/view-results.html: -------------------------------------------------------------------------------- 1 | {{- $token := .Token -}} 2 | {{- $query := .QueryParam "q" -}} 3 | {{- $tags := .QueryParam "tags" -}} 4 | {{- $empty := and (eq "" $query) (eq "" $tags) -}} 5 | 6 | 7 | {{- $results := .Search.Top60.ByShuffle.WhereType "Album" -}} 8 | {{- $after := .QueryParam "after" | int64 -}} 9 | 10 | {{- if ne 0 $after -}} 11 | {{- $results = $results.AfterShuffle $after -}} 12 | {{- end -}} 13 | 14 | {{- $results := $results.Slice -}} 15 | {{- $last := $results.Last -}} 16 | {{- $results = $results.Shuffle -}} 17 | 18 | {{- if $results.NotEmpty -}} 19 | 20 | {{- if eq 0 $after -}} 21 | 45 | 46 |
47 |
48 |
49 |
50 | 51 | {{icon "rss"}} RSS 52 |
53 | 54 |
55 |
56 | 57 | {{- end -}} 58 | 59 |
65 | 66 | {{- range $result := $results -}} 67 | 68 | 69 |
{{$result.Name}}
70 |
71 | {{$result.AttributedTo}} 72 |
73 |
74 | {{- end -}} 75 |
76 | 77 | {{- else if eq 0 $after -}} 78 | 79 | {{- $tags := .FeaturedSearchTags.Slice -}} 80 | 81 |
82 | There are no albums that match 83 | {{if ne "" $query}} 84 | "{{$query}}". 85 | {{- else -}} 86 | your search. 87 | {{- end -}} 88 |
89 | Please try some of these search terms instead... 90 |
91 | 92 |
93 |
94 | {{- range $index, $tag := $tags -}} 95 | 96 | {{icon "tag"}} {{$tag.Name}} 97 | 98 | {{- end -}} 99 |
100 |
101 | 102 | {{- end -}} 103 | 104 | -------------------------------------------------------------------------------- /bandwagon-search-albums/view-sidebar.html: -------------------------------------------------------------------------------- 1 | {{- $licenses := .Dataset "bandwagon-search-albums.licenses" -}} 2 | {{- $tags := .QueryParam "tags" -}} 3 | 4 | {{- $query := .QueryParam "q" -}} 5 | {{- $escapedQuery := queryEscape $query -}} 6 | 7 |
8 | Artists 9 | Albums 10 | Tracks 11 | Calendar 12 | News 13 |
14 | 15 |
16 | 17 |
18 | 19 | 22 |
23 | -------------------------------------------------------------------------------- /bandwagon-search-albums/view-tags.html: -------------------------------------------------------------------------------- 1 | {{- $token := .Token -}} 2 | {{- $tags := .FeaturedSearchTags.ByRank.Slice -}} 3 | 4 |
5 | {{- range $index, $tag := $tags -}} 6 | 7 | {{- $background00 := $tag.Colors.At 0 | parseColor -}} 8 | {{- $background01 := $tag.Colors.At 1 | parseColor -}} 9 | {{- $backgroundImage := concat "linear-gradient(120deg, " $background00.RGBA ", " $background01.RGBA ")" -}} 10 | {{- $textColor := $background00.Text -}} 11 | {{- $shadowColor := $textColor.Text -}} 12 | 13 | 14 |
15 | {{- if ne "" $tag.ImageURL }} 16 | 17 | {{- end -}} 18 |
{{$tag.Name}}
19 |
20 |
21 | {{- end -}} 22 |
23 | 24 | 35 | -------------------------------------------------------------------------------- /bandwagon-search-events/embed-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Before

7 |
8 | 9 | 10 | 11 |

After

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /bandwagon-search-events/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | {{- .View "view-results" -}} 17 | 18 |
19 | Calendar by {{.Hostname}} 20 |
21 | 22 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bandwagon-search-events/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-search-events 3 | templateRole: search 4 | extends: ["bandwagon-search"] 5 | model: Stream 6 | containedBy: ["top"] 7 | icon: home 8 | label: Bandwagon Event Search 9 | description: Search Engine for Events 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string"} 14 | summary: {type:"string"} 15 | data: { 16 | type:object 17 | properties: { 18 | searchText: {type:"string"} 19 | content: {type:"string"} 20 | } 21 | } 22 | } 23 | } 24 | 25 | actions: { 26 | embed: { 27 | steps: [ 28 | {do:"view-html", as-full-page:true} 29 | ] 30 | } 31 | view: { 32 | steps:[ 33 | {do:"if", condition:"{{eq `` (.QueryParam `date`)}}", then:[ 34 | {do:"set-query-param", date:"next-30-days"} 35 | ]} 36 | {do:"view-html"} 37 | ] 38 | } 39 | 40 | feed: { 41 | steps: [ 42 | {do:"view-feed", search-types:["Event"]} 43 | ] 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /bandwagon-search-events/view-results.html: -------------------------------------------------------------------------------- 1 | {{- $host := .Host -}} 2 | {{- $token := .Token -}} 3 | {{- $query := .QueryParam "q" -}} 4 | {{- $escapedQuery := queryEscape $query -}} 5 | 6 | {{- $results := .Search.Top120.ByDate.WhereType "Event" -}} 7 | {{- $results := $results.Slice -}} 8 | 9 | 10 | {{- if $results.IsEmpty -}} 11 | 12 | {{- $tags := .FeaturedSearchTags.Slice -}} 13 | 14 |
15 | There are no events that match "{{$query}}". 16 |
17 | 18 | {{- else -}} 19 | 20 | {{- $token := .Token -}} 21 | {{- $group := groupie -}} 22 | 23 |
24 |
25 |
26 |
27 | 28 | {{icon "rss"}} RSS 29 |
30 | 31 |
32 |
33 | 34 |
35 | {{- range $index, $result := $results -}} 36 | 37 | 38 | 39 |
40 |
41 |
{{$result.Date | shortMonth}}
42 |
{{$result.Date | day}}
43 |
44 |
45 | 46 |
47 |
48 | {{- if ne "" .IconURL -}} 49 | 50 | {{- end -}} 51 |
52 |
53 | 54 |
55 |
{{.Name}}
56 |
57 | {{.Date | shortDate}} 58 | {{- $time := .Date | shortTime -}} 59 | {{- if ne "12:00 AM" $time -}} 60 |  {{- $time -}} 61 | {{- end -}} 62 |
63 | {{.AttributedTo}}
64 | {{.Place.Name}}
65 |
66 |
67 | More Info → 68 |
69 |
70 | 71 |
72 | 73 | {{- end -}} 74 |
75 | 76 | {{- end -}} -------------------------------------------------------------------------------- /bandwagon-search-events/view-sidebar.html: -------------------------------------------------------------------------------- 1 | {{- $query := .QueryParam "q" -}} 2 | {{- $date := .QueryParam "date" -}} 3 | {{- if eq "" $date -}} 4 | {{- $date := "next-30-days" -}} 5 | {{- end -}} 6 | 7 |
8 | Artists 9 | Albums 10 | Tracks 11 | Calendar 12 | News 13 |
14 | 15 |
16 | 17 |
18 | 19 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 | -------------------------------------------------------------------------------- /bandwagon-search-news/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-search-news 3 | templateRole: search 4 | extends: ["bandwagon-search"] 5 | model: Stream 6 | containedBy: ["top"] 7 | icon: home 8 | label: Bandwagon News Search 9 | description: Search Engine for News 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string"} 14 | summary: {type:"string"} 15 | data: { 16 | type:object 17 | properties: { 18 | searchText: {type:"string"} 19 | content: {type:"string"} 20 | } 21 | } 22 | } 23 | } 24 | 25 | tagPaths: ["data.tags"] 26 | 27 | actions: { 28 | feed: { 29 | steps: [ 30 | {do:"view-feed", search-types:["Person"]} 31 | ] 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /bandwagon-search-news/view-results.html: -------------------------------------------------------------------------------- 1 | {{- $token := .Token -}} 2 | {{- $query := .QueryParam "q" -}} 3 | 4 | {{- $results := .Search.Top60.ByCreateDate.Reverse -}} 5 | {{- $results = $results.WhereType "Article" -}} 6 | {{- $before := .QueryParam "before" -}} 7 | 8 | {{- if ne "" $before -}} 9 | {{- $results = $results.WhereLT "publishDate" (int64 $before) -}} 10 | {{- end -}} 11 | 12 | {{- $results := $results.Slice -}} 13 | {{- $last := $results.Last -}} 14 | 15 | 16 | {{- if $results.NotEmpty -}} 17 | 18 | {{- if eq "" $before -}} 19 |
20 |
21 |
22 |
23 | 24 | {{icon "rss"}} RSS 25 |
26 | 27 |
28 |
29 | {{- end -}} 30 | 31 | 32 | {{- $token := .Token -}} 33 | 34 |
41 | 42 | {{- range $index, $result := $results -}} 43 | 44 | 45 |
46 | {{- if ne "" $result.IconURL -}} 47 | 48 | {{- end -}} 49 |
50 | 51 |
52 | 53 |
{{- $result.AttributedTo }}
54 |
{{ $result.CreateDate | shortDate -}}
55 |
56 |
57 | {{- end -}} 58 |
59 | 60 | {{- else if eq "" $before -}} 61 | 62 |
63 | There are no news items that match "{{$query}}". 64 |
65 | 66 | {{- end -}} -------------------------------------------------------------------------------- /bandwagon-search-news/view-sidebar.html: -------------------------------------------------------------------------------- 1 | {{- $query := .QueryParam "q" -}} 2 | 3 |
4 | Artists 5 | Albums 6 | Tracks 7 | Calendar 8 | News 9 |
10 | -------------------------------------------------------------------------------- /bandwagon-search-tracks/json.html: -------------------------------------------------------------------------------- 1 | {{- $host := .Host -}} 2 | {{- $token := .Token -}} 3 | {{- $syndication := .QueryParam "syndication" -}} 4 | {{- $syndicationSlice := array $syndication "ALL-PARTNERS" -}} 5 | 6 | {{- $streams := .Streams.Top600.ByPublishDate.Reverse -}} 7 | {{- $streams := $streams.Where "templateId" "bandwagon-album" -}} 8 | {{- $streams := $streams.WhereIN "syndication.values" $syndicationSlice -}} 9 | {{- $streams := $streams.Slice -}} 10 | 11 | { 12 | "syndication": "{{$syndication}}", 13 | "orderedItems": [ 14 | {{- range $index, $stream := $streams -}} 15 | {{- if gt $index 0 -}} 16 | , 17 | {{- end -}} 18 | { 19 | "url": "{{$host}}/{{$stream.StreamID}}", 20 | "export": "{{$host}}/{{$stream.StreamID}}/zip9348103752", 21 | "name": {{$stream.Label | json}}, 22 | "icon": {{$stream.IconURL | json}}, 23 | "artist": {{$stream.AttributedTo.ProfileURL | json}}, 24 | "publishDate": {{$stream.PublishDate}} 25 | } 26 | {{- end -}} 27 | ] 28 | } -------------------------------------------------------------------------------- /bandwagon-search-tracks/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-search-tracks 3 | templateRole: search 4 | extends: ["bandwagon-search"] 5 | model: Stream 6 | containedBy: ["top"] 7 | icon: book 8 | label: Bandwagon Track Search 9 | description: Search Engine for Tracks 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string"} 14 | summary: {type:"string"} 15 | data: { 16 | type:object 17 | properties: { 18 | searchText: {type:"string"} 19 | content: {type:"string"} 20 | } 21 | } 22 | } 23 | } 24 | 25 | actions: { 26 | 27 | view-albums-list: { 28 | steps: [{do:"view-html"}] 29 | } 30 | 31 | feed: { 32 | steps: [ 33 | {do:"view-feed", search-types:["Audio"]} 34 | ] 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /bandwagon-search-tracks/view-results.html: -------------------------------------------------------------------------------- 1 | {{- $token := .Token -}} 2 | {{- $query := .QueryParam "q" -}} 3 | {{- $tags := .QueryParam "tags" -}} 4 | {{- $empty := and (eq "" $query) (eq "" $tags) -}} 5 | 6 | 7 | {{- $results := .Search.Top30.ByShuffle.WhereType "Audio" -}} 8 | {{- $after := .QueryParam "after" | int64 -}} 9 | 10 | {{- if ne 0 $after -}} 11 | {{- $results = $results.AfterShuffle $after -}} 12 | {{- end -}} 13 | 14 | {{- $results := $results.Slice -}} 15 | {{- $last := $results.Last -}} 16 | {{- $results = $results.Shuffle -}} 17 | 18 | {{- if $results.NotEmpty -}} 19 | 20 | {{- if eq 0 $after -}} 21 |
22 |
23 |
24 |
25 | 26 | {{icon "rss"}} RSS 27 |
28 | 29 |
30 |
31 | 32 | {{- end -}} 33 | 34 |
40 | 41 | {{- range $result := $results -}} 42 | 43 |
44 | {{- if ne "" $result.IconURL -}} 45 | 46 | {{- end -}} 47 |
48 |
49 |
{{$result.Name}}
50 |
51 | Artist: {{$result.AttributedTo}} 52 |
53 |
54 |
55 | {{- end -}} 56 |
57 | 58 | {{- else if eq 0 $after -}} 59 | 60 | {{- $tags := .FeaturedSearchTags.Slice -}} 61 | 62 |
63 | There are no tracks that match 64 | {{if ne "" $query}} 65 | "{{$query}}". 66 | {{- else -}} 67 | your search. 68 | {{- end -}} 69 |
70 | Please try some of these search terms instead... 71 |
72 | 73 |
74 |
75 | {{- range $index, $tag := $tags -}} 76 | 77 | {{icon "tag"}} {{$tag.Name}} 78 | 79 | {{- end -}} 80 |
81 |
82 | 83 | {{- end -}} 84 | -------------------------------------------------------------------------------- /bandwagon-search-tracks/view-sidebar.html: -------------------------------------------------------------------------------- 1 | {{- $query := .QueryParam "q" -}} 2 | {{- $escapedQuery := queryEscape $query -}} 3 | 4 |
5 | Artists 6 | Albums 7 | Tracks 8 | Calendar 9 | News 10 |
11 | -------------------------------------------------------------------------------- /bandwagon-search-tracks/view-tags.html: -------------------------------------------------------------------------------- 1 | {{- $token := .Token -}} 2 | {{- $tags := .FeaturedSearchTags.ByRank.Slice -}} 3 | 4 |
5 |
6 | Genres 7 | Albums 8 |
9 |
10 | 11 |
12 | {{- range $index, $tag := $tags -}} 13 | 14 | {{- $background00 := $tag.Colors.At 0 | parseColor -}} 15 | {{- $background01 := $tag.Colors.At 1 | parseColor -}} 16 | {{- $backgroundImage := concat "linear-gradient(120deg, " $background00.RGBA ", " $background01.RGBA ")" -}} 17 | {{- $textColor := $background00.Text -}} 18 | {{- $shadowColor := $textColor.Text -}} 19 | 20 | 21 |
22 | {{- if ne "" $tag.ImageURL }} 23 | 24 | {{- end -}} 25 |
{{$tag.Name}}
26 |
27 |
28 | {{- end -}} 29 |
30 | -------------------------------------------------------------------------------- /bandwagon-search-users/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-search-users 3 | templateRole: search 4 | extends: ["bandwagon-search"] 5 | model: Stream 6 | containedBy: ["top"] 7 | icon: home 8 | label: Bandwagon User Search 9 | description: Search Engine for Users 10 | schema: { 11 | type:object 12 | properties: { 13 | label: {type:"string"} 14 | summary: {type:"string"} 15 | data: { 16 | type:object 17 | properties: { 18 | searchText: {type:"string"} 19 | content: {type:"string"} 20 | } 21 | } 22 | } 23 | } 24 | 25 | actions: { 26 | feed: { 27 | steps: [ 28 | {do:"view-feed", search-types:["Person"]} 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /bandwagon-search-users/view-results.html: -------------------------------------------------------------------------------- 1 | {{- $token := .Token -}} 2 | {{- $query := .QueryParam "q" -}} 3 | 4 | {{- $results := .Search.Top60.ByShuffle.WhereType "Person" -}} 5 | {{- $after := .QueryParam "after" | int64 -}} 6 | 7 | {{- if ne 0 $after -}} 8 | {{- $results = $results.AfterShuffle $after -}} 9 | {{- end -}} 10 | 11 | {{- $results := $results.Slice -}} 12 | {{- $last := $results.Last -}} 13 | {{- $results = $results.Shuffle -}} 14 | 15 | 16 | {{- if $results.NotEmpty -}} 17 | 18 | {{- $token := .Token -}} 19 | 20 |
26 | 27 | {{- range $index, $result := $results -}} 28 |
29 | 30 | {{- if eq "" $result.IconURL -}} 31 |
32 | {{- else -}} 33 | 34 | {{- end -}} 35 | 36 |
37 |
{{$result.Name}}
38 |
{{- $result.AttributedTo -}}
39 |
40 |
41 |
42 | {{- end -}} 43 |
44 | 45 | {{- if ne 0 $after -}} 46 | 53 | {{- end -}} 54 | 55 | {{- else if eq 0 $after -}} 56 | 57 | {{- $tags := .FeaturedSearchTags.Slice -}} 58 | 59 |
60 | There are no artists that match "{{$query}}". 61 |
62 | Please try some of these search terms instead... 63 |
64 | 65 |
66 |
67 | {{- range $index, $tag := $tags -}} 68 | 69 | {{icon "tag"}} {{$tag.Name}} 70 | 71 | {{- end -}} 72 |
73 |
74 | 75 | {{- end -}} -------------------------------------------------------------------------------- /bandwagon-search-users/view-sidebar.html: -------------------------------------------------------------------------------- 1 | {{- $query := .QueryParam "q" -}} 2 | 3 |
4 | Artists 5 | Albums 6 | Tracks 7 | Calendar 8 | News 9 |
10 | -------------------------------------------------------------------------------- /bandwagon-search/hyperscript/listbox._hs: -------------------------------------------------------------------------------- 1 | behavior Listbox 2 | 3 | init 4 | add .list-box 5 | add [@role="listbox"] 6 | end 7 | 8 | on ArrowDown 9 | 10 | set selection to the first <[role=option].selected /> in me 11 | 12 | if selection is null then 13 | take .selected from my children for the first <[role=option] /> in me 14 | exit 15 | end 16 | 17 | if selection is the last <[role=option] /> in me then 18 | exit 19 | end 20 | 21 | take .selected from my children for the next <[role=option] /> from selection 22 | end 23 | 24 | on ArrowUp 25 | set selection to the first <[role=option].selected /> in me 26 | 27 | if selection is the first <[role=option] /> in me then 28 | send hide to me 29 | remove .selected from selection 30 | exit 31 | end 32 | 33 | if selection is null then 34 | send hide to me 35 | exit 36 | end 37 | 38 | take .selected from my children for the previous <[role=option] /> from selection 39 | end 40 | 41 | end -------------------------------------------------------------------------------- /bandwagon-search/hyperscript/popUp._hs: -------------------------------------------------------------------------------- 1 | behavior Popup 2 | 3 | init 4 | add .pop-up 5 | end 6 | 7 | on show 8 | set visibility to my *visibility 9 | 10 | if visibility is "hidden" then 11 | log "set visible" 12 | set my *visibility to "visible" 13 | end 14 | end 15 | 16 | on hide 17 | set visibility to my *visibility 18 | if visibility is "visible" then 19 | log "set hidden" 20 | set my *visibility to "hidden" 21 | end 22 | end 23 | 24 | end -------------------------------------------------------------------------------- /bandwagon-search/hyperscript/searchSuggestion._hs: -------------------------------------------------------------------------------- 1 | behavior SearchSuggestion 2 | 3 | on mouseover 4 | take .selected from my siblings 5 | end 6 | 7 | on mousedown 8 | useSuggestion() 9 | end 10 | end -------------------------------------------------------------------------------- /bandwagon-search/hyperscript/searchTag._hs: -------------------------------------------------------------------------------- 1 | behavior SearchTag 2 | 3 | init 4 | add .tag 5 | add .nowrap 6 | add .margin-sm 7 | add .margin-right-none 8 | add .clickable 9 | add .flex-align-self-center 10 | add [@tabIndex=0] 11 | 12 | set my innerHTML to my @data-tag + ` ` 13 | end 14 | 15 | on click or keydown[key=="Backspace"] 16 | set newTag to the previous <[data-tag]/> within #search-tags 17 | if newTag is not null then 18 | focus() the newTag 19 | else 20 | focus() the #search-input 21 | end 22 | remove me 23 | end 24 | 25 | on keydown[key=="ArrowLeft"] 26 | set newTag to the previous <[data-tag]/> within #search-tags 27 | if newTag is not null then 28 | focus() the newTag 29 | end 30 | end 31 | 32 | on keydown[key=="ArrowRight"] 33 | halt the event 34 | set newTag to the next <[data-tag]/> within #search-tags 35 | if newTag is not null then 36 | focus() the newTag 37 | else 38 | focus() the #search-input 39 | end 40 | end 41 | 42 | on keydown[key=="Enter"] 43 | halt the event 44 | send click to the #search-submit 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /bandwagon-search/hyperscript/searchWidget._hs: -------------------------------------------------------------------------------- 1 | behavior SearchWidget 2 | init 3 | send focus(initial:true) to the #search-input 4 | end 5 | 6 | -- submit the search 7 | on click from #search-submit 8 | send submit to #search-form 9 | end 10 | 11 | --------------------------- 12 | -- Search Suggestions 13 | 14 | on showSuggestions 15 | -- clean up the search value 16 | set searchValue to the #search-input's value 17 | set searchValue to lastWord(searchValue) 18 | set searchValue to searchValue.toLowerCase() 19 | 20 | -- send query and show suggestions 21 | set #search-suggestion-query.value to searchValue 22 | send search to the #search-suggestions-form 23 | send show to #search-suggestions 24 | end 25 | 26 | --------------------------- 27 | -- Keyboard Navigation 28 | 29 | on keydown[key=="Escape"] from #search-input 30 | send hide to #search-suggestions 31 | end 32 | 33 | on keydown[key=="Enter"] from #search-input 34 | if the #search-suggestions's *visibility is "visible" then 35 | useSuggestion() 36 | else 37 | send submit to #search-form 38 | end 39 | end 40 | 41 | on keydown[key=="ArrowDown"] from #search-input 42 | if the #search-suggestions's *visibility is "hidden" then 43 | trigger showSuggestions 44 | else 45 | send ArrowDown to #search-suggestions 46 | end 47 | end 48 | 49 | on keydown[key=="ArrowUp"] from #search-input 50 | halt the event 51 | send ArrowUp to #search-suggestions 52 | end 53 | 54 | on keyup from #search-input throttled at 100ms 55 | set ignoreKeys to ["ArrowDown", "ArrowUp", "Enter", "Escape"] 56 | 57 | if ignoreKeys.includes(event.key) then 58 | exit 59 | end 60 | 61 | -- handle regular keystrokes 62 | send showSuggestions 63 | end 64 | 65 | on blur from #search-input debounced at 50ms 66 | send hide to #search-suggestions 67 | end 68 | 69 | on submit from #search-form 70 | send hide to #search-suggestions 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /bandwagon-search/hyperscript/utils._hs: -------------------------------------------------------------------------------- 1 | def useSuggestion() 2 | 3 | -- do not execute if suggestions are not visible 4 | if the #search-suggestions's *visibility is "hidden" then 5 | exit 6 | end 7 | 8 | -- find the first "selected" suggestion 9 | set suggestion to the first in #search-suggestions 10 | 11 | if suggestion is undefined then 12 | exit 13 | end 14 | 15 | -- send the request to the server 16 | set the #search-input's value to "#" + suggestion.innerHTML 17 | send click to the #search-submit 18 | hide the #search-suggestions with visibility 19 | end 20 | -------------------------------------------------------------------------------- /bandwagon-search/javascript/parseTags.js: -------------------------------------------------------------------------------- 1 | function parseTags(value) { 2 | 3 | var inTag = false 4 | var tags = [] 5 | var remainder = "" 6 | var foundTag = "" 7 | 8 | for (var index = 0 ; index < value.length; index++) { 9 | var char= value[index] 10 | 11 | // This is the beginning of a tag.. 12 | if (char == "#") { 13 | inTag = true 14 | foundTag = "" 15 | continue 16 | } 17 | 18 | // We're not in a tag, so just add the character to the remainder 19 | if (inTag == false) { 20 | remainder += char 21 | continue 22 | } 23 | 24 | // This is the end of a tag 25 | switch (char) { 26 | 27 | case " ": 28 | tags.push(foundTag) 29 | inTag = false 30 | break 31 | 32 | case ",": 33 | case ".": 34 | case "?": 35 | case ":": 36 | case ";": 37 | remainder += char 38 | tags.push(foundTag) 39 | inTag = false 40 | break 41 | 42 | // Otherwise, add the character to the current tag 43 | default: 44 | foundTag += char 45 | 46 | } 47 | } 48 | 49 | // Also add tags that extend to the end of the input 50 | if (inTag) { 51 | tags.push(foundTag) 52 | } 53 | 54 | return { 55 | tags: tags, 56 | remainder: remainder 57 | } 58 | } 59 | 60 | function lastWord(value) { 61 | var words = value.split(" ") 62 | return words[words.length - 1] 63 | } -------------------------------------------------------------------------------- /bandwagon-search/search-related.html: -------------------------------------------------------------------------------- 1 | {{- $tags := .QueryParam "q" | parseTags -}} 2 | {{- if $tags.NotEmpty -}} 3 | 4 | {{- $tag := $tags.First | .SearchTag -}} 5 | 6 | {{- $related := $tag.RelatedTags -}} 7 | {{- if $related.NotEmpty -}} 8 | {{- $token := .Token -}} 9 | 15 | 16 | 39 | {{- end -}} 40 | 41 | {{- end -}} -------------------------------------------------------------------------------- /bandwagon-search/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId:bandwagon-search 3 | templateRole:search 4 | extends:["base-intent"] 5 | containedBy: [] 6 | model:stream 7 | label:Bandwagon Search page 8 | description:Search Page for Bandwagon 9 | schema: { 10 | type:object 11 | properties: { 12 | icon: {type:"string"} 13 | label: {type:"string"} 14 | summary: {type:"string"} 15 | data: { 16 | type:object 17 | properties: { 18 | searchText: {type:"string"} 19 | content: {type:"string"} 20 | } 21 | } 22 | } 23 | } 24 | states: {} 25 | roles: {} 26 | 27 | bundles: { 28 | javascript: { 29 | content-type:application/javascript 30 | } 31 | hyperscript: { 32 | content-type:text/hyperscript 33 | } 34 | } 35 | 36 | actions: { 37 | 38 | create: { 39 | roles:["owner"] 40 | steps: [ 41 | {do: "set-data", defaults: {label: "New Page"}} 42 | { 43 | do:edit 44 | form: { 45 | type:layout-tabs 46 | label:Create Search Page 47 | children: [ 48 | { 49 | type:layout-vertical 50 | label:Page Info 51 | children: [ 52 | {type:"text", label:"Token", path:"token"} 53 | {type:"text", label:"Icon", path:"icon"} 54 | {type:"text", label:"Page Name", path:"label"} 55 | {type:"textarea", label:"Description", path:"summary"} 56 | ] 57 | }, 58 | { 59 | type:layout-vertical 60 | label:Content 61 | children:[ 62 | {type:"text", path:"data.searchText", label:"Search Text", description:"Prompt to display in the search box."} 63 | {type:"textarea", path:"data.content", label:"Page Heading", description:"Appears at the top of search results. Markdown is okay.", options:{rows:10}} 64 | ] 65 | } 66 | ] 67 | } 68 | } 69 | {do: "save-and-publish"} 70 | {do:"forward-to", url:"/{{.StreamID}}/edit"} 71 | ] 72 | } 73 | edit: { 74 | roles: ["owner"] 75 | steps: [ 76 | {do: "as-modal", background:"view", steps: [ 77 | { 78 | do: edit 79 | options: ["delete:/{{.StreamID}}/delete"] 80 | form: { 81 | type: layout-tabs 82 | label:"Edit Search Page" 83 | children: [ 84 | { 85 | type:"layout-vertical", 86 | label:"Page Info", 87 | children: [ 88 | {type:"text", readOnly:true, label:"Template Type", path:"templateId"} 89 | {type:"text", label:"Token", path:"token"} 90 | {type:"text", label:"Icon", path:"icon"} 91 | {type:"text", label:"Page Name", path:"label"} 92 | {type:"textarea", label:"Description", path:"summary"} 93 | ] 94 | }, 95 | { 96 | type:"layout-vertical", 97 | label:"Content", 98 | children:[ 99 | {type:"text", path:"data.searchText", label:"Search Text", description:"Prompt to display in the search box."} 100 | {type:"textarea", path:"data.content", label:"Page Heading", description:"Appears at the top of search results. Markdown is okay.", options:{rows:10}} 101 | ] 102 | } 103 | ] 104 | } 105 | } 106 | ]} 107 | {do: "save-and-publish", message:"Edited by {{.Author.DisplayName}}"} 108 | {do: "refresh-page"} 109 | ] 110 | } 111 | delete: { 112 | roles: ["owner"] 113 | steps: [ 114 | {do:"delete", title:"Delete '{{.Label}}'?", message:"All content and comments will be lost. There is NO UNDO."} 115 | {do:"forward-to", url:"/{{.ParentID}}"} 116 | ] 117 | } 118 | sharing: { 119 | roles: ["owner"] 120 | steps: [ 121 | {do:"as-modal", steps: [ 122 | {do:"set-simple-sharing", roles: ["viewer"], title:"Who Can See This Article?", message:"Select who can view and comment on this article."} 123 | {do:"save", message:"Sharing updated by {{.Author}}"} 124 | ]} 125 | ] 126 | } 127 | 128 | view: { 129 | steps:[ 130 | {do:"view-html"} 131 | ] 132 | } 133 | view-results: { 134 | steps:[ 135 | {do:"view-html"} 136 | ] 137 | } 138 | view-suggestions: { 139 | steps:[ 140 | {do:"view-html"} 141 | ] 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /bandwagon-search/view-results.html: -------------------------------------------------------------------------------- 1 | {{- $queryString := .QueryParam "q" -}} 2 | {{- $token := .Token -}} 3 | 4 | {{- if eq "" $queryString -}} 5 | 6 | {{- template "view-tags" . -}} 7 | 8 | {{- else -}} 9 | 10 | {{- $results := .Search.Slice -}} 11 | 12 |
13 | 14 | 15 |
16 |
17 |
18 | {{- range $result := $results -}} 19 |
20 |
{{- $result | jsonIndent -}}
21 |
22 | {{- end -}} 23 |
24 | 25 | {{- end -}} -------------------------------------------------------------------------------- /bandwagon-search/view-suggestions.html: -------------------------------------------------------------------------------- 1 | {{- $searchTags := .AllowedSearchTags.Top12.ByName.Slice -}} 2 | {{- range $searchTag := $searchTags -}} 3 |
{{$searchTag.Name}}
7 | {{- end -}} 8 | -------------------------------------------------------------------------------- /bandwagon-search/view-widget.html: -------------------------------------------------------------------------------- 1 | {{- $query := .QueryParam "q" | trim -}} 2 | {{- $placeholder := .Data "searchText" -}} 3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 | 11 | 12 | 13 |
14 |
15 |
22 | 23 | 24 |
25 | 26 | {{- template "search-related" . -}} 27 |
28 | -------------------------------------------------------------------------------- /bandwagon-search/view.html: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /bandwagon-song/oembed.html: -------------------------------------------------------------------------------- 1 | {{- $album := .Parent "view" -}} 2 | {{- $albumColors := $album.Data "color" -}} 3 | {{- $bodyColor := first $albumColors.body "#eeeeee" -}} 4 | {{- $pageColor := parseColor (first $albumColors.page "#ffffff") -}} 5 | {{- $buttonColor := parseColor (first $albumColors.button "#0f62fe") -}} 6 | {{- $titleColor := $pageColor.Text -}} 7 | {{- $textColor := $pageColor.TextExt -}} 8 | 9 |
10 |
11 | 12 | 13 | {{- if ne "" $album.IconURL -}} 14 | 17 | {{- end -}} 18 | 79 | 80 |
19 |
{{.Label}}
20 |
21 | 22 | {{.AttributedTo.Name}} 23 | 24 | · 25 | 26 | {{$album.Label}} 27 | 28 | {{ if ne nil ($album.Data "year") }} 29 | · 30 | {{$album.Data "year"}} 31 | {{- end }} 32 |
33 | 59 | 60 | {{- if ne "" .Summary -}} 61 |
62 | {{.Summary}} 63 |
64 | {{- end -}} 65 | 66 |
67 | {{- $license := $album.DataString "license"}} 68 | {{- if eq "COPYRIGHT" $license -}} 69 | © 70 | Copyright. All Rights Reserved. 71 | {{- else if ne "" $license -}} 72 | {{- $licenses := .Dataset "bandwagon-album.licenses" -}} 73 | {{- $licenseInfo := $licenses.Value $license -}} 74 | {{icon $licenseInfo.Icon}} 75 | {{$licenseInfo.Label}} 76 | {{- end -}} 77 |
78 |
81 |
82 |
-------------------------------------------------------------------------------- /bandwagon-song/template.hjson: -------------------------------------------------------------------------------- 1 | { 2 | templateId: bandwagon-song 3 | templateRole: song 4 | label: Song 5 | extends: ["base-intent", "bandwagon-common"] 6 | containedBy: ["album"] 7 | schema: { 8 | type: object 9 | properties: { 10 | label: {type: "string", required: true} 11 | iconUrl: {type:"string", format:"url"} 12 | data: { 13 | type: object 14 | properties: { 15 | tags: {type:"string"} 16 | length: {type: "string"} 17 | genre: {type: "string"} 18 | artist: {type: "string"} 19 | composer: {type: "string"} 20 | producer: {type: "string"} 21 | publisher: {type: "string"} 22 | isrc: {type: "string"} 23 | license: {type: "string"} 24 | lyrics: {type: "string"} 25 | streamURL: {type: "string", format:"url"} 26 | featured: {type: "boolean"} 27 | explicit: {type: "boolean"} 28 | attachmentId: {type: "string"} 29 | attachmentFilename: {type: "string"} 30 | links: { 31 | type: object 32 | wildcard:{type: "string", format:"url"} 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | socialRole: Audio 40 | socialRules: [ 41 | {target:"@context", append:"https://funkwhale.audio/ns"} 42 | {target:"type", value:"Audio"} 43 | {target:"attributedTo.id", path:"attributedTo.profileUrl"}, 44 | {target:"attributedTo.name", path:"attributedTo.name"}, 45 | {target:"links", forEach:"data.links", rules:[ 46 | {target:"label", path:"key"} 47 | {target:"rel", value:"alternate"} 48 | {target:"href", path:"value"} 49 | ]} 50 | {target:"track.type", value:"Track"} 51 | {target:"track.id", path:"url"} 52 | {target:"track.name", path:"label"} 53 | {target:"track.album", expression:"{{.ParentURL}}"} 54 | {target:"track.position", path:"rank"} 55 | {target:"content", path:"data.lyrics"} 56 | {target:"album", expression:"{{.ParentURL}}"} 57 | {target:"library", expression:"{{.ParentURL}}/pub/children"} 58 | ] 59 | 60 | search: { 61 | type: Audio 62 | iconUrl:"{{first .IconURL (join `/` .ParentID.Hex `/icon`)}}" 63 | summary:"" 64 | text: "{{.Label}} {{.AttributedTo.Name}} {{index .Data `lyrics`}}" 65 | tags: "LIC:{{index .Data `license`}}" 66 | } 67 | 68 | tagPaths: ["data.tags"] 69 | 70 | actions: { 71 | create: { 72 | roles: ["admin", "author"] 73 | steps: [ 74 | {do:"as-modal", steps:[ 75 | { 76 | do:"edit", 77 | form:{ 78 | type: layout-tabs 79 | label: + Add a Song 80 | children: [ 81 | { 82 | type: layout-vertical 83 | label: General 84 | children: [ 85 | {type: "text", path: "label", label: "Song Title"}, 86 | {type: "upload", path: "data.attachmentId", label: "Audio File", options:{filename:"data.attachmentFilename", accept:"audio/*"}}, 87 | {type: "upload", path: "iconUrl", label:"Cover Art", options:{accept:"image/*", filename:"data.imageFilename", delete:"/{{.StreamID}}/delete-icon", rules:{width:800, height:800, types:["webp"]}}} 88 | {type: "text", path: "data.length", label: "Length"}, 89 | {type: "toggle", path: "isFeatured", options:{true-text:"Featured. Highlighted on track list", false-text:"Featured?"}} 90 | ] 91 | } 92 | { 93 | type: layout-vertical 94 | label: Description 95 | children: [ 96 | {type: "textarea", path: "data.lyrics", description:"Enter lyrics or song description. Displays on this song's detail page.", options:{rows: 16}} 97 | {type: "toggle", path: "data.explicit", options:{true-text:"Explicit Lyrics", false-text:"Explicit Lyrics"}} 98 | ] 99 | } 100 | { 101 | type: layout-vertical 102 | label: Metadata 103 | children: [ 104 | {type: "select", path: "data.license", label: "License", options:{provider:"bandwagon-album.licenses"}}, 105 | {type: "text", path: "data.tags", label: "Tags", description:"Enter #Hashtags separated by spaces."}, 106 | {type: "text", path: "data.genre", label: "Genre"}, 107 | {type: "text", path: "data.artist", label: "Artist"}, 108 | {type: "text", path: "data.composer", label: "Composer"}, 109 | {type: "text", path: "data.producer", label: "Producer"}, 110 | {type: "text", path: "data.publisher", label: "Publisher"}, 111 | {type: "text", path: "data.isrc", label: "ISRC"}, 112 | ] 113 | } 114 | { 115 | type: layout-vertical 116 | label: Links 117 | children: [ 118 | {type: "text", path: "data.streamURL", label: "Video Stream URL"}, 119 | {type:"text", path:"data.links.AMAZON", options:{placeholder:"Amazon Music"}} 120 | {type:"text", path:"data.links.APPLE", options:{placeholder:"Apple Music"}} 121 | {type:"text", path:"data.links.BANDCAMP", options:{placeholder:"Bandcamp"}} 122 | {type:"text", path:"data.links.GOOGLE", options:{placeholder:"Google Play"}} 123 | {type:"text", path:"data.links.SOUNDCLOUD", options:{placeholder:"Soundcloud"}} 124 | {type:"text", path:"data.links.SPOTIFY", options:{placeholder:"Spotify"}} 125 | {type:"text", path:"data.links.TIDAL", options:{placeholder:"Tidal"}} 126 | {type:"text", path:"data.links.YOUTUBE", options:{placeholder:"YouTube Music"}} 127 | {type:"text", path:"data.links.OTHER1", options:{placeholder:"Other"}} 128 | {type:"text", path:"data.links.OTHER2", options:{placeholder:"Other"}} 129 | {type:"text", path:"data.links.OTHER3", options:{placeholder:"Other"}} 130 | ] 131 | } 132 | ] 133 | } 134 | } 135 | ]} 136 | {do:"upload-attachments", category:"image", fieldname:"iconUrl", download-path:"iconUrl", accept-type:"image/*", maximum:1, rules:{width:800, height:800, types:["webp"]}} 137 | {do:"upload-attachments", action:"replace", category:"Audio", fieldname:"data.attachmentId", attachment-path:"data.attachmentId", filename-path:"data.attachmentFilename", accept-type:"audio/*", maximum:1} 138 | {do:"process-tags", paths:"data.tags"} 139 | {do:"save-and-publish", outbox:false} 140 | {do:"search-index"} 141 | {do:"refresh-page"} 142 | ] 143 | } 144 | view: { 145 | steps: [ 146 | {do:"view-html"} 147 | ] 148 | } 149 | edit: { 150 | roles: ["admin", "author"] 151 | steps: [ 152 | { 153 | do:"as-modal", 154 | steps:[ 155 | { 156 | do:"edit", 157 | options: ["delete:/{{.StreamID}}/delete"] 158 | form:{ 159 | type: layout-tabs 160 | label: Edit Song 161 | children: [ 162 | { 163 | type: layout-vertical 164 | label: General 165 | children: [ 166 | {type: "text", path: "label", label: "Song Title"}, 167 | {type: "upload", path: "data.attachmentId", label: "Audio File", options:{filename:"data.attachmentFilename", accept:"audio/*"}}, 168 | {type: "upload", path: "iconUrl", label:"Cover Art", options:{accept:"image/*", filename:"data.imageFilename", delete:"/{{.StreamID}}/delete-icon", rules:{width:800, height:800, types:["webp"]}}} 169 | {type: "text", path: "data.length", label: "Length"}, 170 | {type: "toggle", path: "isFeatured", options:{true-text:"Featured. Highlighted on track list", false-text:"Featured?"}} 171 | ] 172 | } 173 | { 174 | type: layout-vertical 175 | label: Description 176 | children: [ 177 | {type: "textarea", path: "data.lyrics", description:"Enter lyrics or song description. Displays on this song's detail page.", options:{rows: 16}} 178 | {type: "toggle", path: "data.explicit", options:{true-text:"Explicit Lyrics", false-text:"Explicit Lyrics"}} 179 | ] 180 | } 181 | { 182 | type: layout-vertical 183 | label: Metadata 184 | children: [ 185 | {type: "select", path: "data.license", label: "License", options:{provider:"bandwagon-album.licenses"}}, 186 | {type: "text", path: "data.tags", label: "Tags", description:"Enter #Hashtags separated by spaces."}, 187 | {type: "text", path: "data.genre", label: "Genre"}, 188 | {type: "text", path: "data.artist", label: "Artist"}, 189 | {type: "text", path: "data.composer", label: "Composer"}, 190 | {type: "text", path: "data.producer", label: "Producer"}, 191 | {type: "text", path: "data.publisher", label: "Publisher"}, 192 | {type: "text", path: "data.isrc", label: "ISRC"}, 193 | ] 194 | } 195 | { 196 | type: layout-vertical 197 | label: Links 198 | children: [ 199 | {type: "text", path: "data.streamURL", label: "Video Stream URL"}, 200 | {type:"text", path:"data.links.AMAZON", options:{placeholder:"Amazon Music"}} 201 | {type:"text", path:"data.links.APPLE", options:{placeholder:"Apple Music"}} 202 | {type:"text", path:"data.links.BANDCAMP", options:{placeholder:"Bandcamp"}} 203 | {type:"text", path:"data.links.GOOGLE", options:{placeholder:"Google Play"}} 204 | {type:"text", path:"data.links.SOUNDCLOUD", options:{placeholder:"Soundcloud"}} 205 | {type:"text", path:"data.links.SPOTIFY", options:{placeholder:"Spotify"}} 206 | {type:"text", path:"data.links.TIDAL", options:{placeholder:"Tidal"}} 207 | {type:"text", path:"data.links.YOUTUBE", options:{placeholder:"YouTube Music"}} 208 | {type:"text", path:"data.links.OTHER1", options:{placeholder:"Other"}} 209 | {type:"text", path:"data.links.OTHER2", options:{placeholder:"Other"}} 210 | {type:"text", path:"data.links.OTHER3", options:{placeholder:"Other"}} 211 | ] 212 | } 213 | ] 214 | } 215 | } 216 | ] 217 | } 218 | {do:"upload-attachments", category:"image", fieldname:"iconUrl", download-path:"iconUrl", accept-type:"image/*", maximum:1, rules:{width:800, height:800, types:["webp"]}} 219 | {do:"upload-attachments", action:"replace", category:"Audio", fieldname:"data.attachmentId", attachment-path:"data.attachmentId", filename-path:"data.attachmentFilename", accept-type:"audio/*", maximum:1} 220 | {do:"process-tags", paths:"data.tags"} 221 | {do:"save-and-publish"} 222 | {do:"search-index"} 223 | {do:"refresh-page"} 224 | ] 225 | } 226 | 227 | delete: { 228 | roles: ["admin", "author"] 229 | steps: [ 230 | {do:"delete", title:"Delete this Song?"} 231 | {do:"refresh-page"} 232 | ] 233 | } 234 | 235 | delete-icon: { 236 | roles: ["author"] 237 | steps: [ 238 | {do: "delete-attachments"} 239 | {do: "set-data", values:{"iconUrl": ""}} 240 | {do: "save"} 241 | ] 242 | } 243 | prev: { 244 | steps:[ 245 | {do:"with-prev-sibling", steps:[ 246 | {do:"redirect-to", url:"/{{.StreamID}}"} 247 | ]} 248 | ] 249 | } 250 | 251 | next: { 252 | steps:[ 253 | {do:"with-next-sibling", steps:[ 254 | {do:"redirect-to", url:"/{{.StreamID}}"} 255 | ]} 256 | ] 257 | } 258 | } 259 | } -------------------------------------------------------------------------------- /bandwagon-song/view.html: -------------------------------------------------------------------------------- 1 | {{- $album := .Parent "view" -}} 2 | {{- $links := .Data "links" -}} 3 | {{- $streamURL := .Data "streamURL" -}} 4 | {{- $lyrics := .Data "lyrics" -}} 5 | {{- $attachmentID := .Data "attachmentId" -}} 6 | {{- $duration := .Data "length"}} 7 | {{- $iconURL := .IconURL -}} 8 | 9 | {{- if eq "" $iconURL -}} 10 | {{- $iconURL = $album.IconURL -}} 11 | {{- end -}} 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 | Track Name: 48 | {{.Label}} 49 | 50 | {{- if eq true (.Data "explicit") -}} 51 | {{icon "explicit-fill"}} 52 | {{- end -}} 53 |

54 | 65 | 66 |
67 | {{ if ne nil ($album.Data "year") -}} 68 | 69 | Release Date: 70 | {{$album.Data "year"}} 71 | 72 | {{ if ne nil (.Data "length") -}} 73 | 74 | {{ end }} 75 | {{- end -}} 76 | 77 | Play Time: 78 | {{.Data "length"}} 79 | 80 |
81 | 82 |
83 |
84 | 85 |
86 | {{icon "skip-backward-fill"}} Prev 87 | {{ if ne nil $attachmentID }} 88 | 89 | 90 | 91 | 96 | 100 | {{ else }} 101 | 102 | {{ end }} 103 | Next {{icon "skip-forward-fill"}} 104 |
105 | 106 |
107 | 108 | 109 |
110 | 111 |
112 | 113 |
114 |
115 | 116 |
117 | 118 |
119 | 120 | {{- if ne nil $lyrics -}} 121 |
{{$lyrics | text}}
122 | {{- end -}} 123 | 124 |
125 | 126 |
127 | 253 | 254 |
255 | 256 | {{- if .UserCan "edit" -}} 257 |
258 | 259 |
260 | 261 |
262 |
263 |
264 | {{- end -}} 265 | 266 |
267 | 268 |
--------------------------------------------------------------------------------