1<!DOCTYPE html> 2<html lang="en"> 3 <head> 4 5 6 <title>Right-Click Select — 7Blender Community 8 </title> 9 <meta name="theme-color" content="#f28128"> 10 11<meta charset="utf-8"/> 12<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/> 13<meta name="description" content="Ideas for Blender 14"/> 15<meta name="keywords" content="animation,animate,2d,3d,2danimation,cg,motiongraphics,blender,greasepencil,toonboom,tvpaint,flash"/> 16 17<meta property="fb:app_id" content="384707122164385"/> 18<meta name="twitter:card" content="summary_large_image"/> 19<meta name="twitter:site" content="@BlenderToday"/> 20<meta property="og:site_name" content="Blender Community"/> 21<meta property="og:url" content="https://blender.community/c/rightclickselect/"/> 22<meta property="og:type" content="website"/> 23<meta name="twitter:title" content="Right-Click Select"/> 24<meta property="og:title" content="Right-Click Select"/> 25<meta name="twitter:description" content="Ideas for Blender 26"/> 27<meta property="og:description" content="Ideas for Blender 28"/> 29<meta name="twitter:image" content="https://blender.community/static/images/social.jpg"/> 30<meta name="twitter:image:alt" content="Right-Click Select"/> 31<meta property="og:image" content="https://blender.community/static/images/social.jpg"/> 32<meta property="og:image:width" content="1280"/> 33<meta property="og:image:height" content="720"/> 34 35 36<link rel="shortcut icon" type="image/jpg" href="https://d3a2gvihmbqfjo.cloudfront.net/cache/70/12/70129cd159e57d165040904b6cd7d089.png"/> 37 38 39 40<link href="https://blender.community/static/css/main.cc0c2e7493f8.css" rel="stylesheet" type="text/css" media="screen,projection" /> 41 42 43<style>:root { 44 --community-theme-color: #f28128; 45} 46</style> 47 48 49 </head> 50 <body class="community-rightclickselect"> 51 52 53 <div id="app" :class="{'is-entity-active': activeEntity, 'is-loading': isLoading}" class="app-container"> 54 <modal-container @toggle-modal="toggleModal" v-if="activeModalContent" :modal-type="activeModalType" :modal-title="activeModalTitle" :modal-content="activeModalContent"></modal-container> 55 <nav class="app-nav"> 56 57 58<ul class="communities"> 59 <li class="c-main "><a data-tooltip="true" data-tippy-content="Home" data-tippy-placement="right" href="/" class="profile-avatar-container"> 60 <div class="community-logo"><i class="i-home"></i> 61 </div></a><a href="/" class="community-name">Home</a> 62 </li> 63 64 65 <li class="c-rightclickselect is-active"><a data-tippy-content="Right-Click Select" data-tippy-placement="right" data-tooltip="true" href="/c/rightclickselect/" title="Right-Click Select" class="profile-avatar-container "> 66 <div class="profile-avatar avatar-size-medium"><img src="https://d3a2gvihmbqfjo.cloudfront.net/cache/70/12/70129cd159e57d165040904b6cd7d089.png" width="128" height="128" alt="Right-Click Select"/> 67 </div></a><a href="/c/rightclickselect/" class="community-name">Right-Click Select</a> 68 69 </li> 70 <li class="separator"> 71 <hr/> 72 </li> 73 <li><a data-tooltip="true" data-tippy-content="All Communities" data-tippy-placement="right" href="/c/" class="profile-avatar-container"> 74 <div class="community-logo"><img src="https://blender.community/static/favicon/favicon.58112853b96c.png" alt="All Communities"/> 75 </div></a><a href="/" class="community-name">All Communities</a> 76 </li> 77 <li class="c-about"><a data-tooltip="true" data-tippy-content="About" data-tippy-placement="right" href="/about/" class="profile-avatar-container"> 78 <div class="profile-avatar avatar-size-medium"><i class="i-help"></i> 79 </div><a href="About" class="community-name">About</a></a> 80 </li> 81</ul> 82 </nav> 83 84 85 <main class="app-main"> 86 87 88<header class="app-main-header"> 89 <div v-if="isLoading" class="app-loader"></div> 90 <div class="header-title"><button class="btn-navbar-toggle js-navbar-toggle"><span><i class="i-menu"></i></span> 91</button><span><a data-tippy-content="Right-Click Select" data-tippy-placement="" data-tooltip="false" href="/c/rightclickselect/" title="Right-Click Select" class="profile-avatar-container "> 92 <div class="profile-avatar avatar-size-medium"><img src="https://d3a2gvihmbqfjo.cloudfront.net/cache/70/12/70129cd159e57d165040904b6cd7d089.png" width="128" height="128" alt="Right-Click Select"/> 93 </div></a></span><span><a href="/c/rightclickselect/" class="header-community-title">Right-Click Select</a></span> 94 </div> 95 <div v-if="isSearchEnabled" class="header-search"> 96 <div class="form-group"> 97 <input @keyup="setQueryText" @keydown.enter="getEntities" v-model="query.text" placeholder="Search..." class="form-control"/> 98 <div class="btn-toolbar-row"> 99 <div class="btn-group"> 100 <button @click="clearQueryText" v-if="query.text" class="btn-clear"><i class="i-cancel"></i> 101 </button> 102 <button @click="getEntities" class="btn-send"><i class="i-search"></i> 103 </button> 104 </div> 105 </div> 106 </div> 107 </div> 108 <div class="header-entity-detail-close"> 109 <button class="btn-icon js-entity-detail-close"><span><i class="i-arrow-left"></i></span> 110 </button> 111 </div> 112 <div class="header-action-bar"> 113 <div class="btn-toolbar-row"> 114<div class="header-create-buttons"><a data-tooltip="true" data-tippy-content="Submit an Idea" href="/c/rightclickselect/create" class="btn-theme"><span><i class="i-idea"></i><span>Submit Idea</span></span></a> 115</div> 116 117 118<a href="/accounts/login/" title="Login with Blender ID" class="btn-link"><span><i class="i-log-in"></i><span>Login</span></span></a> 119 </div> 120 </div> 121</header> 122 123 124<div class="app-main-content"> 125 <div class="app-feed"> 126 <filter-sort-toolbar :community-categories="community.categories" :development-statuses="community.developmentStatuses" :sorting-orders="community.sortingOrders" :query="query" :query-extension="queryExtension" @set-query-category="setQueryCategory" @set-query-sorting="setQuerySorting" @set-query-development-status="setQueryDevelopmentStatus"></filter-sort-toolbar><ul v-if="entities && entities.length > 0" class="entities-list"> 127 <entity-list-item v-for="entity in entities" v-bind:key="entity.id" v-bind:entity="entity" v-bind:active-entity="activeEntity" @set-active-entity="setActiveEntity"></entity-list-item> 128 <li v-if="nextPageNumber"> 129 <div class="btn-load-more-container"> 130 <button @click="appendEntitiesFromNextPage" :class="{'is-loading': isLoadingNextPage}" class="btn-wide"><span>Load More </span> 131 </button> 132 </div> 133 </li> 134</ul> 135<div v-else="v-else" class="entities-list-empty">No items to show. 136</div> 137 </div> 138 <div class="app-detail"> 139 <div v-if="activeEntity"> 140 <entity-detail @set-active-reply-form-comment-id="activeReplyFormCommentId = $event" @set-active-entity="setActiveEntity" @set-active-entity-reaction="setActiveEntityReaction" @set-active-entity-comments-reaction="setActiveEntityCommentsReaction" @append-entity-comments-from-next-page="appendEntityCommentsFromNextPage" @toggle-modal="toggleModal" @set-query-category="setQueryCategory" :active-reply-form-comment-id="activeReplyFormCommentId" :entity="activeEntity" :entity-type="'idea'" :comments="activeEntityComments" :current-user="currentUser" :url-api-comment-list-view-next-page="urlApiCommentListViewNextPage"></entity-detail> 141 </div> 142 <div v-else="v-else" class="community-detail"> 143 <div class="community-detail-header"> 144<a data-tippy-content="Right-Click Select" data-tippy-placement="" data-tooltip="false" href="/c/rightclickselect/" title="Right-Click Select" class="profile-avatar-container "> 145 <div class="profile-avatar avatar-size-large"><img src="https://d3a2gvihmbqfjo.cloudfront.net/cache/70/12/70129cd159e57d165040904b6cd7d089.png" width="128" height="128" alt="Right-Click Select"/> 146 </div></a> 147 148<div> 149 <h1 class="community-detail-title">Right-Click Select</h1> 150 <div class="community-detail-tagline">Ideas for Blender</div> 151 <div class="btn-toolbar-row"> 152 <follow-toggle :url-follow-toggle="community.urlFollowToggle" :is-followed="community.isFollowed"></follow-toggle> 153<a data-tooltip="true" data-tippy-content="Website" href="https://chat.blender.org/#/room/#rightclickselect:blender.org" target="_blank" title="Website" class="btn-link btn-icon"><span><i class="i-link"></i></span></a> 154 155 </div> 156</div> 157 </div> 158 <div class="community-detail-content"><p> 159 Got an idea for a feature that you think 160 <a class="is-external" href="https://blender.org" rel="nofollow" target="_blank"> 161 Blender 162 </a> 163 absolutely 164 <em> 165 must 166 </em> 167 have? This is the place to make your proposals. Share your idea with the community. Discuss it. Revise it. Flesh it out to the point that it's a well thought-out, 168 <em> 169 actionable 170 </em> 171 proposal that a Blender developer can work with. 172</p> 173<p> 174 Don't have any ideas right now? That's OK! Your experience counts. Help other people improve their ideas by voting and offering comments and insights. 175</p> 176 177 178 </div> 179 </div> 180 181<footer class="site-footer"> 182 <div class="btn-toolbar-row"><a data-tooltip="true" data-tippy-content="Follow Blender Today on Twitter" href="https://twitter.com/BlenderToday" target="_blank" title="Follow Blender Today on Twitter" class="btn-link btn-icon"><span><i class="i-twitter"></i></span></a><a data-tooltip="true" data-tippy-content="Blender Community on YouTube" href="https://www.youtube.com/BlenderCommunity" target="_blank" title="Blender Community on YouTube" class="btn-link btn-icon"><span><i class="i-youtube"></i></span></a> 183 </div> 184 <div class="footer-nav"> 185 <ul> 186 <li><a href="https://github.com/armadillica/dillo">give feedback</a> 187 </li> 188 <li><a href="/about/">about</a> 189 </li> 190 <li><a href="/privacy/">privacy</a> 191 </li> 192 <li><a href="/terms/">terms</a> 193 </li> 194 </ul> 195 </div> 196 197</footer> 198 </div> 199</div> 200 201 </main> 202 </div> 203 204 205
206<script type="text/javascript" src="https://blender.community/static/js/vendors.237e09e287be.js" charset="utf-8"></script>
206 207
207<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
207 208
208<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
208 209
209<script type="text/javascript">axios.defaults.xsrfCookieName = 'csrftoken' 210axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" 211 </script>
211 212 213
213<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
vendor: 1 bytes, line 213
213
214<script type="text/javascript" src="https://blender.community/static/js/mixins.f1a22b079fd4.js" charset="utf-8"></script>
vendor: 1 bytes, line 214
214
215<script type="text/javascript" src="https://blender.community/static/js/filters.8a67c180bc87.js" charset="utf-8"></script>
215 216 217 218 219
220<script id="current-user-data" type="application/json">{"isAuthenticated": false, "isStaff": false, "isModerator": false, "username": null, "avatar": null, "url": null, "name": null, "notificationsCount": 0}</script>
220 221
222<script id="community-data" type="application/json">{"categories": [{"slug": "", "name": "All Categories"}, {"slug": "general", "name": "General"}, {"slug": "modeling", "name": "Modeling"}, {"slug": "sculpting", "name": "Sculpting"}, {"slug": "painting", "name": "Painting"}, {"slug": "particles", "name": "Particles"}, {"slug": "simulation", "name": "Simulation"}, {"slug": "animation", "name": "Animation"}, {"slug": "rigging", "name": "Rigging"}, {"slug": "rendering", "name": "Rendering"}, {"slug": "compositing", "name": "Compositing"}, {"slug": "motion-tracking", "name": "Motion Tracking"}, {"slug": "user-interface", "name": "User Interface"}, {"slug": "interoperability", "name": "Interoperability"}, {"slug": "game-engine", "name": "Game Engine"}, {"slug": "meta", "name": "Meta"}, {"slug": "scripting", "name": "Scripting"}, {"slug": "grease-pencil", "name": "Grease Pencil"}, {"slug": "sequencer", "name": "Video Sequencer"}, {"slug": "geometry-nodes", "name": "Geometry Nodes"}, {"slug": "materials-and-procedural-textures", "name": "Shading and Textures"}, {"slug": "uv", "name": "UV Editing"}, {"slug": "asset-browser", "name": "Asset Browser"}, {"slug": "text-editor", "name": "Text Editor"}, {"slug": "library-overrides", "name": "Library Overrides"}, {"slug": "outliner", "name": "Outliner"}, {"slug": "viewport", "name": "3D Viewport"}, {"slug": "benchmark", "name": "Benchmark"}, {"slug": "extensions", "name": "Extensions"}], "sortingOrders": [{"slug": "hot", "name": "Hot"}, {"slug": "new", "name": "New"}, {"slug": "top", "name": "Top"}], "slug": "rightclickselect", "activeEntityApiDetailViewUrl": null, "isFollowed": false, "name": "Right-Click Select", "urlFollowToggle": "/api/follow-toggle/15/30/", "urlApiEntityListView": "/api/rightclickselect/ideas/", "developmentStatuses": [{"slug": "", "name": "All Statuses"}, {"slug": "open", "name": "Open"}, {"slug": "in_development", "name": "In Development"}, {"slug": "done", "name": "Done"}, {"slug": "archived", "name": "Archived"}, {"slug": "incomplete", "name": "Incomplete"}]}</script>
222 223 224 225 226 227 228
229<script type="text/x-template" id="notifications-template"> 230 <div><a @click.stop="toggleNotifications" :class="{'has-notifications': hasNotifications, 'is-active': isDropdownOpen}" :data-notifications-count="initialNotificationsCount" href="#" id="js-tippy-notifications" class="btn btn-icon btn-link notifications-toggle"><span><i class="i-bell"></i></span></a> 231 <div v-if="isDropdownOpen"> 232 <div class="notifications-container"> 233 <div class="notifications-header"><span>Activity</span><a @click.stop="markAllAsRead" href="#">Mark all as read</a> 234 </div> 235 <div class="notifications-content"> 236 <div v-if="notifications && notifications.length == 0" class="notifications-empty">No notifications yet. 237 </div> 238 <ul v-if="notifications && notifications.length > 0"> 239 <div v-for="notification in notifications" :class="'activity-verb-' + notification.verbSlug" class="activity-list-item"><a :href="notification.actionObjectUrl" class="activity-list-item-icon"><i :class="'i-' + notification.verbSlug"></i></a> 240 <div class="activity-list-item-content"> 241 <div class="activity-list-item-header"><a :href="notification.actor.profileUrl" class="actor">{{ notification.actor.username }} 242</a><span>{{ notification.verb }}</span> 243 </div><a :href="notification.actionObjectUrl" :title="notification.actionObject" class="activity-list-item-action">{{ notification.actionObject }} 244</a><a :href="notification.actionObjectUrl" class="date">{{ notification.timeSince }}</a> 245 </div> 246 </div> 247 </ul> 248 </div> 249 <div v-if="urlNextPage" class="modal-action-container"> 250 <button @click="loadNextPage" :class="{'is-loading': isLoadingNotifications}" class="btn-wide btn-secondary js-toggle-prevent"><span>Load More</span> 251 </button> 252 </div> 253 </div> 254 </div> 255 </div> 256</script>
vendor: 1 bytes, line 256
256
257<script type="text/javascript">Vue.component('notifications', { 258 mixins: [dropdownMixin], 259 template: '#notifications-template', 260 props: { 261 initialNotificationsCount: Number, 262 // Define the base url, queried everytime the dropdown is expanded 263 urlNotifications: String, 264 urlMarkAllAsRead: String, 265 }, 266 data: function () { 267 return { 268 notifications: null, 269 urlNextPage: null, 270 notificationsCount: 0, 271 isLoadingNotifications: false, 272 } 273 }, 274 computed: { 275 hasNotifications: function () { 276 return this.notificationsCount && this.notificationsCount > 0 277 } 278 }, 279 methods: { 280 loadNotifications: function (urlNotifications, append) { 281 this.isLoadingNotifications = true; 282 axios 283 .get(urlNotifications) 284 .then(response => { 285 if (append === true) { 286 this.notifications.push(...response.data.results) 287 } else { 288 this.notifications = response.data.results; 289 } 290 this.urlNextPage = response.data.urlNextPage; 291 292 this.isLoadingNotifications = false; 293 }) 294 }, 295 toggleNotifications: function () { 296 if (this.isDropdownOpen) { 297 return this.hideDropdown() 298 } 299 this.loadNotifications(this.urlNotifications) 300 return this.showDropdown() 301 }, 302 loadNextPage: function () { 303 this.loadNotifications(this.urlNextPage, true) 304 }, 305 markAllAsRead: function () { 306 axios 307 .post(this.urlMarkAllAsRead) 308 .then(response => { 309 // Remove the notifications count from the page title. 310 if (this.notificationsCount > 0) { 311 document.title = document.title.split(") ").pop(); 312 } 313 314 this.notificationsCount = 0; 315 Snackbar.show({text: 'All notifications marked as read!', icon: 'i-check', showAction: false, customClass: 'success'}); 316 }) 317 } 318 }, 319 mounted () { 320 tippy('#js-tippy-notifications', {content: 'Notifications'}) 321 this.notificationsCount = this.initialNotificationsCount; 322 323 // Append the notifications count to the page title. 324 if (this.notificationsCount > 0) { 325 document.title = '(' + this.notificationsCount + ') ' + document.title.replace(/ *\([^)]*\) */g, ""); 326 } 327 } 328}) 329</script>
329 330
331<script type="text/x-template" id="modal-container-template"> 332 <div :class="'modal-type-' + modalType" role="dialog" tabindex="-1" class="modal-container"> 333 <div @click="closeModal" class="modal-backdrop"></div> 334 <div role="document" class="modal-dialog"> 335 <div class="modal-content"> 336 <div class="modal-header"> 337 <h2 class="modal-title">{{ modalTitle }}</h2> 338 <button @click="closeModal" aria-label="Close" type="button"><span aria-hidden="true"><i class="i-cancel"></i></span> 339 </button> 340 </div> 341 <modal-body-share v-if="modalType === 'share'" :absoluteUrl="modalContent.absoluteUrl"></modal-body-share> 342 <modal-body-generic-form v-if="modalType === 'generic_form'" :urlForm="modalContent.urlForm"></modal-body-generic-form> 343 <modal-body-user-list v-if="modalType === 'userList'" :urlUserList="modalContent.urlUserList"></modal-body-user-list> 344 </div> 345 </div> 346 </div> 347</script>
vendor: 1 bytes, line 347
347
348<script type="application/javascript">Vue.component('modal-container', { 349 template: '#modal-container-template', 350 props: {modalType: String, modalTitle: String, modalContent: Object }, 351 methods: { 352 closeModal: function() { 353 this.$emit('toggle-modal') 354 } 355 } 356}) 357</script>
357 358
359<script type="text/x-template" id="modal-body-share-template"> 360 <div class="modal-body"> 361 <div class="btn-toolbar-row"><a :href="'https://www.facebook.com/sharer/sharer.php?u=' + absoluteUrlWithDomain" title="Share on Facebook" class="btn-icon btn-social-facebook"><span><i class="i-facebook"></i></span></a><a :href="'https://twitter.com/intent/tweet?text=' + absoluteUrlWithDomain" title="Share on Twitter" class="btn-icon btn-social-twitter"><span><i class="i-twitter"></i></span></a><a :href="'https://www.linkedin.com/sharing/share-offsite/?url=' + absoluteUrlWithDomain" title="Share on LinkedIn" class="btn-icon btn-social-linkedin"><span><i class="i-linkedin"></i></span></a> 362 </div> 363 </div> 364</script>
vendor: 1 bytes, line 364
364
365<script type="application/javascript">Vue.component('modal-body-share', { 366 template: '#modal-body-share-template', 367 props: {absoluteUrl: String }, 368 computed: { 369 absoluteUrlWithDomain: function () { 370 return window.location.href; 371 } 372 }, 373}) 374</script>
374 375
376<script type="text/x-template" id="modal-body-generic-form-template"> 377 <div v-html="formHtml" class="modal-body"></div> 378</script>
vendor: 1 bytes, line 378
378
379<script type="application/javascript">Vue.component('modal-body-generic-form', { 380 template: '#modal-body-generic-form-template', 381 props: {urlForm: String }, 382 data: function () { 383 return { 384 formHtml: null 385 } 386 }, 387 mounted: function (){ 388 axios 389 .get(this.urlForm) 390 .then(response => { 391 this.formHtml = response.data; 392 }) 393 .then(() => { 394 let form = document.getElementById("generic_form"); 395 form.addEventListener("submit", this.submitForm); 396 }); 397 }, 398 methods: { 399 submitForm: function (event) { 400 event.preventDefault(); 401 const formData = new FormData(event.target) 402 const params = new URLSearchParams(); 403 for (let [key, val] of formData.entries()) { 404 params.set(key, val) 405 } 406 const config = { 407 headers: { 408 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 409 } 410 } 411 axios.post(this.urlForm, params, config) 412 .then(response => {this.formHtml = response.data;}) 413 .then(() => { 414 let form = document.getElementById("generic_form"); 415 form.addEventListener("submit", this.submitForm); 416 }) 417 } 418 }, 419}) 420</script>
420 421
422<script type="text/x-template" id="modal-body-user-list-template"> 423 <div> 424 <div class="modal-body"> 425 <div v-if="isLoading" class="modal-loading">Loading...</div> 426 <div v-if="! isLoading && userList.length === 0" class="modal-loading">No votes found</div> 427 <ul> 428 <li v-for="(user, index) in userList"> 429 <div class="profile-list-item"> 430 <user-avatar :size="'medium'" :user="user"></user-avatar><a :href="user.url" class="profile-list-info"> 431 <div class="profile-list-title"><span>{{ user.name }}</span> 432 </div> 433 <div class="profile-list-subtitle"><span>{{ user.username }}</span> 434 </div></a> 435 </div> 436 </li> 437 </ul> 438 </div> 439 <div v-if="urlNextPage" class="modal-action-container"> 440 <button @click="loadNextPage" class="btn-wide btn-secondary"><span>Load More</span> 441 </button> 442 </div> 443 </div> 444</script>
vendor: 1 bytes, line 444
444
445<script type="application/javascript">Vue.component('modal-body-user-list', { 446 template: '#modal-body-user-list-template', 447 props: {urlUserList: String }, 448 data: function () { 449 return { 450 isLoading: true, 451 userList: [], 452 urlNextPage: null 453 } 454 }, 455 mounted: function (){ 456 this.loadUserList(this.urlUserList); 457 }, 458 methods: { 459 loadUserList: function (urlUserList, append) { 460 axios 461 .get(urlUserList) 462 .then(response => { 463 this.isLoading = false; 464 if (append === true) { 465 this.userList.push(...response.data.results) 466 } else { 467 this.userList = response.data.results; 468 } 469 this.urlNextPage = response.data.urlNextPage; 470 }) 471 }, 472 loadNextPage: function () { 473 this.loadUserList(this.urlNextPage, true); 474 } 475 } 476}) 477</script>
477 478 479
480<script type="text/x-template" id="user-avatar-template"><a :href="user.url" :title="user.username" data-placement="" data-toggle="" class="profile-avatar-container"> 481 <div :class="sizeClass" class="profile-avatar"><img v-if="user.avatar" :alt="user.username" :src="user.avatar" height="128" width="128"/><i v-else="v-else" class="i-user"></i> 482 </div></a> 483</script>
vendor: 1 bytes, line 483
483
484<script>Vue.component('user-avatar', { 485 template: '#user-avatar-template', 486 props: {user: Object, size: String}, 487 488 computed: { 489 sizeClass: function () { 490 let size = this.size ? this.size : 'medium'; 491 return 'avatar-size-' + size; 492 } 493 }, 494}) 495</script>
495 496
497<script type="text/x-template" id="reactions-template"> 498 <div v-if="widgetType == 'icon'" class="btn-group"> 499 <div @click="likeToggle" :class="{'is-liked': isLiked}" data-toggle="tooltip" title="Like this comment" class="btn-like"><span><i :class="[isLiked ? 'i-heart-filled' : 'i-heart']"></i></span> 500 </div> 501 <div v-if="likesCount" @click="toggleModalUserListLiked" :class="{'is-liked': isLiked}" data-toggle="tooltip" title="See who liked this comment" class="btn-like"><span>{{ likesCount }}</span> 502 </div> 503 </div> 504 <div v-else-if="widgetType == 'button'" class="btn-group"> 505 <div @click="likeToggle" :class="{'is-liked': isLiked}" title="Like this post" class="btn-secondary btn-icon"><span><i :class="[isLiked ? 'i-heart-filled' : 'i-heart']"></i><span v-if="!likesCount">Like</span></span> 506 </div> 507 <div v-if="likesCount" @click="toggleModalUserListLiked" :class="{'is-liked': isLiked}" title="See who liked this post" class="btn-secondary"><span v-if="likesCount">{{ likesCount }}</span><span v-else="v-else" @click="likeToggle">Like</span> 508 </div> 509 </div> 510</script>
vendor: 1 bytes, line 510
510
511<script type="text/javascript">Vue.component('reactions', { 512 template: '#reactions-template', 513 props: { 514 // Affect display of the component (either a simple icon, or a button) 515 widgetType: { 516 type: String, 517 default: 'icon', 518 validator: function (value) { 519 // The value must match one of these strings 520 return ['icon', 'button'].indexOf(value) !== -1 521 } 522 }, 523 currentUser: { 524 type: Object, 525 required: true, 526 validator: function (user) { 527 const expectedProps = ['isAuthenticated', 'username', 'name', 'avatar', 'url']; 528 return expectedProps.every(item => user.hasOwnProperty(item)); 529 } 530 }, 531 commentId: Number, 532 likesCount: Number, 533 isLiked: Boolean, 534 isOwn: Boolean, 535 likeToggleUrl: String, 536 entityUrlApiDetailView: String, 537 urlUserListLiked: String 538 }, 539 methods: { 540 likeToggle: function() { 541 // Ensure the user is authenticated, otherwise redirect to login 542 // TODO Add next url parameter 543 if (! this.currentUser.isAuthenticated) { 544 window.location.href = "/accounts/login/"; 545 return; 546 } 547 548 // Prevent own likes. 549 if (!this.isLiked && this.isOwn) { 550 Snackbar.show({text: "Don't we all like our own stuff?", icon: 'i-happy', showAction: false, customClass: 'warning'}) 551 return; 552 } 553 554 // Fake interactivity 555 // Update like/unlike toggle before doing the request. If the request is 556 // unsuccessful, revert to the original values and display an error. 557 let isLiked = !this.isLiked; 558 // Make a deep copy of likesCount so it can be manipulated 559 let likesCount = JSON.parse(JSON.stringify(this.likesCount)); 560 if (isLiked) { 561 likesCount++; 562 } else { 563 likesCount--; 564 } 565 566 // If commentId is defined generate events related to comments, 567 // otherwise generate events related to entities. 568 if (this.commentId) { 569 this.$emit('set-active-entity-comments-reaction', 570 {commentId: this.commentId, isLiked: isLiked, likesCount: likesCount}); 571 } else { 572 this.$emit('set-active-entity-reaction', 573 {isLiked: isLiked, likesCount: likesCount}); 574 } 575 const params = new URLSearchParams(); 576 const config = { 577 headers: { 578 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 579 } 580 } 581 axios 582 .post(this.likeToggleUrl, params, config) 583 .then(response => { 584 // Uncomment this to update component data after the API request has 585 // succeeded 586 // this.likesCountData = response.data.likes_count; 587 // this.isLikedData = response.data.action === 'liked'; 588 // Uncomment this to trigger a full entity reload 589 // this.$emit('set-active-entity', this.entityUrlApiDetailView) 590 }).catch(error => { 591 if (error.response) { 592 Snackbar.show({text: error.response.data, icon: 'i-alert-triangle', showAction: false, customClass: 'error'}) 593 } 594 }); 595 }, 596 toggleModalUserListLiked: function () { 597 this.$emit('toggle-modal', { 598 type: 'userList', 599 title: 'Who liked this', 600 content: {'urlUserList': this.urlUserListLiked} 601 } 602 ) 603 } 604 } 605}) 606</script>
606 607
608<script type="text/x-template" id="follow-toggle-template"><a @click.prevent.stop="followToggle" :href="urlFollowToggle" :class="[isFollowedData ? 'btn-secondary' : 'btn-theme-primary']" :data-tippy-content="[isFollowedData ? 'Stop following this community' : 'Follow this community']" data-tooltip="true"><span><i :class="[isFollowedData ? 'i-user-minus' : 'i-user-plus']"></i><span v-if="isFollowedData">Unfollow</span><span v-else="v-else">Follow</span></span></a> 609</script>
vendor: 1 bytes, line 609
609
610<script type="application/javascript">Vue.component('follow-toggle', { 611template: '#follow-toggle-template', 612props: {urlFollowToggle: String, isFollowed: Boolean}, 613data: function () { 614 return { 615 isFollowedData: null, 616 } 617}, 618mounted() { 619 this.isFollowedData = this.isFollowed 620}, 621methods: { 622 followToggle: function() { 623 axios 624 .get(this.urlFollowToggle) 625 .then(response => { 626 this.isFollowedData = !this.isFollowedData 627 }) 628 } 629 } 630}) 631</script>
631 632
633<script type="text/x-template" id="entity-list-item-template"> 634 <li @click="toggleActive" :class="[entity.isHot ? 'is-hot' : '', isActive ? 'is-active' : '', entity.isPinnedByModerator ? 'is-pinned' : '', entity.votes ? 'idea-status-' + [entity.developmentStatus] : '']" class="entity-list-item"><a v-if="!entity.votes && !entity.operatingSystem" :href="entity.absoluteUrl" :title="entity.categories" class="entity-list-item-icon"><i :class="['i-' + [entity.categories]]"></i></a><a v-if="entity.votes" :href="entity.absoluteUrl" class="entity-list-item-icon"> 635 <div :style="'background-size: 100% ' + [entity.votes.positivePercentage] + '%'" :title="[[entity.votes.positivePercentage] + '% Positive']" class="idea-percent-widget"><i class="i-idea"></i> 636 </div></a><a v-if="entity.operatingSystem" :href="entity.absoluteUrl" :title="entity.operatingSystem.name" class="entity-list-item-icon"><i :class="['i-' + [entity.operatingSystem.slug]]"></i></a> 637 <div class="entity-list-item-content"><a :href="entity.absoluteUrl" class="entity-list-item-title">{{ entity.title }} 638</a> 639 <div class="entity-list-item-meta"> 640 <ul class="entity-list-item-meta-categories"> 641 <li v-if="entity.operatingSystem" :title="entity.operatingSystem.name" class="item-os"><strong>{{ entity.operatingSystem.name }}</strong> 642 </li> 643 644 <li v-if="entity.categories != 0" class="item-categories"><span :title="entity.categories" class="category">{{ entity.categories }}</span> 645 </li> 646 </ul> 647 <ul class="entity-list-item-meta-info"> 648 <li v-if="entity.isLink"><a :href="entity.linkURL" target="_blank" title="External link">{{ entity.linkHostname }} 649</a> 650 </li> 651 <li v-if="entity.developmentStatus && entity.developmentStatus != 'open'" title="Development Status" class="idea-dev-status"><strong>{{ entity.developmentStatus | titleize }}</strong> 652 </li> 653 654 <li v-if="entity.commentsCount != 0" title="Comments"><i class="i-comment"></i>{{ entity.commentsCount }} 655 656 </li> 657 <li v-if="entity.likesCount != 0" title="Likes"><i class="i-heart"></i>{{ entity.likesCount }} 658 659 </li> 660 <li class="item-date"><a :href="entity.absoluteUrl" :title="entity.datePublished"><span>{{ entity.naturalDatePublished }}</span></a> 661 </li> 662 <li v-if="entity.isHot" title="Hot stuff!"><i class="i-fire"></i> 663 </li> 664 <li v-if="entity.votes" :title="entity.votes.positivePercentage + '% positive'"><i class="i-arrow-up"></i>{{ entity.votesPositiveCount }} 665<i class="i-arrow-down"></i>{{ entity.votesNegativeCount }} 666 667 </li> 668 <li v-if="entity.downloadsCount && entity.downloadsCount != 0" title="Downloads"><i class="i-download"></i>{{ entity.downloadsCount }} 669 670 </li> 671 <li v-if="entity.isPinnedByModerator" title="Pinned"><i title="Pinned" class="i-pin"></i> 672 </li> 673 </ul> 674 </div> 675 </div> 676 </li> 677</script>
vendor: 1 bytes, line 677
677
678<script type="application/javascript">Vue.component('entity-list-item', { 679 template: '#entity-list-item-template', 680 props: {entity: Object, activeEntity: Object }, 681 682 computed: { 683 isActive: function () { 684 if (!this.activeEntity) return false; 685 return this.entity.urlApiDetailView === this.activeEntity.urlApiDetailView; 686 } 687 }, 688 689 methods: { 690 toggleActive: function(e) { 691 e.preventDefault(); 692 this.$emit('set-active-entity', this.entity.urlApiDetailView); 693 694 let app = document.getElementById('app'); 695 app.classList.add('is-entity-active'); 696 } 697 } 698}) 699</script>
699 700 701
702<script type="text/x-template" id="entity-detail-template"> 703 <div id="entity-detail"> 704 <div id="modal-media" @click="dismissMedia" v-if="expandedMedia" class="modal-container"> 705 <div class="media"><img :src="expandedMedia.src" :alt="expandedMedia.alt"/> 706 </div> 707 <div class="modal-backdrop"></div> 708 </div> 709 <div :class="[ 710 entity.isLink ? 'is-link' : 'is-text', 711 entity.isHot ? 'is-hot' : '', 712 entity.linkIsOembed ? 'is-oembed' : '', 713 'is-type-' + entityType, 714 ]" class="entity-detail-item"> 715 <div class="entity-detail-item-header"> 716 <div class="entity-detail-item-meta"> 717 <ul v-if="entity.categories.length > 0" class="entity-detail-item-tags"> 718 <li v-for="(category, index) in entity.categories"><span v-if="index != 0">, </span><a :href="entity.absoluteUrl + '?category=' + category[1]" @click.prevent.stop="$emit('set-query-category', category[1])"><span :title="category[0]">{{ category[0] }}</span></a> 719 </li> 720 </ul> 721 <ul v-else="v-else" class="entity-detail-item-tags"> 722 <li>Categories</li> 723 </ul> 724 <ul> 725 <li v-if="entity.isEditable && !isReadOnly"><a :href="entity.editUrl" title="Edit Post">Edit</a> 726 </li> 727 <li v-if="entity.likesCount > 0"><a @click="toggleModalUserListLiked" href="#" title="Likes"><span><i :class="[entity.isHot ? 'i-fire' : 'i-heart']"></i>{{ entity.likesCount }} 728</span></a> 729 </li> 730 <li v-if="entity.status != 'published'"> 731 <div :class="'badge-' + entity.status" class="badge-pill">{{ entity.status }}</div> 732 </li> 733 <li><a v-if="!isReadOnly && entity.naturalDatePublished" :href="entity.absoluteUrl" :title="'Published on ' + entity.datePublished">Published {{ entity.naturalDatePublished }} ago</a> 734 </li> 735 <li v-if="entity.isEdited"><span :title="'Edited on ' + entity.dateUpdated">Edited {{ entity.naturalDateUpdated }} ago</span> 736 </li> 737 738 <li v-if="entity.isPinnedByModerator"><i title="Pinned Post" class="i-pin"></i> 739 </li> 740 </ul> 741 </div><a v-if="entity.title && entity.isLink" :href="entity.linkURL" rel="nofollow" target="_blank" class="entity-detail-item-title">{{ entity.title }}</a> 742 <div v-else-if="entity.title" class="entity-detail-item-title">{{ entity.title }} 743 </div> 744 <div v-else="v-else" class="entity-detail-item-title">Future Masterpiece 745 </div> 746 </div> 747 748 <div v-if="entity.downloadable" class="entity-summary-container downloadable"><a v-if="entity.downloadable.url" :href="entity.downloadable.url" class="entity-summary-icon"><i :class="['i-' + [entity.operatingSystem.slug]]"></i></a> 749 <div v-if="entity.downloadable.url" class="downloadable-meta-container"> 750 <div class="downloadable-meta"> 751 <h3>{{ entity.operatingSystem.name }} Build</h3> 752 <ul> 753 <li v-if="entity.downloadable.sizeHuman">{{ entity.downloadable.sizeHuman }}</li> 754 <li v-if="entity.downloadable.count && entity.downloadable.count != 0" :title="entity.downloadable.count + ' downloads'"><i class="i-download"></i>{{ entity.downloadable.countCompact }} 755 756 </li> 757 </ul> 758 </div> 759 <div class="btn-toolbar-row"><a :href="entity.downloadable.url" class="btn-theme-primary"><span><i class="i-download"></i><span>Download</span></span></a> 760 </div> 761 </div> 762 <div v-else="v-else" class="downloadable-meta-container error"> 763 <p>There is a problem with this build.</p> 764 <p>Please <a href="https://github.com/armadillica/dillo/issues">report the issue</a>. 765 </p> 766 </div> 767 </div> 768 769 <div v-if="entity.content && entity.isLink && entity.linkIsOembed" v-html="entity.content" class="entity-content"></div><a v-else-if="entity.content && entity.isLink" :href="entity.linkURL" :title="entity.linkURL" rel="nofollow" target="_blank" class="entity-link-content"> 770 <div class="entity-link-url"> 771 <div><img :src="entity.linkFavicon" alt="Link favicon"/> 772 </div> 773 <div>{{ entity.linkURL }}</div> 774 </div> 775 <button class="btn-theme"><span><i class="i-link"></i><span>Open Link</span></span> 776 </button></a>
777 <div v-else-if="entity.content" v-html="entity.content" class="entity-content"></div> 778 <div v-if="entity.votes" class="entity-summary-container idea"> 779 <div class="idea-percent-widget-container"> 780 <div :style="'background-size: 100% ' + [entity.votes.positivePercentage] + '%'" :title="[[entity.votes.positivePercentage] + '% Positive']" class="idea-percent-widget"><i class="i-idea"></i> 781 </div> 782 <div class="percentage">{{ entity.votes.positivePercentage }}%</div> 783 </div> 784 <div class="idea-action-buttons-container"> 785 <h3 v-if="entity.developmentStatus" class="status">Development Status: <span :class="['dev-status-' + [entity.developmentStatus]]">{{ entity.developmentStatus | titleize }} 786<i v-if="entity.developmentStatus == 'done'" class="i-check"></i></span> 787 </h3> 788 <div class="btn-toolbar-row"> 789 <vote v-if="!isReadOnly" v-on="$listeners" :current-user="currentUser" :direction="'up'" :entity="entity"></vote> 790 <vote v-if="!isReadOnly" v-on="$listeners" :current-user="currentUser" :direction="'down'" :entity="entity"></vote><a v-if="entity.developmentUrl" :href="entity.developmentUrl" target="_blank" title="Development URL" class="btn btn-theme"><span><i class="i-code"></i><span>{{ entity.developmentUrlLabel }}</span></span></a> 791 <div v-if="(entity.isOwn || currentUser.isModerator) && !isReadOnly && !entity.isDuplicateOf && entity.duplicates.length === 0" @click="toggleModalDuplicateOf" class="btn btn-secondary"><span><i class="i-copy"></i><span>Mark Duplicate</span></span> 792 </div> 793 </div> 794 <div v-if="entity.isDuplicateOf" class="idea-duplicates"> 795 <p>This idea has been marked as duplicate of <a :href="entity.isDuplicateOf.url">{{ entity.isDuplicateOf.title }}</a>. 796 </p> 797 <button v-if="(entity.isOwn || currentUser.isModerator) && !isReadOnly" @click="duplicateOfDelete" class="btn-secondary"><span><i class="i-cancel"></i><span>Unmark Duplicate</span></span> 798 </button> 799 </div> 800 <div v-if="entity.duplicates.length > 0" class="idea-duplicates"> 801 <p><i class="i-copy"></i> This idea is popular! It has {{ entity.duplicates.length }} {{ entity.duplicates.length | pluralize('duplicate') }}. 802 </p> 803 <ul> 804 <li v-for="duplicate in entity.duplicates"><span class="meta"><i title="Upvotes" class="i-arrow-up"></i>{{ duplicate.votes.positiveCount }} 805<i title="Downvotes" class="i-arrow-down"></i>{{ duplicate.votes.negativeCount }}</span><a :href="duplicate.url">{{ duplicate.title }}</a> 806 </li> 807 </ul> 808 </div> 809 </div> 810 </div> 811 <div class="entity-detail-info-container"> 812 <div class="entity-detail-user-info"> 813 <user-avatar :user="entity.user"></user-avatar> 814 <div class="user-info"><a :href="entity.user.url" :title="entity.user.username" class="username"><span v-if="entity.user.name">{{ entity.user.name }}</span><span v-else="v-else">{{ entity.user.username }}</span></a> 815 <div class="user-info-meta"> 816 <div v-if="entity.user.badges && entity.user.badges.length > 0" class="badges-row"> 817 <div v-for="badge in entity.user.badges" :title="badge.name" class="badge is-user"><img :src="badge.urlImage" :alt="badge.name"/> 818 </div> 819 </div> 820 <div v-if="entity.user.likes_count" title="Karma" class="user-rating"><i class="i-heart"></i>{{ entity.user.likes_count }} 821 </div> 822 </div> 823 </div> 824 </div> 825 826 <div class="entity-detail-reactions-container"> 827 <div v-if="!isReadOnly" class="btn-toolbar-row right"><a v-if="entity.isEditable" :href="entity.editUrl" title="Edit Post" class="btn-icon btn-secondary"><span><i class="i-edit"></i><span>Edit</span></span></a> 828 <reactions v-on="$listeners" :widget-type="'button'" :current-user="currentUser" :is-liked="entity.isLiked" :is-own="entity.isOwn" :like-toggle-url="entity.likeToggleUrl" :likes-count="entity.likesCount" :entity-url-api-detail-view="entity.urlApiDetailVie
828w" :url-user-list-liked="entity.urlUserListLiked"></reactions><a @click="toggleModalShare" title="Share Post" class="btn-icon btn-secondary"><span><i class="i-share"></i></span></a> 829 <div v-if="currentUser.isAuthenticated" class="dropdown"><a @click="toggleDropdown" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-secondary btn-icon"><span><i class="i-more-vertical"></i></span></a> 830 <div :class="{ show: isDropdownOpen }" class="dropdown-menu dropdown-menu-right"><a href="#" @click="toggleModalGenericForm(entity.urlReport)" title="Report Post" class="dropdown-item"><span><i class="i-flag"></i>Report Post...</span></a> 831 <div v-if="currentUser.isModerator" class="dropdown-divider"></div><a v-if="currentUser.isModerator" href="#" @click="toggleModalGenericForm(entity.user.urlRemoveSpam)" title="Remove Spam" class="dropdown-item danger"><span><i class="i-trash"></i>Remove Spam...</span></a> 832 <div v-if="currentUser.isStaff" class="dropdown-divider"></div><a v-if="currentUser.isStaff" :href="entity.urlEditAdmin" data-toggle="tooltip" title="Edit in Admin" class="dropdown-item is-admin"><span><i class="i-coffee"></i>Admin</span></a> 833 </div> 834 </div> 835 </div> 836 </div> 837 </div> 838 </div> 839 <div v-if="!isReadOnly" class="comments-list-container"> 840 <comment-form v-if="!isReadOnly" v-on="$listeners" :entity="entity" :current-user="currentUser"></comment-form> 841 <div v-if="comments && comments.length" class="comments-list-header">{{ entity.commentsCount }} Comment<span v-if="entity.commentsCount > 1">s</span> 842 </div> 843 <comment-list v-if="!isReadOnly" v-on="$listeners" :active-reply-form-comment-id="activeReplyFormCommentId" :entity="entity" :comments="comments" :url-api-comment-list-view-next-page="urlApiCommentListViewNextPage" :current-user="currentUser"></comment-list> 844 </div> 845 </div> 846</script>
vendor: 1 bytes, line 846
846
847<script type="application/javascript">Vue.component('entity-detail', { 848 mixins: [dropdownMixin], 849 template: '#entity-detail-template', 850 data: function () { 851 return { 852 expandedMedia: null, 853 } 854 }, 855 props: { 856 entity: Object, 857 entityType: { 858 type: String, 859 validator: function (value) { 860 // The value must match one of these strings 861 return ['post', 'idea', 'build'].indexOf(value) !== -1 862 } 863 }, 864 comments: Array, 865 urlApiCommentListViewNextPage: String, 866 currentUser: Object, 867 activeReplyFormCommentId: Number, 868 isReadOnly: false, // Set to true when using the component to preview a post in edit mode 869 }, 870 updated: function (val, oldVal) { 871 // Look for images with js-media-expand and allow them to expandMedia 872 let elements = document.getElementsByClassName('js-media-expand'); 873 for (var i = 0; i < elements.length; i++) { 874 elements[i].addEventListener('click', this.expandMedia, false); 875 } 876 }, 877 mounted() { 878 // Close modal when pressing Esc. 879 document.body.addEventListener('keyup', e => { 880 if (e.keyCode === 27) { this.dismissMedia();} 881 }) 882 }, 883 methods: { 884 expandMedia: function (event) { 885 document.body.classList.add('is-modal-open'); 886 this.expandedMedia = {src: event.target.src, alt: event.target.alt} 887 }, 888 dismissMedia: function () { 889 document.body.classList.remove('is-modal-open'); 890 this.expandedMedia = null; 891 }, 892 toggleModalShare: function () { 893 if (navigator.share) { 894 navigator.share({ 895 title: this.entity.title, 896 text: this.entity.content, 897 url: this.entity.absoluteUrl, 898 }) 899 .then(() => console.log('Success!')) 900 .catch((error) => console.log('Error', error)); 901 } else { 902 this.$emit('toggle-modal', { 903 type: 'share', 904 title: 'Share', 905 content: {'absoluteUrl': this.entity.absoluteUrl}} 906 ) 907 } 908 }, 909 toggleModalGenericForm: function (urlForm) { 910 this.$emit('toggle-modal', { 911 type: 'generic_form', 912 title: 'Report', 913 content: {'urlForm': urlForm}} 914 ) 915 }, 916 toggleModalUserListLiked: function () { 917 this.$emit('toggle-modal', { 918 type: 'userList', 919 title: 'Who liked this', 920 content: {'urlUserList': this.entity.urlUserListLiked}} 921 ) 922 }, 923 // RCS Idea Entity method 924 toggleModalDuplicateOf: function () { 925 this.$emit('toggle-modal', { 926 type: 'generic_form', 927 title: 'This is a duplicate of...', 928 content: {'urlForm': this.entity.urlDuplicateOf} 929 } 930 ) 931 }, 932 // RCS Idea Entity method 933 duplicateOfDelete: function () { 934 if(confirm('Unmark duplicate?')) 935 axios 936 .post(this.entity.urlDuplicateOfDelete) 937 .then(response => { 938 this.$emit('set-active-entity', this.entity.urlApiDetailView) 939 }) 940 .catch(error => { 941 console.log('Error! Could not reach the API. ' + error) 942 }) 943 }, 944 } 945}) 946</script>
946 947
948<script type="text/x-template" id="comment-form-template"> 949 <div class="comments-container"> 950 <div v-if="currentUser.isAuthenticated" class="comment-item comment-form-container"> 951 <div class="comment-avatar"> 952 <user-avatar :size="'small'" :user="currentUser"></user-avatar> 953 </div> 954 <div class="comment-body"> 955 <div class="comment-form"> 956 <div class="textarea-wrap js-textarea-wrap"><textarea @keydown.ctrl.enter="postComment" @keydown.meta.enter="postComment" @keydown.ctrl.b="setMarkdownStyle" @keydown.meta.b="setMarkdownStyle" @keydown.ctrl.i="setMarkdownStyle" @keydown.meta.i="setMarkdownStyle" v-model="message" onInput="this.parentNode.dataset.replicatedValue = this.value" placeholder="Write a comment..." rows="2" class="form-control"></textarea> 957 </div> 958 <div class="btn-toolbar-row"> 959 <button @click.stop="cancelComment" data-toggle="tooltip" title="Cancel" class="btn btn-comment-cancel"><span><i class="i-cancel"></i></span> 960 </button> 961 <button @click="postComment" :class="{'is-loading': isLoading}" title="Send Comment" class="btn-comment"><span>Comment</span> 962 </button> 963 </div> 964 <div v-html="messagePreview" :class="{'is-loading': isLoadingPreview}" class="comment-preview comment-content js-comment-preview"></div> 965 </div> 966 </div> 967 </div> 968 <div v-else="v-else" class="comment-form-login"><a href="/accounts/login" title="Login with Blender ID" class="btn-secondary btn-wide"><span><i class="i-log-in"></i><span>Login to Comment</span></span></a> 969 </div> 970 </div> 971</script>
vendor: 1 bytes, line 971
971
972<script type="application/javascript">Vue.component('comment-form', { 973 template: '#comment-form-template', 974 props: { 975 entity: Object, 976 parentCommentId: Number, 977 currentUser: Object 978 }, 979 data: function () { 980 return { 981 message: this.message, 982 messagePreview: null, 983 isLoading: false, 984 isLoadingPreview: false, 985 } 986 }, 987 watch: { 988 // whenever question changes, this function will run 989 message: function (newMessage, oldMessage) { 990 this.debouncedPreviewCommentContent() 991 } 992 }, 993 created: function () { 994 this.debouncedPreviewCommentContent = _.debounce(this.previewCommentContent, 500) 995 }, 996 methods: { 997 previewCommentContent: function () { 998 this.isLoadingPreview = true; 999 const params = new URLSearchParams({'content': this.message}); 1000 axios 1001 .post('/api/markdown-preview/', params) 1002 .then(response => { 1003 this.messagePreview = response.data.preview; 1004 this.isLoadingPreview = false; 1005 }) 1006 .catch(error => { 1007 this.messagePreview = 'Error! Could not reach the API. ' + error 1008 }) 1009 1010 }, 1011 cancelComment: function(){ 1012 if(confirm('Do you want to cancel the reply?')) 1013 this.$emit('set-active-reply-form-comment-id', null) 1014 }, 1015 postComment: function() { 1016 if (!this.message){ 1017 Snackbar.show({text: 'Try writing something.', icon: 'i-alert-triangle', showAction: false, customClass: 'error'}); 1018 return; 1019 } 1020 1021 this.isLoading = true; 1022 1023 //if (this.message.length < 5) return; 1024 const params = new URLSearchParams(); 1025 params.append('content', this.message); 1026 params.append('entity_object_id', this.entity.objectId); 1027 params.append('entity_content_type_id', this.entity.contentTypeId); 1028 if (this.parentCommentId) { 1029 params.append('parent_comment_id', this.parentCommentId); 1030 } 1031 const config = { 1032 headers: { 1033 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 1034 } 1035 } 1036 axios 1037 .post('/c/create/', params, config) 1038 .then(response => { 1039 this.message = ''; 1040 this.$emit('set-active-entity', this.entity.urlApiDetailView) 1041 this.$emit('set-active-reply-form-comment-id', null) 1042 1043 //- Clear spacer around textarea, and comment preview. 1044 let textareaWrap = document.querySelector('.js-textarea-wrap'); 1045 textareaWrap.dataset.replicatedValue = ''; 1046 let commentPreview = document.querySelector('.js-comment-preview'); 1047 commentPreview.replaceChildren(); 1048 1049 this.isLoading = false; 1050 }) 1051 .catch(error => { 1052 if (error.response) { 1053 if (error.response['data']) { 1054 Snackbar.show({text: error.response['data']['error'], icon: 'i-alert-triangle', showAction: false, customClass: 'error'}); 1055 } 1056 this.isLoading = false; 1057 } 1058 }); 1059 }, 1060 setMarkdownStyle(textArea) { 1061 let markdown = ''; 1062 switch(textArea.key) { 1063 case 'b': 1064 markdown = '**'; 1065 break; 1066 case 'i': 1067 markdown = '*'; 1068 break; 1069 } 1070 1071 let newMessage = this.message; 1072 let selectionStart = textArea.srcElement.selectionStart; 1073 let selectionEnd = textArea.srcElement.selectionEnd; 1074 let textToStyle = newMessage.substring(selectionStart, selectionEnd).trim(); 1075 1076 newMessage = newMessage.substring(0, selectionStart) + markdown + textToStyle + markdown + newMessage.substring(selectionEnd); 1077 this.message = newMessage; 1078 }, 1079 }, 1080}) 1081</script>
1081 1082
1083<script type="text/x-template" id="comment-list-item-template"> 1084 <div> 1085 <div :id="'comment-' + comment.id" :class="{ 1086 isAuthor: comment.isContentAuthor, 1087 isEditing: isEditing, 1088 isOwn: comment.isOwn, 1089 'has-replies': comment.replies && comment.replies.length != 0}" class="comment-item"> 1090 <div class="comment-avatar"> 1091 <user-avatar :size="'small'" :user="comment.user"></user-avatar> 1092 </div> 1093 <div class="comment-body"> 1094 <div class="comment-header"><a :href="comment.user.url" :title="comment.user.username" class="comment-author"><span :title="comment.user.name" class="comment-username">{{ comment.user.username }}</span><i v-if="comment.isContentAuthor" title="Post Author" class="i-mic"></i><span v-if="comment.isAuthorAdmin" title="Admin" class="is-admin"><i class="i-shield"></i></span><span v-if="comment.isAuthorModerator" title="Moderator" class="is-moderator"><i class="i-shield"></i></span></a> 1095 <div v-if="comment.user.badges && comment.user.badges.length > 0" class="comment-badges badges-row"> 1096 <div v-for="badge in comment.user.badges" :title="badge.name" class="badge is-user"><img :src="badge.urlImage" :alt="badge.name"/> 1097 </div> 1098 </div><a :href="entity.absoluteUrl + '#comment-' + comment.id" :title="comment.dateCreated" class="comment-date">{{ comment.naturalCreationTime }} 1099<i v-if="comment.isEdited === true" :title="'Edited on ' + comment.dateUpdated" class="i-edit"></i></a><span v-if="formErrorMessage">{{ formErrorMessage }}</span> 1100 </div> 1101 <div v-if="!isEditing" v-html="comment.content" class="comment-content"></div> 1102 <div v-else="v-else" class="comment-content"> 1103 <div class="comment-form"><textarea @keydown.ctrl.enter="editComment" @keydown.meta.enter="editComment" v-model="message" placeholder="Edit the comment..." rows="8" class="form-control"></textarea> 1104 <div class="btn-toolbar-row"> 1105 <button @click="toggleEditing" title="Cancel" type="button" class="btn btn-link"><span><i class="i-cancel"></i></span> 1106 </button> 1107 <button @click="editComment" title="Edit Comment" class="btn btn-comment"><span>Save Changes</span> 1108 </button> 1109 </div> 1110 </div> 1111 </div> 1112 <div class="comment-actions"> 1113 <div class="btn-toolbar-row"> 1114 <reactions v-if="!isEditing" v-on="$listeners" :comment-id="comment.id" :current-user="currentUser" :is-liked="comment.isLiked" :is-own="comment.isOwn" :likes-count="comment.likesCount" :like-toggle-url="comment.likeToggleUrl" :entity-url-api-detail-view="entity.urlApiDetailView" :url-user-list-liked="comment.urlUserListLiked"></reactions><a v-if="!isEditing" @click.stop="$emit('set-active-reply-form-comment-id', comment.id)" data-toggle="tooltip" href="#" title="Reply to this comment" class="btn-reply"><span><i class="i-reply"></i></span></a> 1115 <div v-if="!isEditing" class="dropdown"><a @click="toggleDropdown" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-link"><span><i class="i-more-vertical"></i></span></a> 1116 <div :class="{ show: isDropdownOpen }" class="dropdown-menu dropdown-menu-right"><a href="#" @click="toggleModalGenericForm(comment.urlReport, 'Report')" v-if="!comment.isOwn" title="Report Comment" class="dropdown-item"><span><i class="i-flag"></i>Report Comment...</span></a><a v-if="comment.isOwn || currentUser.isModerator" @click.stop="toggleEditing" href="#" class="dropdown-item"><span><i class="i-edit"></i>Edit</span></a> 1117 <div v-if="comment.isOwn || currentUser.isModerator" class="dropdown-divider"></div><a v-if="comment.isOwn || currentUser.isModerator" @click.stop="deleteComment" data-toggle="tooltip" href="#" title="Delete this comment" class="dropdown-item danger"><span><i class="i-trash"></i>Delete</span></a><a v-if="currentUser.isModerator" @click.stop="toggleModalGenericForm(comment.user.urlRemoveSpam, 'Remove Spam')" data-toggle="tooltip" href="#" title="Remove Spam" class="dropdown-item danger"><span><i class="i-trash"></i>
1117Remove Spam...</span></a> 1118 <div v-if="currentUser.isStaff" class="dropdown-divider"></div><a v-if="currentUser.isStaff" :href="comment.editAdminUrl" data-toggle="tooltip" title="Edit in Admin" class="dropdown-item is-admin"><span><i class="i-coffee"></i>Admin</span></a> 1119 </div> 1120 </div> 1121 </div> 1122 </div> 1123 <comment-form v-if="activeReplyFormCommentId === comment.id" v-on="$listeners" :current-user="currentUser" :parent-comment-id="comment.parentCommentId ? comment.parentCommentId : comment.id" :entity="entity"></comment-form> 1124 </div> 1125 </div> 1126 <comment-list v-on="$listeners" :active-reply-form-comment-id="activeReplyFormCommentId" :comments="comment.replies" :current-user="currentUser" :entity="entity" class="has-replies"></comment-list> 1127 </div> 1128</script>
vendor: 1 bytes, line 1128
1128
1129<script type="application/javascript">Vue.component('comment-list-item', { 1130 mixins: [dropdownMixin], 1131 template: '#comment-list-item-template', 1132 props: { 1133 entity: Object, 1134 comment: Object, 1135 currentUser: Object, 1136 parentCommentId: Boolean, 1137 activeReplyFormCommentId: Number, 1138 }, 1139 data: function () { 1140 return { 1141 isEditing: false, 1142 message: this.comment.content, 1143 // Contains error message when failing to submit update 1144 formErrorMessage: null, 1145 } 1146 }, 1147 methods: { 1148 deleteComment: function(data) { 1149 // Truncate comment content after 20 characters. 1150 commentContent = this.comment.contentRaw.slice(0, 20) + '...'; 1151 1152 // Prompt confirm dialog box. 1153 if(confirm('Delete comment "' + commentContent + '"?')) 1154 axios 1155 .post(this.comment.deleteUrl) 1156 .then(response => { 1157 // TODO: Reduce comments.length or reload the comments list. 1158 commentDiv = document.getElementById('comment-' + this.comment.id) 1159 commentDiv.parentElement.parentElement.remove(); 1160 1161 this.$emit('set-active-entity', this.entityUrlApiDetailView) 1162 }) 1163 }, 1164 toggleEditing: function () { 1165 this.formErrorMessage = null; 1166 if (this.isEditing) { 1167 this.isEditing = false; 1168 } else { 1169 this.message = this.comment.contentRaw; 1170 this.isEditing = true; 1171 } 1172 }, 1173 editComment: function () { 1174 if (!this.message){ 1175 Snackbar.show({text: 'Try writing something.', icon: 'i-alert-triangle', showAction: false, customClass: 'error'}); 1176 this.isEditing = true; 1177 return; 1178 } 1179 1180 const params = new URLSearchParams(); 1181 params.append('content', this.message); 1182 const config = { 1183 headers: { 1184 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 1185 } 1186 } 1187 axios 1188 .post(this.comment.editUrl, params, config) 1189 .then(response => { 1190 this.toggleEditing() 1191 this.$emit('set-active-entity', this.entityUrlApiDetailView) 1192 this.$emit('set-active-reply-form-comment-id', null) 1193 this.comment.content = response.data.content; 1194 this.comment.contentRaw = response.data.contentRaw; 1195 1196 Snackbar.show({text: 'Comment saved successfully.', icon: 'i-check', showAction: false, customClass: 'success'}); 1197 }) 1198 .catch(err => { 1199 if (err.response) { 1200 // client received an error response (5xx, 4xx) 1201 this.formErrorMessage = err.response.data.error 1202 1203 Snackbar.show({text: err.response.data.error, icon: 'i-alert-triangle', showAction: false, customClass: 'error'}); 1204 } 1205 }) 1206 }, 1207 toggleModalGenericForm: function (urlForm, title) { 1208 this.$emit('toggle-modal', { 1209 type: 'generic_form', 1210 title: title, 1211 content: {'urlForm': urlForm} 1212 } 1213 ) 1214 } 1215 } 1216}) 1217</script>
1217 1218
1219<script type="text/x-template" id="comment-list-template"> 1220 <ul v-if="comments && comments.length" class="comments-container"> 1221 <li v-for="(comment, index) in comments" class="comment-item-container"> 1222 <comment-list-item v-on="$listeners" :comment="comment" :entity="entity" :current-user="currentUser" :active-reply-form-comment-id="activeReplyFormCommentId"></comment-list-item> 1223 </li> 1224 <li v-if="urlApiCommentListViewNextPage"> 1225 <button @click="$emit('append-entity-comments-from-next-page')" class="btn-secondary comments-more"><span>Load More</span> 1226 </button> 1227 </li> 1228 </ul> 1229</script>
vendor: 1 bytes, line 1229
1229
1230<script type="application/javascript">Vue.component('comment-list', { 1231 template: '#comment-list-template', 1232 props: { 1233 urlApiCommentListViewNextPage: String, 1234 activeReplyFormCommentId: Number, 1235 entity: Object, 1236 comments: Array, 1237 currentUser: Object 1238 } 1239}) 1240</script>
1240 1241 1242
1243<script type="text/x-template" id="vote-template"> 1244 <div class="btn-group"> 1245 <div v-if="!entity.isOwn" @click="voteToggle" :class="{'is-voted': isVoted, 'is-loading': isLoading}" title="Vote" class="btn btn-theme btn-vote-arrow"><span><i :class="[direction === 'up' ? 'i-arrow-up' : 'i-arrow-down']"></i><span v-if="direction === 'up'">Upvote</span><span v-else="v-else">Downvote</span></span> 1246 </div> 1247 <div @click="toggleModalUserListVoted(direction)" title="Who voted for this?" class="btn btn-theme"><span><span v-if="direction === 'up'">{{ entity.votes.positiveCount }}</span><span v-else="v-else">{{ entity.votes.negativeCount }}</span><span v-if="entity.isOwn"><span v-if="direction === 'up'"> Upvotes</span><span v-else="v-else"> Downvotes</span></span></span> 1248 </div> 1249 </div> 1250</script>
vendor: 1 bytes, line 1250
1250
1251<script type="text/javascript">Vue.component('vote', { 1252 template: '#vote-template', 1253 props: { 1254 // Affect display of the component (either a simple icon, or a button) 1255 direction: { 1256 type: String, 1257 default: 'up', 1258 validator: function (value) { 1259 // The value must match one of these strings 1260 return ['up', 'down'].indexOf(value) !== -1 1261 } 1262 }, 1263 currentUser: { 1264 type: Object, 1265 required: true, 1266 validator: function (user) { 1267 const expectedProps = ['isAuthenticated', 'username', 'name', 'avatar', 'url']; 1268 return expectedProps.every(item => user.hasOwnProperty(item)); 1269 } 1270 }, 1271 entity: Object, 1272 }, 1273 data: function () { 1274 return { 1275 isLoading: false, 1276 } 1277 }, 1278 methods: { 1279 voteToggle: function() { 1280 this.isLoading = true; 1281 const params = new URLSearchParams(); 1282 params.set('direction', this.direction); 1283 const config = { 1284 headers: { 1285 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 1286 } 1287 } 1288 axios 1289 .post(this.entity.votes.voteToggleUrl, params, config) 1290 .then(response => { 1291 // TODO(fsiddi): Reduce the scope of this event. 1292 // We don't need to refresh the whole UI, rather only the likes counter 1293 this.$emit('set-active-entity', this.entity.urlApiDetailView) 1294 this.isLoading = false; 1295 }) 1296 }, 1297 // RCS Idea Entity method 1298 toggleModalUserListVoted: function (direction) { 1299 let title = direction === 'up' ? 'Who up voted this' : 'Who down voted this' 1300 let url = direction === 'up' ? this.entity.urlUserListVotedUp : this.entity.urlUserListVotedDown 1301 this.$emit('toggle-modal', { 1302 type: 'userList', 1303 title: title, 1304 content: {'urlUserList': url} 1305 } 1306 ) 1307 }, 1308 }, 1309 computed: { 1310 votesCount: function () { 1311 return this.direction === 'up' ? this.entity.votes.positiveCount : this.entity.votes.negativeCount 1312 }, 1313 isVoted: function () { 1314 return this.direction === 'up' ? this.entity.votes.isVotedPositive : this.entity.votes.isVotedNegative 1315 } 1316 }, 1317}) 1318</script>
1318 1319
1320<script type="text/x-template" id="filter-sort-toolbar-template"> 1321 <div class="app-feed-toolbar"> 1322 <div v-if="communityCategories.length" class="form-group form-group-h"> 1323 <div> 1324 <select @change="queryChangeCategory($event)" v-model="queryCategory" title="Category" class="form-control"> 1325 <option v-for="category in communityCategories" :title="category.name" :value="category.slug">{{ category.name }}</option> 1326 </select> 1327 </div> 1328 <div class="toolbar-right"> 1329 <select @change="queryChangeDevelopmentStatus($event)" v-model="queryDevelopmentStatus" title="Filter by" class="form-control form-select-filter-by"> 1330 <option v-for="status in developmentStatuses" :title="status.name" v-bind:value="status.slug">{{ status.name }}</option> 1331 </select> 1332 <select @change="queryChangeSorting($event)" v-model="querySorting" title="Sort by" class="form-control form-select-sort-by"> 1333 <option v-for="order in sortingOrders" :title="order.name" v-bind:value="order.slug">{{ order.name }}</option> 1334 </select> 1335 </div> 1336 </div> 1337 </div> 1338</script>
vendor: 1 bytes, line 1338
1338
1339<script type="application/javascript">Vue.component('filter-sort-toolbar', { 1340template: '#filter-sort-toolbar-template', 1341props: { 1342 communityCategories: Array, 1343 developmentStatuses: Array, 1344 sortingOrders: Array, 1345 query: Object, 1346 queryExtension: Object 1347}, 1348data: function () { 1349 return { 1350 queryDevelopmentStatus: "", 1351 queryCategory: "", 1352 querySorting: null, 1353 } 1354}, 1355watch: { 1356 query: { 1357 deep: true, 1358 handler: function (val, oldVal) { 1359 this.queryCategory = val.category 1360 this.querySorting = val.sorting 1361 } 1362 }, 1363 queryExtension: { 1364 deep: true, 1365 handler: function (val, oldVal) { 1366 this.queryDevelopmentStatus = val.developmentStatus 1367 } 1368 } 1369}, 1370methods: { 1371 queryChangeCategory: function(event) { 1372 this.$emit('set-query-category', event.target.value) 1373 }, 1374 queryChangeSorting: function (event) { 1375 this.$emit('set-query-sorting', event.target.value) 1376 }, 1377 queryChangeDevelopmentStatus: function (event) { 1378 this.$emit('set-query-development-status', event.target.value) 1379 } 1380} 1381}) 1382</script>
1382 1383 1384
1385<script type="text/javascript" src="https://blender.community/static/rcs/js/idea-list.b56b77a74ae6.js" charset="utf-8"></script>
1385 1386 1387
1388<script type="text/javascript" src="https://blender.community/static/js/tutti.f09baef4086e.js" charset="utf-8"></script>
1388 1389 1390 1391
1391<script>tippy('[data-tooltip=true]'); 1392 1393 </script>
vendor: 72 bytes, lines 1393-1395
1393 1394 1395 <script async="" src="https://www.googletagmanager.com/gtag/js?id=
1395UA-37082864-4
vendor: 16 bytes, lines 1395-1396
1395"></script> 1396
1396<script>
vendor: 132 bytes, lines 1396-1401
1396window.dataLayer = window.dataLayer || []; 1397function gtag() { 1398 dataLayer.push(arguments); 1399} 1400gtag('js', new Date()); 1401gtag('config', '
1401UA-37082864-4
vendor: 8 bytes, lines 1401-1402
1401'); 1402
1402</script>
1402 1403 1404 </body> 1405</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.