Wiki source code of XWiki Color Themes
Last modified by Alex Cotiuga on 2026/07/27 08:39
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | {{velocity wiki="false"}} | ||
| 2 | #if ($xcontext.action == 'get') | ||
| 3 | #set ($statusCode = 400) | ||
| 4 | #set ($message = 'The theme collection could not be sent. Please try again or contact Agnease by email at alex@agnease.com.') | ||
| 5 | |||
| 6 | #set ($email = $stringtool.trim($request.get('email'))) | ||
| 7 | #set ($contactWebsite = $stringtool.trim($request.get('contactWebsite'))) | ||
| 8 | #set ($startedAtRaw = $stringtool.trim($request.get('contactStartedAt'))) | ||
| 9 | #set ($sourcePage = $stringtool.trim($request.get('sourcePage'))) | ||
| 10 | |||
| 11 | #set ($spamScore = 0) | ||
| 12 | |||
| 13 | ## Honeypot: real users should never fill this field. | ||
| 14 | #if ("$!contactWebsite" != '') | ||
| 15 | #set ($spamScore = $spamScore + 5) | ||
| 16 | #end | ||
| 17 | |||
| 18 | ## Submission timing check. | ||
| 19 | #if ("$!startedAtRaw" == '') | ||
| 20 | #set ($spamScore = $spamScore + 2) | ||
| 21 | #else | ||
| 22 | #set ($startedAt = $numbertool.toNumber($startedAtRaw)) | ||
| 23 | #if ("$!startedAt" == '') | ||
| 24 | #set ($spamScore = $spamScore + 2) | ||
| 25 | #else | ||
| 26 | #set ($now = $datetool.systemDate.time) | ||
| 27 | #set ($elapsed = $now - $startedAt) | ||
| 28 | |||
| 29 | ## Reject very fast submissions. | ||
| 30 | #if ($elapsed > 0 && $elapsed < 5000) | ||
| 31 | #set ($spamScore = $spamScore + 3) | ||
| 32 | #end | ||
| 33 | #end | ||
| 34 | #end | ||
| 35 | |||
| 36 | #if ("$!email" == '') | ||
| 37 | #set ($message = 'Please enter your email address.') | ||
| 38 | #elseif (!$email.matches('^[^@\s]+@[^@\s]+\.[^@\s]+$')) | ||
| 39 | #set ($message = 'Please enter a valid email address.') | ||
| 40 | #elseif ($spamScore >= 3) | ||
| 41 | #set ($message = 'The theme collection could not be sent. Please try again or contact Agnease by email.') | ||
| 42 | #else | ||
| 43 | #try('themeRequestException') | ||
| 44 | #set ($themePackageName = 'AgneaseXWikiColorThemes.xar') | ||
| 45 | #set ($themePackageAttachment = $doc.getAttachment($themePackageName)) | ||
| 46 | $themePackageAttachment | ||
| 47 | |||
| 48 | #if (!$themePackageAttachment) | ||
| 49 | #set ($statusCode = 400) | ||
| 50 | #set ($message = "The theme package attachment could not be found on this page: ${themePackageName}.") | ||
| 51 | #else | ||
| 52 | |||
| 53 | ## Save a lightweight lead using the same ContactRequest storage approach. | ||
| 54 | #try('saveLeadException') | ||
| 55 | #set ($className = 'Agnease.Code.ContactRequest.ContactRequestClass') | ||
| 56 | #set ($now = $datetool.get('yyyyMMddHHmm')) | ||
| 57 | #set ($random = $mathtool.random(100000, 999999)) | ||
| 58 | #set ($uniqueName = "ProductRequest-${now}-${random}") | ||
| 59 | #set ($contactRequestDoc = $xwiki.getDocumentAsAuthor('ContactRequests.' + $uniqueName)) | ||
| 60 | #set ($contactRequestObj = $contactRequestDoc.getObject($className, true)) | ||
| 61 | |||
| 62 | #set ($discard = $contactRequestObj.set('email', $email)) | ||
| 63 | |||
| 64 | #if ("$!sourcePage" != '') | ||
| 65 | #set ($discard = $contactRequestObj.set('sourcePage', $sourcePage)) | ||
| 66 | #else | ||
| 67 | #set ($discard = $contactRequestObj.set('sourcePage', $doc.fullName)) | ||
| 68 | #end | ||
| 69 | |||
| 70 | #set ($discard = $contactRequestObj.set('scope', 'Requested the free Agnease XWiki color theme collection.')) | ||
| 71 | #set ($discard = $contactRequestDoc.saveAsAuthor()) | ||
| 72 | #end | ||
| 73 | |||
| 74 | ## The ProductsMailTemplate page is protected and only viewable to administrators, | ||
| 75 | ## so the mail template must be prepared with the rights of the product page author. | ||
| 76 | #set ($originalCurrentUserReference = $xcontext.context.userReference) | ||
| 77 | #set ($discard = $xcontext.context.setUserReference($doc.author)) | ||
| 78 | |||
| 79 | #try('sendProductEmailException') | ||
| 80 | ## Send the product email to the visitor. | ||
| 81 | #set ($emailSubject = 'Agnease XWiki color theme collection') | ||
| 82 | #set ($templateReference = $services.model.createDocumentReference('', 'Newsletter', 'ProductsMailTemplate')) | ||
| 83 | #set ($themePackageAttachments = [$themePackageAttachment]) | ||
| 84 | |||
| 85 | #set ($templateParameters = { | ||
| 86 | 'velocityVariables': { | ||
| 87 | 'subject': $emailSubject, | ||
| 88 | 'productName': 'Agnease XWiki Theme Moods', | ||
| 89 | 'themePackageName': $themePackageName, | ||
| 90 | 'recipientEmail': $email, | ||
| 91 | 'sourcePage': $doc.fullName, | ||
| 92 | 'isNewsletter': false | ||
| 93 | }, | ||
| 94 | 'attachments': $themePackageAttachments | ||
| 95 | }) | ||
| 96 | |||
| 97 | #set ($productMessage = $services.mail.sender.createMessage($email, $emailSubject)) | ||
| 98 | #set ($discard = $productMessage.setType('Agnease Product Download')) | ||
| 99 | |||
| 100 | #set ($templatePartAdded = false) | ||
| 101 | |||
| 102 | #try('templatePartException') | ||
| 103 | #set ($templatePart = $productMessage.addPart('xwiki/template', $templateReference, $templateParameters)) | ||
| 104 | #if ($templatePart) | ||
| 105 | #set ($templatePartAdded = true) | ||
| 106 | #end | ||
| 107 | #end | ||
| 108 | |||
| 109 | #if ("$!templatePartException" != '') | ||
| 110 | #set ($statusCode = 400) | ||
| 111 | #set ($message = "Template part error: $exceptiontool.getStackTrace($templatePartException)") | ||
| 112 | #elseif ($services.mail.sender.lastError) | ||
| 113 | #set ($statusCode = 400) | ||
| 114 | #set ($message = "Mail sender error while preparing template: $exceptiontool.getStackTrace($services.mail.sender.lastError)") | ||
| 115 | #elseif (!$templatePartAdded) | ||
| 116 | #set ($statusCode = 400) | ||
| 117 | #set ($message = 'The mail template body part could not be added.') | ||
| 118 | #else | ||
| 119 | #set ($productMailResult = $services.mail.sender.send([$productMessage], 'database')) | ||
| 120 | |||
| 121 | #set ($productMailErrorCount = 0) | ||
| 122 | #set ($productMailErrorSummary = '') | ||
| 123 | |||
| 124 | #foreach ($mailError in $productMailResult.statusResult.getAllErrors()) | ||
| 125 | #set ($productMailErrorCount = $productMailErrorCount + 1) | ||
| 126 | #set ($productMailErrorSummary = "$!mailError.errorSummary $!mailError.errorDescription") | ||
| 127 | #end | ||
| 128 | |||
| 129 | #if ($productMailErrorCount > 0) | ||
| 130 | #set ($statusCode = 400) | ||
| 131 | #set ($message = "Mail send error: $productMailErrorSummary") | ||
| 132 | #else | ||
| 133 | #set ($statusCode = 200) | ||
| 134 | #set ($message = 'The XWiki color theme collection was sent to your email address.') | ||
| 135 | #end | ||
| 136 | #end | ||
| 137 | #end | ||
| 138 | |||
| 139 | ## Always restore the original current user after preparing/sending the visitor email. | ||
| 140 | #set ($discard = $xcontext.context.setUserReference($originalCurrentUserReference)) | ||
| 141 | |||
| 142 | #if ("$!sendProductEmailException" != '') | ||
| 143 | #set ($statusCode = 400) | ||
| 144 | #set ($message = "Product email error: $exceptiontool.getStackTrace($sendProductEmailException)") | ||
| 145 | #end | ||
| 146 | |||
| 147 | ## Send an internal notification to Agnease. | ||
| 148 | ## Best-effort only; this should not fail the visitor request. | ||
| 149 | #try('notificationException') | ||
| 150 | #set ($notificationRecipient = 'alex@agnease.com') | ||
| 151 | #set ($notificationSubject = 'New Agnease product request: XWiki Color Themes') | ||
| 152 | #set ($notificationBody = " | ||
| 153 | Email: | ||
| 154 | |||
| 155 | |||
| 156 | Product: | ||
| 157 | Agnease XWiki Color Themes | ||
| 158 | |||
| 159 | Theme package: | ||
| 160 | $themePackageName | ||
| 161 | |||
| 162 | Source page: | ||
| 163 | $doc.getExternalURL() | ||
| 164 | |||
| 165 | Lead request page: | ||
| 166 | $!contactRequestDoc.getExternalURL() | ||
| 167 | ") | ||
| 168 | |||
| 169 | #set ($notificationMessage = $services.mail.sender.createMessage($notificationRecipient, $notificationSubject)) | ||
| 170 | #set ($discard = $notificationMessage.setType('Agnease Product Request')) | ||
| 171 | #set ($discard = $notificationMessage.addPart('text/plain', $notificationBody)) | ||
| 172 | #set ($discard = $services.mail.sender.sendAsynchronously([$notificationMessage], 'database')) | ||
| 173 | #end | ||
| 174 | #end | ||
| 175 | #end | ||
| 176 | |||
| 177 | #if ("$!themeRequestException" != '') | ||
| 178 | #set ($statusCode = 400) | ||
| 179 | #set ($message = 'The theme collection could not be sent. Please try again or contact Agnease by email.') | ||
| 180 | #end | ||
| 181 | #end | ||
| 182 | |||
| 183 | #set ($discard = $response.setStatus($statusCode)) | ||
| 184 | #jsonResponse({'message': $message}) | ||
| 185 | #end | ||
| 186 | {{/velocity}} | ||
| 187 | |||
| 188 | {{velocity}} | ||
| 189 | ## Page title: Agnease XWiki Atmospheres | ||
| 190 | ## Meta title: Accessible XWiki Color Themes | Agnease Atmospheres | ||
| 191 | ## Meta description: Explore five WCAG-compliant XWiki color themes by Agnease, from Alpine Day to Polar Night, compatible with XWiki 17.10.10, 18.4.2 and 18.5.0. | ||
| 192 | |||
| 193 | #set ($discard = $xwiki.ssx.use('PublicWebSite.WebHome')) | ||
| 194 | |||
| 195 | #set ($themes = [{ | ||
| 196 | 'id': 'polar-night', | ||
| 197 | 'name': 'Polar Night', | ||
| 198 | 'paragraphs': [ | ||
| 199 | 'Polar Night is a refined dark theme for focused work and comfortable reading in low-light environments. Deep navy surfaces, restrained borders and clear blue accents create a precise modern interface inspired by professional development and operations tools.', | ||
| 200 | 'It is particularly well suited to technical documentation, developer portals, engineering knowledge bases and IT operations environments. Carefully balanced text, status and interaction colors keep navigation accessible and content readable without making the interface feel heavy.' | ||
| 201 | ], | ||
| 202 | 'images': [ | ||
| 203 | 'polar-night-user-profile.png', | ||
| 204 | 'polar-night-home.png', | ||
| 205 | 'polar-night-search.png', | ||
| 206 | 'polar-night-notifications.png', | ||
| 207 | 'polar-night-sandbox.png', | ||
| 208 | 'polar-night-mfa.png' | ||
| 209 | ] | ||
| 210 | },{ | ||
| 211 | 'id': 'alpine-day', | ||
| 212 | 'name': 'Alpine Day', | ||
| 213 | 'paragraphs': [ | ||
| 214 | 'Alpine Day combines glacier-blue and cool blue-gray surfaces with deep teal navigation and crisp navy typography. The restrained palette gives XWiki a fresh, contemporary mood while preserving the clarity and visual hierarchy expected from a professional knowledge platform.', | ||
| 215 | 'It is a natural fit for customer portals, service platforms, modern intranets and technical knowledge bases. Alpine Day offers a cooler and more expressive alternative to the default Iceberg theme while remaining calm, practical and comfortable throughout the working day.' | ||
| 216 | ], | ||
| 217 | 'images': [ | ||
| 218 | 'alpine-day-home.png', | ||
| 219 | 'alpine-day-search.png', | ||
| 220 | 'alpine-day-notifications.png', | ||
| 221 | 'alpine-day-user-profile.png', | ||
| 222 | 'alpine-day-sandbox.png', | ||
| 223 | 'alpine-day-mfa.png' | ||
| 224 | ] | ||
| 225 | },{ | ||
| 226 | 'id': 'ivory-ledger', | ||
| 227 | 'name': 'Ivory Ledger', | ||
| 228 | 'paragraphs': [ | ||
| 229 | 'Ivory Ledger is a warm, paper-inspired theme for content-rich XWiki environments. Ivory surfaces, muted earthy accents and carefully balanced typography create an editorial atmosphere designed to make substantial pages and long reading sessions feel more comfortable.', | ||
| 230 | 'It is especially well suited to legal documentation, policies, educational resources, research material and long-form technical content. The warmer palette softens the clinical appearance of a conventional white interface while retaining strong contrast, structure and readability.' | ||
| 231 | ], | ||
| 232 | 'images': [ | ||
| 233 | 'ivory-ledger-mfa.png', | ||
| 234 | 'ivory-ledger-home.png', | ||
| 235 | 'ivory-ledger-search.png', | ||
| 236 | 'ivory-ledger-notifications.png', | ||
| 237 | 'ivory-ledger-user-profile.png', | ||
| 238 | 'ivory-ledger-sandbox.png' | ||
| 239 | ] | ||
| 240 | },{ | ||
| 241 | 'id': 'ember-night', | ||
| 242 | 'name': 'Ember Night', | ||
| 243 | 'paragraphs': [ | ||
| 244 | 'Ember Night pairs deep charcoal surfaces with controlled amber and copper accents for a dark theme with a confident industrial character. Warm highlights make navigation and important actions easy to identify without adding excessive brightness or visual noise.', | ||
| 245 | 'Designed for engineering teams, industrial operations, monitoring environments and technical organizations, it brings the comfort of a dark interface to sustained everyday work. Its warmer character provides a clear alternative to the cool blue direction of Polar Night.' | ||
| 246 | ], | ||
| 247 | 'images': [ | ||
| 248 | 'ember-night-notifications.png', | ||
| 249 | 'ember-night-home.png', | ||
| 250 | 'ember-night-search.png', | ||
| 251 | 'ember-night-user-profile.png', | ||
| 252 | 'ember-night-sandbox.png', | ||
| 253 | 'ember-night-mfa.png' | ||
| 254 | ] | ||
| 255 | },{ | ||
| 256 | 'id': 'verdant-office', | ||
| 257 | 'name': 'Verdant Office', | ||
| 258 | 'paragraphs': [ | ||
| 259 | 'Verdant Office introduces a composed green visual language associated with balance, clarity and structured work. Soft neutral surfaces keep the content at the center, while restrained forest and sage accents guide users through navigation, links and important actions.', | ||
| 260 | 'The theme suits healthcare, sustainability, HSE, public-sector and corporate knowledge platforms particularly well. It creates a dependable and welcoming working environment without becoming decorative, overly branded or visually demanding.' | ||
| 261 | ], | ||
| 262 | 'images': [ | ||
| 263 | 'verdant-office-home.png', | ||
| 264 | 'verdant-office-search.png', | ||
| 265 | 'verdant-office-notifications.png', | ||
| 266 | 'verdant-office-user-profile.png', | ||
| 267 | 'verdant-office-sandbox.png', | ||
| 268 | 'verdant-office-mfa.png' | ||
| 269 | ] | ||
| 270 | }]) | ||
| 271 | |||
| 272 | {{html clean="false"}} | ||
| 273 | <section class="hero hero-centered" aria-labelledby="themes-title"> | ||
| 274 | <div class="container hero-inner"> | ||
| 275 | <div class="hero-kicker"> | ||
| 276 | <i class="fa fa-paint-brush" aria-hidden="true"></i> | ||
| 277 | Agnease color themes for XWiki | ||
| 278 | </div> | ||
| 279 | |||
| 280 | <h1 id="themes-title">XWiki Color Themes by Agnease</h1> | ||
| 281 | |||
| 282 | <p class="lead"> | ||
| 283 | Explore five carefully designed light and dark alternatives to the default Iceberg theme, | ||
| 284 | each giving XWiki a distinct visual mood without changing the familiar platform experience. | ||
| 285 | </p> | ||
| 286 | |||
| 287 | <ul class="benefits"> | ||
| 288 | <li>XWiki 17.10.10 LTS</li> | ||
| 289 | <li>XWiki 18.4.2 Intermediary LTS</li> | ||
| 290 | <li>XWiki 18.5.0 Stable</li> | ||
| 291 | <li>WCAG-compliant color contrast</li> | ||
| 292 | </ul> | ||
| 293 | </div> | ||
| 294 | </section> | ||
| 295 | |||
| 296 | #foreach ($theme in $themes) | ||
| 297 | <section aria-labelledby="${theme.id}-title"> | ||
| 298 | <div class="container"> | ||
| 299 | <div class="product-gallery-panel"> | ||
| 300 | <h2 id="${theme.id}-title">$theme.name</h2> | ||
| 301 | |||
| 302 | #foreach ($paragraph in $theme.paragraphs) | ||
| 303 | <p class="gallery-note">$paragraph</p> | ||
| 304 | #end | ||
| 305 | |||
| 306 | {{/html}} | ||
| 307 | |||
| 308 | {{gallery}} | ||
| 309 | #foreach ($image in $theme.images) | ||
| 310 | [[image:$image]] | ||
| 311 | #end | ||
| 312 | {{/gallery}} | ||
| 313 | |||
| 314 | {{html clean="false"}} | ||
| 315 | </div> | ||
| 316 | </div> | ||
| 317 | </section> | ||
| 318 | #end | ||
| 319 | |||
| 320 | <section class="cta-section" aria-labelledby="themes-final-cta-title"> | ||
| 321 | <div class="container"> | ||
| 322 | <div class="cta-panel"> | ||
| 323 | |||
| 324 | <h2 id="themes-final-cta-title">Explore XWiki in a different mood</h2> | ||
| 325 | |||
| 326 | <p> | ||
| 327 | Start with the five ready-made themes or send your colors and logo to receive | ||
| 328 | a custom light and dark theme for your organization. | ||
| 329 | </p> | ||
| 330 | |||
| 331 | <form id="themeCollectionForm" class="form-inline"> | ||
| 332 | <div class="form-group"> | ||
| 333 | <label class="sr-only" for="themeCollectionEmail">Email address</label> | ||
| 334 | <input | ||
| 335 | id="themeCollectionEmail" | ||
| 336 | type="email" | ||
| 337 | name="email" | ||
| 338 | class="form-control" | ||
| 339 | placeholder="your@email.com" | ||
| 340 | required | ||
| 341 | /> | ||
| 342 | </div> | ||
| 343 | |||
| 344 | <div class="contact-hp-wrapper" aria-hidden="true"> | ||
| 345 | <label for="themeCollectionWebsite">Website</label> | ||
| 346 | <input | ||
| 347 | id="themeCollectionWebsite" | ||
| 348 | type="text" | ||
| 349 | name="contactWebsite" | ||
| 350 | autocomplete="off" | ||
| 351 | tabindex="-1" | ||
| 352 | /> | ||
| 353 | </div> | ||
| 354 | |||
| 355 | <input type="hidden" name="contactStartedAt" value="$datetool.systemDate.time" /> | ||
| 356 | <input type="hidden" name="sourcePage" value="$doc.fullName" /> | ||
| 357 | |||
| 358 | <div class="hero-actions"> | ||
| 359 | <input | ||
| 360 | id="themeCollectionSubmit" | ||
| 361 | type="submit" | ||
| 362 | class="btn btn-secondary" | ||
| 363 | value="Get the free theme collection" | ||
| 364 | /> | ||
| 365 | |||
| 366 | <a | ||
| 367 | id="customThemeButton" | ||
| 368 | class="btn btn-secondary" | ||
| 369 | href="$xwiki.getURL('contact.WebHome')" | ||
| 370 | > | ||
| 371 | Request a custom theme | ||
| 372 | </a> | ||
| 373 | </div> | ||
| 374 | </form> | ||
| 375 | {{/html}} | ||
| 376 | {{html clean="false" wiki="true"}} | ||
| 377 | <div class="reviewNotifications"> | ||
| 378 | <div class="hidden reviewNotificationSuccess"> | ||
| 379 | |||
| 380 | {{success}}reviewNotification{{/success}} | ||
| 381 | |||
| 382 | </div> | ||
| 383 | |||
| 384 | <div class="hidden reviewNotificationError"> | ||
| 385 | |||
| 386 | {{error}}reviewNotification{{/error}} | ||
| 387 | |||
| 388 | </div> | ||
| 389 | </div> | ||
| 390 | |||
| 391 | </div> | ||
| 392 | </div> | ||
| 393 | </section> | ||
| 394 | {{/html}} | ||
| 395 | {{/velocity}} |