<commit_before>﻿// ==UserScript==
// @name           Trellonest
// @description    This extension enables nested boards in Trello
// @include        https://trello.com/*
// @version        1.0
// ==/UserScript==

(function ($) {
  var trelloAPI = "https://trello.com/1/";

  function getLoginToken() {
    var token = document.cookie.match(/token=(.*?)(;|$)/);
    return (token && unescape(token[1])) || null;
  }

  var boardUrlRegex = /((^(https?:\/\/)?trello.com)?\/?b\/)?([^\/]+)\/?/;
  function parseBoardUrl(url) {
    var match = boardUrlRegex.exec(url);
    return match[4] || null;
  }

  function getBoard(cb) {
    var boardMatch = window.location.pathname.match(/^\/b\/(.*?)(\/|$)/);
    if (boardMatch) {
      var board = boardMatch && boardMatch[1];
      if (board) {
        $.get(trelloAPI + "boards/" + board + "?fields=id")
          .done(function (resp) {
            boardCache = resp.id;
            cb(null, resp.id);
          })
          .fail(function (xhr, status, err) {
            cb(err);
          });
      } else {
        cb(null, null);
      }
    } else {
      var cardMatch = window.location.pathname.match(/^\/c\/(.*?)(\/|$)/);
      if (cardMatch) {
        var card = cardMatch && cardMatch[1];

        $.get(trelloAPI + "cards/" + card + "?fields=idBoard")
          .done(function (resp) {
            boardCache = resp.idBoard;
            cb(null, resp.idBoard);
          })
          .fail(function (xhr, status, err) {
            cb(err);
          });
      } else {
        cb(new Error("Unsupported trello page"));
      }
    }
  }

  function getActiveCard() {
    var cardMatch = window.location.pathname.match(/^\/c\/(.*?)(\/|$)/);
    if (!cardMatch) return null;
    var card = cardMatch && cardMatch[1];
    return card || null;
  }

  var establishingSocket = false;
  var currentSocket;
  function establishSocket() {
    if (establishingSocket) return;
    establishingSocket = true;

    var token = getLoginToken();

    getBoard(function (err, boardId) {
      if (err) {
        establishingSocket = false;
        return;
      }

      if (currentSocket) {
        establishingSocket = false;

        currentSocket.close();
        currentSocket = null;
        return; // reestablishing called by close
      }

      var reqId = 0;
      var socket = new WebSocket("wss://trello.com/1/Session/socket?token=" + token);
      currentSocket = socket;
      establishingSocket = false;

      socket.onopen = function () {
        socket.send(JSON.stringify({ type: "ping", reqid: reqId++ }));
        socket.send(JSON.stringify({ type: "setSessionStatus", status: "idle", reqid: reqId++ }));
        socket.send(JSON.stringify({ type: "subscribe", modelType: "Board", idModel: boardId, tags: ["clientActions", "updates"], invitationTokens: [], reqid: reqId++ }));
      };

      socket.onmessage = function (msg) {
        if (!msg.data) return;

        var data = JSON.parse(msg.data);
        if (!data.notify || data.notify.typeName !== "Card" || data.notify.event !== "updateModels") return;

        var deltas = {};
        for (var i = 0; i < data.notify.deltas.length; ++i) {
          if (data.notify.deltas[i].desc !== undefined) {
            deltas[data.notify.deltas[i].id] = data.notify.deltas[i].desc;
          }
        }

        var keys = Object.keys(boardCards);
        for (var i = 0; i < keys.length; ++i) {
          if (deltas[boardCards[keys[i]].id] !== undefined) {
            boardCards[keys[i]].desc = deltas[boardCards[keys[i]].id];
          }
        }
      };

      var intervalPing = setInterval(function () {
        socket.send("");
      }, 20000); // ping every 20 seconds
      
      var intervalActivity = setInterval(function () {
        socket.send(JSON.stringify({ type: "setSessionStatus", status: "idle", idBoard: boardId, reqid: reqId++ }));
      }, (5 * 60)* 1000); // ping every 5 minutes
      
      socket.onclose = function () {
        clearInterval(intervalPing);
        clearInterval(intervalActivity);
        establishSocket();
      };

      socket.onerror = function () {
        clearInterval(intervalPing);
        clearInterval(intervalActivity);
        establishSocket();
      };
    });
  }

  function linkCardToBoard(cardId, targetBoard, cb) {
    var oldDesc = boardCards[cardId].desc;
    var newDesc = oldDesc ? oldDesc.replace(/\[\/\/\]:\s*#board\s*\((.*?)\)/, "[//]:#board(" + targetBoard + ")") : "[//]:#board(" + targetBoard + ")";

    if (oldDesc === newDesc && newDesc.indexOf("[//]:#board(" + targetBoard + ")") === -1) {
      newDesc += "\r\n\r\n[//]:#board(" + targetBoard + ")";
    }

    $.ajax({
      type: "PUT",
      url: trelloAPI + "cards/" + cardId,
      data: {
        desc: newDesc,
        token: getLoginToken(),
        invitationTokens: []
      }
    }).done(function () {
      if (!cb) return;
      cb(null);
    }).fail(function (xhr, _, err) {
      if (!cb) return;
      cb(err);
    });
  }

  function addLinkButton(buttonContainer) {
    var linkBtn = $('<a href="#" class="button-link js-link-board-card" title="Link this card to another board."> <span class="icon-sm icon-card"></span> Link board </a>');
    buttonContainer.append(linkBtn);

    linkBtn.on("click", function () {
      var card = getActiveCard();
      if (!card) return alert("Could not parse card id");

      var target = prompt("Please provide the target board id");
      if (target === null) return;

      target = parseBoardUrl(target);
      if (target === null) {
        alert('Failed to parse provided board url!');
        return;
      }

      linkCardToBoard(card, target);
    });
  }

  function addCreateButton(buttonContainer) {
    var createBtn = $('<a href="#" class="button-link js-create-board-card" title="Create a new board from this card."> <span class="icon-sm icon-card"></span> Create board </a>');
    buttonContainer.append(createBtn);

    createBtn.on("click", function () {
      var cardId = getActiveCard();
      if (!cardId) return alert("Could not parse card id");

      var card = boardCards[cardId];
      $.post(trelloAPI + "boards?fields=url", { name: card.name, desc: card.desc, token: getLoginToken() }, function (e) {<commit_msg>Print proper error message if an error occured during linking<commit_after>﻿// ==UserScript==
// @name           Trellonest
// @description    This extension enables nested boards in Trello
// @include        https://trello.com/*
// @version        1.0
// ==/UserScript==

(function ($) {
  var trelloAPI = "https://trello.com/1/";

  function getLoginToken() {
    var token = document.cookie.match(/token=(.*?)(;|$)/);
    return (token && unescape(token[1])) || null;
  }

  var boardUrlRegex = /((^(https?:\/\/)?trello.com)?\/?b\/)?([^\/]+)\/?/;
  function parseBoardUrl(url) {
    var match = boardUrlRegex.exec(url);
    return match[4] || null;
  }

  function getBoard(cb) {
    var boardMatch = window.location.pathname.match(/^\/b\/(.*?)(\/|$)/);
    if (boardMatch) {
      var board = boardMatch && boardMatch[1];
      if (board) {
        $.get(trelloAPI + "boards/" + board + "?fields=id")
          .done(function (resp) {
            boardCache = resp.id;
            cb(null, resp.id);
          })
          .fail(function (xhr, status, err) {
            cb(err);
          });
      } else {
        cb(null, null);
      }
    } else {
      var cardMatch = window.location.pathname.match(/^\/c\/(.*?)(\/|$)/);
      if (cardMatch) {
        var card = cardMatch && cardMatch[1];

        $.get(trelloAPI + "cards/" + card + "?fields=idBoard")
          .done(function (resp) {
            boardCache = resp.idBoard;
            cb(null, resp.idBoard);
          })
          .fail(function (xhr, status, err) {
            cb(err);
          });
      } else {
        cb(new Error("Unsupported trello page"));
      }
    }
  }

  function getActiveCard() {
    var cardMatch = window.location.pathname.match(/^\/c\/(.*?)(\/|$)/);
    if (!cardMatch) return null;
    var card = cardMatch && cardMatch[1];
    return card || null;
  }

  var establishingSocket = false;
  var currentSocket;
  function establishSocket() {
    if (establishingSocket) return;
    establishingSocket = true;

    var token = getLoginToken();

    getBoard(function (err, boardId) {
      if (err) {
        establishingSocket = false;
        return;
      }

      if (currentSocket) {
        establishingSocket = false;

        currentSocket.close();
        currentSocket = null;
        return; // reestablishing called by close
      }

      var reqId = 0;
      var socket = new WebSocket("wss://trello.com/1/Session/socket?token=" + token);
      currentSocket = socket;
      establishingSocket = false;

      socket.onopen = function () {
        socket.send(JSON.stringify({ type: "ping", reqid: reqId++ }));
        socket.send(JSON.stringify({ type: "setSessionStatus", status: "idle", reqid: reqId++ }));
        socket.send(JSON.stringify({ type: "subscribe", modelType: "Board", idModel: boardId, tags: ["clientActions", "updates"], invitationTokens: [], reqid: reqId++ }));
      };

      socket.onmessage = function (msg) {
        if (!msg.data) return;

        var data = JSON.parse(msg.data);
        if (!data.notify || data.notify.typeName !== "Card" || data.notify.event !== "updateModels") return;

        var deltas = {};
        for (var i = 0; i < data.notify.deltas.length; ++i) {
          if (data.notify.deltas[i].desc !== undefined) {
            deltas[data.notify.deltas[i].id] = data.notify.deltas[i].desc;
          }
        }

        var keys = Object.keys(boardCards);
        for (var i = 0; i < keys.length; ++i) {
          if (deltas[boardCards[keys[i]].id] !== undefined) {
            boardCards[keys[i]].desc = deltas[boardCards[keys[i]].id];
          }
        }
      };

      var intervalPing = setInterval(function () {
        socket.send("");
      }, 20000); // ping every 20 seconds
      
      var intervalActivity = setInterval(function () {
        socket.send(JSON.stringify({ type: "setSessionStatus", status: "idle", idBoard: boardId, reqid: reqId++ }));
      }, (5 * 60)* 1000); // ping every 5 minutes
      
      socket.onclose = function () {
        clearInterval(intervalPing);
        clearInterval(intervalActivity);
        establishSocket();
      };

      socket.onerror = function () {
        clearInterval(intervalPing);
        clearInterval(intervalActivity);
        establishSocket();
      };
    });
  }

  function linkCardToBoard(cardId, targetBoard, cb) {
    var oldDesc = boardCards[cardId].desc;
    var newDesc = oldDesc ? oldDesc.replace(/\[\/\/\]:\s*#board\s*\((.*?)\)/, "[//]:#board(" + targetBoard + ")") : "[//]:#board(" + targetBoard + ")";

    if (oldDesc === newDesc && newDesc.indexOf("[//]:#board(" + targetBoard + ")") === -1) {
      newDesc += "\r\n\r\n[//]:#board(" + targetBoard + ")";
    }

    $.ajax({
      type: "PUT",
      url: trelloAPI + "cards/" + cardId,
      data: {
        desc: newDesc,
        token: getLoginToken(),
        invitationTokens: []
      }
    }).done(function () {
      if (!cb) return;
      cb(null);
    }).fail(function (xhr, _, err) {
      if (!cb) return;
      cb(err);
    });
  }

  function addLinkButton(buttonContainer) {
    var linkBtn = $('<a href="#" class="button-link js-link-board-card" title="Link this card to another board."> <span class="icon-sm icon-card"></span> Link board </a>');
    buttonContainer.append(linkBtn);

    linkBtn.on("click", function () {
      var card = getActiveCard();
      if (!card) return alert("Could not parse card id");

      var target = prompt("Please provide the target board id");
      if (target === null) return;

      target = parseBoardUrl(target);
      if (target === null) {
        alert('Failed to parse provided board url!');
        return;
      }

      linkCardToBoard(card, target, function (err) {
        if (err) {
          alert('Linking the board failed! The error was: ' + (err.message || err));
        }
      });
    });
  }

  function addCreateButton(buttonContainer) {
    var createBtn = $('<a href="#" class="button-link js-create-board-card" title="Create a new board from this card."> <span class="icon-sm icon-card"></span> Create board </a>');
    buttonContainer.append(createBtn);

    createBtn.on("click", function () {
      var cardId = getActiveCard();
      if (!cardId) return alert("Could not parse card id");

      var card = boardCards[cardId];
      $.post(trelloAPI + "boards?fields=url", { name: card.name, desc: card.desc, token: getLoginToken() }, function (e) {
{'fraction_non_alphanumeric': 0.17429022082018927, 'fraction_numerical': 0.004416403785488959, 'mean_word_length': 2.353874636339593, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 19, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1727872', 'n_tokens_mistral': 4011, 'n_tokens_neox': 3909, 'n_words': 886}