TagPro GroPro

Enhance your group experience!

当前为 2018-02-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name TagPro GroPro
  3. // @version 1.1
  4. // @description Enhance your group experience!
  5. // @author Ko
  6. // @supportURL https://www.reddit.com/message/compose/?to=Wilcooo
  7. // @website https://redd.it/no-post-yet
  8. // @icon https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/G(roPro).png
  9. // @download https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/tpgp.user.js
  10. // @match http://*.koalabeast.com:*/*
  11. // @grant GM_notification
  12. // @license MIT
  13. // @namespace https://greasyfork.org/users/152992
  14. // ==/UserScript==
  15.  
  16.  
  17.  
  18.  
  19. ////////////////////////////////////////////////////////////////////////////////////////////
  20. // ### --- OPTIONS --- ### //
  21. //////////////////////////////////////////////////////////////////////////////////////// //
  22. // //
  23. // Check this for a guide on how to change these options: // //
  24. // https://www.reddit.com/r/TagPro/wiki/modding#wiki_how_to_modify_a_userscript // //
  25. // //
  26. // Show a notification when you are on another right tab (f.e. browsing /r/TagPro) // //
  27. const show_notifications = true; // //
  28. // //
  29. // Play a 'bwep' sound whenever someone sends a message // //
  30. const sound_on_chat = true; // //
  31. // //
  32. // Play a 'dink' sound whenever someone joines // //
  33. const sound_on_join = true; // //
  34. // //
  35. // Play a 'donk' sound whenever someone leaves // //
  36. const sound_on_left = true; // //
  37. // //
  38. // You can change those 3 sounds in the box below the options // //
  39. // //
  40. // Color names in chat, according to the team of that player // //
  41. // (Red&Blue teams, Green playing pubs, White spectating, Gray waiting) // //
  42. const color_names = true; // //
  43. // //
  44. // Show a timestamp next to every chat message. // //
  45. const show_timestamps = true; // //
  46. // //
  47. // This requires the `show_timestamps` to be true. // //
  48. // It will add seconds to the timestamp as well. // //
  49. const show_seconds = false; // //
  50. // //
  51. // This requires the `show_timestamps` to be true. // //
  52. // It will fade the timestamp when you have read the message // //
  53. const fade_read_chats = true; // //
  54. // //
  55. // Use the arrow up/down keys to scroll to earlier sent messages (like in a console) // //
  56. const chat_history = true; // //
  57. // //
  58. // Don't scroll down for new messages when you are scrolling through old ones. // //
  59. // Instead it will show an arrow, indicating that new messages are available. // //
  60. const prevent_scroll = true; // //
  61. // //
  62. // Shows available groups on the homepage, and lets you create a new group with // //
  63. // a single click from there too. When already in a group, it shows only that one. // //
  64. const groups_on_home = true; // //
  65. // //
  66. // Position on homepage ( can be 'top', 'home', or 'bottom' ) // //
  67. // 'home' means beneath the video on the homepage // //
  68. // TODO: link to a picture that explains these positions // //
  69. const position = 'top'; // //
  70. // //
  71. // Shows the group description as set by the leader/admins // //
  72. // If there is no description, and when you don't have the rights to edit, // //
  73. // it is still hidden. Not recommended to turn off, because you could miss important // //
  74. // information. Balls without this script *do* still see the description. // //
  75. const show_description = true; // //
  76. // //
  77. // Show 'Ready!' beneath everyone who has checked the 'ready' button. // //
  78. const show_ready_states = true; // //
  79. // //
  80. // Show the 'ready' button, to tell everyone that you are ready. // //
  81. // Not recommended to turn of, as others might unnecessarily wait on you when you // //
  82. // don't click the ready button! // //
  83. const show_ready_btn = true; // //
  84. // //
  85. //////////////////////////////////////////////////////////////////////////////////////// //
  86. // ### --- END OF OPTIONS --- ### //
  87. ////////////////////////////////////////////////////////////////////////////////////////////
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  95. // ### --- SOUNDS --- ### //
  96. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //
  97. // //
  98. var chat_sound = new Audio('https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/audio/chat.wav'); // //
  99. var left_sound = new Audio('https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/audio/left.mp3'); // //
  100. var join_sound = new Audio('https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/audio/joined.mp3'); // //
  101. // //
  102. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //
  103. // //
  104. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111. //////////////////////////////////////
  112. // SCROLL FURTHER AT YOUR OWN RISK! //
  113. //////////////////////////////////////
  114.  
  115.  
  116.  
  117.  
  118. var short_name = 'gropro'; // An alphabetic (no spaces/numbers, preferably lowercase) distinctive name for the script.
  119. var version = GM_info.script.version; // The version number is automatically fetched from the metadata.
  120. tagpro.ready(function(){ tagpro.scripts = Object.assign( tagpro.scripts || {}, {short_name:{version:version}} ); });
  121. console.log('START: ' + GM_info.script.name + ' (v' + version + ' by ' + GM_info.script.author + ')');
  122.  
  123.  
  124.  
  125.  
  126.  
  127. // Userscripts load in the order that they appear in Tamermonkey.
  128. // Set this option to true if you want this script to be inserted to
  129. // the page above than previously load scripts, instead of below.
  130. const insertBefore = false;
  131.  
  132.  
  133.  
  134. if (window.location.pathname === '/groups') { // If we are on the groups selection page
  135. }
  136.  
  137. else if (window.location.pathname.match(/^\/groups\/[a-z]{8}$/)) { // If we are in a group
  138.  
  139. tagpro.ready( function(){
  140.  
  141.  
  142.  
  143.  
  144. // Keep track of all interesting variables.
  145. // TagPro does this too, but it's hidden :(
  146. // Thats why we do this ourselfs too :)
  147.  
  148. var group = tagpro.group = Object.assign(tagpro.group, {
  149. self: null,
  150. players: {},
  151. privateGame: $(".group.container").hasClass("js-private-game"),
  152. privateGroup: false,
  153. currentGamePort: null,
  154. chat: [],
  155. selfAssignment: false,
  156. settings: {},
  157. maxPlayers: 0,
  158. maxSpectators: 0,
  159. });
  160.  
  161. var socket = group.socket;
  162.  
  163. socket.on('chat', function(chat) {
  164. chat.time = Date.now(); // This is not in the original TagPro code, but it's handy
  165. group.chat.push(chat);
  166. });
  167.  
  168. socket.on('port', function(port) {
  169. group.currentGamePort = port;
  170. });
  171.  
  172. socket.on('member', function(member) {
  173. if (!group.players[member.id]) send_description(); // Not original TP code either
  174.  
  175. // This is slightly altered to allow a 'ready' variable to persist
  176. group.players[member.id] = Object.assign(group.players[member.id] || {}, member);
  177.  
  178. if (group.self) group.self = group.players[group.self.id];
  179. });
  180.  
  181. socket.on('removed', function(removed) {
  182. delete group.players[removed.id];
  183. });
  184.  
  185. socket.on('full', function() {
  186. alert('GroPro: This group is full :(');
  187. });
  188.  
  189. socket.on('banned', function() {
  190. alert('GroPro: You got banned :(');
  191. });
  192.  
  193. socket.on('you', function(you) {
  194. group.self = group.players[you];
  195. });
  196.  
  197. socket.on('private', function(private){
  198. group.privateGame = private.isPrivate;
  199. group.maxSpectators = private.maxSpectators;
  200. group.maxPlayers = private.maxPlayers;
  201. group.selfAssignment = private.selfAssignment;
  202. group.noScript = private.noScript;
  203. group.respawnWarnings = private.respawnWarnings;
  204. });
  205.  
  206. socket.on('setting', function(setting) {
  207. group.settings[setting.name] = setting.value;
  208. });
  209.  
  210. socket.on('publicGroup', function(publicGroup) {
  211. group.public = publicGroup;
  212. });
  213.  
  214.  
  215.  
  216. var chat_log = document.getElementsByClassName('js-chat-log')[0];
  217.  
  218.  
  219.  
  220.  
  221.  
  222. // Show notifications on receiving chats // Play sound
  223. function notify(chat){
  224. if (show_notifications && !document.hasFocus()) {
  225. // GM_notification( text, title, icon (defaults to script icon), onclick)
  226. GM_notification( chat.message, chat.from || group.settings.groupName, null, window.focus );
  227. }
  228.  
  229. // Play a sound
  230. if (chat.from && sound_on_chat) chat_sound.play();
  231. else if (chat.message.endsWith(' has left the group.') && sound_on_left)
  232. left_sound.play();
  233. else if (chat.message.endsWith(' has joined the group.') && sound_on_join)
  234. join_sound.play();
  235. else if (sound_on_chat) chat_sound.play();
  236. }
  237.  
  238.  
  239.  
  240.  
  241.  
  242. // This function will fade the timestamp when you've seen the message
  243. function fadeTimestamp(t) {
  244.  
  245. // If the window isn't focussed
  246. if(!document.hasFocus()) {
  247. return window.addEventListener("focus", function() {
  248. fadeTimestamp(t);
  249. }, {once:true});
  250. }
  251.  
  252. // If the chat row is not in view (due to scrolling)
  253. if ( t[0].offsetTop < chat_log.scrollTop || t[0].offsetTop > chat_log.scrollTop + chat_log.clientHeight ) {
  254. return chat_log.addEventListener("scroll", function(){
  255. fadeTimestamp(t);
  256. }, {once:true});
  257. }
  258.  
  259. // Wait 3 secs, and fade if the document is still focussed
  260. else {
  261. setTimeout( function(){
  262. if (document.hasFocus())
  263. t.fadeTo("slow",0.4);
  264. else fadeTimestamp(t);
  265. }, 3000);
  266. }
  267. }
  268.  
  269.  
  270.  
  271.  
  272.  
  273. // Don't scroll down when reading old messages
  274.  
  275. var scrolled = true;
  276.  
  277. chat_log.addEventListener("scroll", function(){
  278. scrolled = chat_log.scrollTop >= chat_log.scrollHeight - chat_log.clientHeight;
  279. if (scrolled) arrow.style.display = 'none';
  280. });
  281.  
  282. function scrollChat() {
  283. if (!prevent_scroll || scrolled)
  284. chat_log.scrollTop = chat_log.scrollHeight;
  285.  
  286. else {arrow.style.display = '';}
  287. }
  288.  
  289. var last_chat = '';
  290.  
  291. $('<img id="chat-log-arrow" style="position:absolute;right:30px;top:150px;display:none" src="https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/arrow.png">').appendTo(chat_log);
  292. var arrow = document.getElementById('chat-log-arrow');
  293. arrow.style.cursor = 'pointer';
  294. arrow.title = 'New messages!';
  295. arrow.onclick = function(){
  296. chat_log.scrollTop = chat_log.scrollHeight;
  297. arrow.style.display = 'none';
  298. };
  299.  
  300.  
  301.  
  302.  
  303.  
  304. // This function receives all chat messages
  305.  
  306. function handleChat(chat) {
  307.  
  308. var player = group.players[Object.keys(group.players).filter( id => group.players[id].name == chat.from )[0]];
  309. var match;
  310.  
  311. // Append messages starting with ⋯
  312. var last = group.chat[group.chat.length-1] || {from:null};
  313.  
  314. if ( chat.message.startsWith('⋯') && last.from == chat.from && last.time > Date.now()-5) {
  315. last_chat = last_chat + chat.message.slice(1);
  316. $(".js-chat-log .chat-message").last().text( last_chat );
  317.  
  318. scrollChat();
  319.  
  320. return;
  321. }
  322.  
  323. // Handle commands
  324. if ( ( match = chat.message.match(/^\[GroPro:(\w{1,11})\](.{0,100})$/) ) ) { // If the message is of the form [GroPro:xxx]yyy
  325. var command = match[1], // the xxx part
  326. value = match[2]; // the yyy part
  327.  
  328. if (command=='description' && player.leader) {
  329. group.description = value;
  330.  
  331. if (show_description) update_gd();
  332.  
  333. return;
  334. }
  335.  
  336. if (command=='ready') {
  337. player.ready = true;
  338. updateReadyStates();
  339. if (!value) return;
  340. }
  341.  
  342. if (command=='notready') {
  343. player.ready = false;
  344. updateReadyStates();
  345. if (!value) return;
  346. }
  347.  
  348. var warning = tagpro.helpers.displayError('Someone sent an unrecognizable command (as you can see in chat). The sender probably doesn\'t know what GroPro is, or you don\'t have the latest version installed.');
  349. warning[0].onclick = ()=>warning.fadeOut(); // Click to hide
  350. warning[0].style.cursor = 'pointer';
  351. warning[0].title = 'Click to hide';
  352. }
  353.  
  354. // Handle an actual message
  355. last_chat = chat.message;
  356.  
  357.  
  358. var timestamp;
  359.  
  360. if (show_timestamps) {
  361. var time = new Date().toTimeString().substr(0, show_seconds ? 8 : 5 );
  362. timestamp = $("<span></span>").addClass("timestamp").text( time );
  363. if (fade_read_chats) fadeTimestamp(timestamp);
  364. }
  365.  
  366. var player_name = null;
  367. if (chat.from) {
  368. var team = player && player.team+1 ? " team-"+player.team : "";
  369.  
  370. player_name = $("<span></span>").addClass("player-name" + team).text(chat.from + ": ");
  371. }
  372.  
  373. var chat_message = $("<span></span>").text(chat.message).addClass("chat-message");
  374. $("<div></div>").addClass("chat-line").append(timestamp).append(player_name).append(chat_message).appendTo(chat_log);
  375.  
  376. scrollChat();
  377.  
  378. notify(chat);
  379. }
  380.  
  381. // Replace TagPro's function that puts chats in the chat-log
  382. socket.listeners('chat')[0] = handleChat;
  383.  
  384. // Find the correct styleSheet
  385. for (var styleSheet of document.styleSheets) if (styleSheet.href.includes('/style.css')) break;
  386.  
  387. // Add a rule to the sheet for the timestamp and player names
  388. styleSheet.insertRule(".group .chat-log .timestamp { margin-right: 5px; color: Yellow; }");
  389.  
  390. if (color_names) {
  391. styleSheet.insertRule(".group .chat-log .player-name { color: #4c4c4c }"); // Gray
  392. styleSheet.insertRule(".group .chat-log .player-name.team-0 { color: #8BC34A; }"); // Green
  393. styleSheet.insertRule(".group .chat-log .player-name.team-1 { color: #D32F2F; }"); // Red
  394. styleSheet.insertRule(".group .chat-log .player-name.team-2 { color: #1976D2; }"); // Blue
  395. styleSheet.insertRule(".group .chat-log .player-name.team-3 { color: #e0e0e0; }"); // White
  396. }
  397.  
  398.  
  399.  
  400.  
  401.  
  402. // Split long messages, so that you can send those too
  403. // Also save all sent messages
  404. // for the history option
  405.  
  406. var sent = [], hist = -1, curr = "";
  407.  
  408. $('.js-chat-input').off('keydown'); // Remove old handler
  409.  
  410. document.getElementsByClassName('js-chat-input')[0].onkeydown = function(key){
  411. if (key.which == 13) { // ENTER
  412. sent.unshift(this.value);
  413. hist = -1;
  414.  
  415. if (this.value.length <= 120) socket.emit("chat", this.value);
  416. else {
  417. var cut, chats = [ this.value.slice( 0, 120 ) ];
  418. while ((cut = this.value.slice( chats.length*119+1 ))) {
  419. chats.push( '⋯' + cut.slice(0,119) );
  420. }
  421.  
  422. for (var c of chats) socket.emit("chat", c);
  423. }
  424. this.value = "";
  425.  
  426. //chat_log.scrollTop = chat_log.scrollHeight;
  427. }
  428.  
  429. if (chat_history && key.which == 38) { // ARROW-UP
  430. if (hist == -1) curr = this.value;
  431. if (hist < sent.length-1) {
  432. this.value = sent[ ++hist ];
  433. key.preventDefault(); // Prevent the caret/cursor to jump to the start
  434. }
  435. }
  436. if (chat_history && key.which == 40) { // ARROW-DOWN
  437. if (hist > -1) {
  438. this.value = sent[ --hist ] || curr;
  439. key.preventDefault(); // Prevent the caret to jump to the end
  440. }
  441. }
  442. };
  443.  
  444.  
  445.  
  446.  
  447.  
  448. // Group description
  449.  
  450. document.getElementsByClassName('js-chat-input')[0].placeholder = 'Send a message';
  451.  
  452. if (show_description) {
  453.  
  454. $(`
  455. <div id="gd-container" class="col-md-12" style="display:none"><hr>
  456. <h3 style="float:left;font-size:16px">Group Description</h3>
  457. <div id="gd-btns" style="display:none;float:right;margin-bottom:14px">
  458. <a id="gd-save" class="btn btn-default">Save</a>
  459. <a id="gd-cancel" class="btn btn-default">Cancel</a>
  460. </div>
  461. <textarea readonly id="gd-text" maxlength=100 placeholder="Group description (this is also sent to those without the script)" type="text" style="background:#212121;width:100%;padding:5px 10px;resize:vertical" class="chat"></textarea>
  462. <hr></div>`).insertAfter(document.getElementById('group-chat').parentNode);
  463.  
  464. var gd_container = document.getElementById('gd-container');
  465. var gd_text = document.getElementById('gd-text');
  466. var gd_save = document.getElementById('gd-save');
  467. var gd_cancel = document.getElementById('gd-cancel');
  468. var gd_btns = document.getElementById('gd-btns');
  469.  
  470. gd_save.onclick = function(){
  471. if (gd_text.value != group.description)
  472. socket.emit('chat', "[GroPro:description]"+gd_text.value);
  473.  
  474. gd_btns.style.display = 'none';
  475. };
  476.  
  477. gd_cancel.onclick = function(){
  478. update_gd();
  479.  
  480. gd_btns.style.display = 'none';
  481. };
  482.  
  483. socket.once('you', function(you){
  484.  
  485. update_gd();
  486. socket.on('member', function(member){
  487. if (member.id == group.self.id)
  488. update_gd(false);
  489. });
  490. });
  491. }
  492.  
  493. group.description = "";
  494.  
  495. function editDescription(){
  496. // Show the save/cancel buttons
  497. gd_btns.style.display = '';
  498. }
  499.  
  500. function onLeader(){
  501. gd_text.readOnly = false;
  502. gd_text.onfocus = editDescription;
  503. }
  504.  
  505. function onNonLeader(){
  506. gd_text.readOnly = true;
  507. gd_text.onfocus = null;
  508. gd_text.value = group.description;
  509.  
  510. gd_btns.style.display = 'none';
  511. }
  512.  
  513. function update_gd(text=true) {
  514.  
  515. if (!show_description) return;
  516.  
  517. if (group.description || group.self.leader)
  518. gd_container.style.display = ''; //show
  519. else
  520. gd_container.style.display = 'none'; //hide
  521.  
  522. if (group.self.leader) {
  523. gd_text.readOnly = false;
  524. gd_text.onfocus = editDescription;
  525. } else {
  526. gd_text.readOnly = true;
  527. gd_text.onfocus = null;
  528.  
  529. gd_btns.style.display = 'none';
  530. }
  531.  
  532. if (text) gd_text.value = group.description;
  533. }
  534.  
  535. function send_description() {
  536.  
  537. if (group.self && group.self.leader && group.description ) {
  538. socket.emit('chat', "[GroPro:description]"+gd_text.value);
  539. }
  540. }
  541.  
  542.  
  543.  
  544.  
  545.  
  546. // Ready button
  547.  
  548. // First, add the button
  549.  
  550. if (show_ready_btn) {
  551.  
  552. var ready_public_btn = document.createElement('label');
  553. ready_public_btn.className = 'btn btn-default group-setting';
  554. ready_public_btn.style.marginRight = '14px';
  555. ready_public_btn.innerHTML = '<input type="checkbox" style="margin:0;vertical-align:middle"> I\'m Ready!';
  556. var ready_private_btn = ready_public_btn.cloneNode(true);
  557.  
  558. var launch_public_btn = document.getElementById('launch-public-btn');
  559. var launch_private_btn = document.getElementById('launch-private-btn');
  560.  
  561. launch_public_btn.parentElement.insertBefore( ready_public_btn, launch_public_btn);
  562. launch_private_btn.parentElement.insertBefore( ready_private_btn, launch_private_btn);
  563.  
  564. var ready_public_box = ready_public_btn.getElementsByTagName('input')[0];
  565. var ready_private_box = ready_private_btn.getElementsByTagName('input')[0];
  566.  
  567. var ready_state = false;
  568. var last_clicked;
  569.  
  570. ready_public_btn.onchange = ready_private_btn.onchange = function(change){
  571.  
  572. // Find out what your new readystate is
  573. ready_state = change.target.checked;
  574.  
  575. // Update both buttons accordingly (one of them obviously is already right)
  576. ready_public_btn.getElementsByTagName('input')[0].checked = ready_state;
  577. ready_private_btn.getElementsByTagName('input')[0].checked = ready_state;
  578.  
  579. // Disable the buttons for a few seconds to prevent spamming
  580. console.log(ready_public_btn.style.cursor);
  581. ready_public_btn.style.cursor = 'wait';
  582. ready_private_btn.style.cursor = 'wait';
  583.  
  584. ready_public_box.style.cursor = 'wait';
  585. ready_private_box.style.cursor = 'wait';
  586.  
  587. ready_public_box.disabled = true;
  588. ready_private_box.disabled = true;
  589.  
  590. setTimeout(function(){
  591. ready_public_btn.style.cursor = '';
  592. ready_private_btn.style.cursor = '';
  593.  
  594. ready_public_box.style.cursor = '';
  595. ready_private_box.style.cursor = '';
  596.  
  597. ready_public_box.disabled = false;
  598. ready_private_box.disabled = false;
  599. },3000);
  600.  
  601. // Warn if the state is changed right after the delay runs out
  602. if (last_clicked > Date.now() - 6000) {
  603. let warning = tagpro.helpers.displayError('Please don\'t change your ready-state more often than needed. Players without the script receive a chat message every time you change it. Thank you :)');
  604. warning[0].onclick = ()=>warning.fadeOut(); // Click to hide
  605. warning[0].style.cursor = 'pointer';
  606. warning[0].title = 'Click to hide';
  607. }
  608. last_clicked = Date.now();
  609.  
  610. // Tell it to the world
  611. socket.emit('chat', ready_state ? "[GroPro:ready]" : "[GroPro:notready]");
  612.  
  613. };
  614. }
  615.  
  616. for (var event of ['port','member','removed','you','private'])
  617. socket.on(event, updateReadyStates);
  618.  
  619. // This function updates the ready tag beneath each player
  620.  
  621. function updateReadyStates(){
  622.  
  623. if (!show_ready_states) return;
  624.  
  625. for (var player_item of document.getElementsByClassName('player-item')) {
  626. var player = group.players[ $(player_item).data('model') && $(player_item).data('model').id ];
  627.  
  628. var location = player_item.getElementsByClassName('player-location')[0];
  629.  
  630. if (player.ready && player.location == "page") {
  631. location.innerText = 'Ready!';
  632. location.style.color = '#8BC34A';
  633. }
  634.  
  635. else {
  636. switch (player.location) {
  637. case "page":
  638. location.innerText = "In Here";
  639. break;
  640. case "joining":
  641. location.innerText = "Joining a Game";
  642. break;
  643. case "game":
  644. location.innerText = "In a Game";
  645. }
  646. location.style.color = '';
  647. }
  648. }
  649. }
  650.  
  651. });
  652.  
  653. }
  654.  
  655. else if (window.location.pathname === '/games/find') { // In the process of joining a game
  656. }
  657.  
  658. else if (window.location.port.match(/^8[0-9]{3}$/)) { // If we are in a game
  659. }
  660.  
  661. else if (window.location.pathname === '/') { // If we are on the homepage
  662.  
  663. if (groups_on_home) {
  664.  
  665. // Create a container for the groups
  666. var container = document.createElement('div');
  667. container.id = 'GroPro-groups';
  668. container.className = 'container';
  669.  
  670. // Add the container to the userscript-div and make unhide that
  671. var pos = document.getElementById('userscript-'+position);
  672. if (insertBefore) pos.insertBefore(container, pos.firstChild);
  673. else pos.append(container);
  674. pos.classList.remove('hidden');
  675.  
  676. // Load the groups from the /groups page
  677. // and put them inside the container.
  678. $(container).load('/groups #groups-list', function(result){
  679.  
  680. if ( $(result).find('#groups-list').length > 0 ) { // true if you haven't yet joined a group
  681.  
  682. groups_list = document.getElementById('groups-list');
  683. for (let group of groups_list.children) {
  684.  
  685. // Change the width of the group boxes (responsive)
  686. group.className = 'col-sm-6 col-md-4';
  687.  
  688. // Replace the 'no groups available' message
  689. if (group.innerText == "No public groups available. Create one!")
  690. group.outerHTML = `
  691. <div class="col-sm-6 col-md-4">
  692. <div class="group-item">
  693. <div class="row">
  694. <div class="col-md-12">
  695. <div class="group-name">No public groups available. Create one!</div>
  696. </div>
  697. </div>
  698. </div>
  699. </div>`;
  700.  
  701. }
  702.  
  703. // Add a 'create group' widget
  704. groups_list.innerHTML += `
  705. <div class="col-sm-6 col-md-4">
  706. <div class="group-item">
  707. <div class="row">
  708. <form method="post" action="/groups/create">
  709. <div class="col-md-12">
  710. <input name="name" class="group-name" style="background:0;border:0;width:100%" value="Your group">
  711. </div>
  712. <div class="col-md-12 pull-right">
  713. <label tabindex="0" onkeydown="labelKeyDown(event)" class="btn btn-default" style="margin:6px">
  714. <input tabindex="-1" type="checkbox" name="public">
  715. Public Group
  716. </label>
  717. <button class="btn btn-primary" style="margin:6px">Create Group</button>
  718. </div>
  719. </form>
  720. </div>
  721. </div>
  722. </div>`;
  723.  
  724. labelKeyDown = function(event) {
  725. if (event.code == 'Space') {
  726. event.preventDefault();
  727. event.target.firstElementChild.checked ^= true;
  728. }
  729. if (event.code == 'Enter') {
  730. event.preventDefault();
  731. event.target.closest('form').submit();
  732. }
  733. };
  734. }
  735. });
  736. }
  737. }
  738.  
  739.  
  740. else { // If we are on any other page of the server
  741. }