TagPro GroPro

Enhance your group experience!

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

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