- // ==UserScript==
- // @name Sololearn Code Comments
- // @namespace http://tampermonkey.net/
- // @version 0.1
- // @description Use comment section features on web version of Sololearn playground
- // @author DonDejvo
- // @match https://www.sololearn.com/compiler-playground/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=sololearn.com
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- (async () => {
- 'use strict';
-
- class Store {
- static _instance;
-
- _token;
- _profile;
-
- static _get() {
- if (this._instance == null) {
- this._instance = new Store();
- }
- return this._instance;
- }
-
- static async login(userId, token) {
- this._get()._token = token;
- const data = await this.postAction("https://api3.sololearn.com/Profile/GetProfile", {
- excludestats: true,
- id: userId
- });
- this._get()._profile = data.profile;
- }
-
- static async postAction(url, body) {
- const res = await fetch(url, {
- headers: {
- "Content-Type": "application/json",
- "Authorization": "Bearer " + this._get()._token
- },
- referrer: "https://www.sololearn.com/",
- body: JSON.stringify(body),
- method: "POST",
- mode: "cors"
- });
- return await res.json();
- }
-
- static get profile() {
- return this._get()._profile;
- }
- }
-
- class Code {
- _data;
- _comments = [];
- _replies = [];
-
- static async load(publicId) {
- const data = await Store.postAction("https://api3.sololearn.com/Playground/GetCode", {
- publicId: publicId
- });
- return new Code(data);
- }
-
- constructor(data) {
- this._data = data;
- }
-
- _getReplies(parentId) {
- const elem = this._replies.find(elem => elem.parentId == parentId);
- return elem ? elem.comments : [];
- }
-
- _addReply(comment, parentId) {
- const elem = this._replies.find(elem => elem.parentId == parentId);
- if (elem) {
- elem.comments.push(comment);
- }
- else {
- this._replies.push({
- parentId,
- comments: [comment]
- });
- }
- }
-
- async _loadReplies(parentId, count) {
- const elem = this._replies.find(elem => elem.parentId == parentId);
- const index = elem ? elem.comments.length : 0;
- const data = await Store.postAction("https://api3.sololearn.com/Discussion/GetCodeComments", {
- codeId: this._data.code.id,
- count,
- index,
- orderBy: 1,
- parentId
- });
- for (let comment of data.comments) {
- this._addReply(comment, parentId);
- }
- return data;
- }
-
- _clearComments() {
- this._comments = [];
- this._replies = [];
- }
-
- getComments(parentId = null) {
- if (parentId == null) {
- return this._comments;
- }
- return this._getReplies(parentId);
- }
-
- async loadComments(parentId = null, count = 20) {
- if (parentId) {
- const data = await this._loadReplies(parentId, count);
- return data.comments;
- }
- const index = this._comments.length;
- const data = await Store.postAction("https://api3.sololearn.com/Discussion/GetCodeComments", {
- codeId: this._data.code.id,
- count,
- index,
- orderBy: 1,
- parentId
- });
- for (let comment of data.comments) {
- this._comments.push(comment);
- }
- return data.comments;
- }
-
- async createComment(message, parentId = null) {
- const data = await Store.postAction("https://api3.sololearn.com/Discussion/CreateCodeComment", {
- codeId: this._data.code.id,
- message,
- parentId
- });
- const comment = data.comment;
- if (parentId) {
- this._addReply(comment, parentId);
- }
- else {
- this._comments.push(comment);
- }
- return data.comment;
- }
-
- async deleteComment(id) {
- let toDelete;
- toDelete = this._comments.find(elem => elem.id == id);
- if (toDelete) {
- let idx;
- idx = this._comments.indexOf(toDelete);
- this._comments.splice(idx, 1);
- const elem = this._replies.find(elem => elem.parentId == id);
- if (elem) {
- idx = this._replies.indexOf(elem);
- this._replies.splice(idx, 1);
- }
- }
- else {
- for (let elem of this._replies) {
- for (let comment of elem.comments) {
- if (comment.id == id) {
- const idx = elem.comments.indexOf(comment);
- elem.comments.splice(idx, 1);
- }
- }
- }
- }
- await Store.postAction("https://api3.sololearn.com/Discussion/DeleteCodeComment", {
- id
- });
- }
-
- async editComment(message, id) {
- await Store.postAction("https://api3.sololearn.com/Discussion/EditCodeComment", {
- id,
- message
- });
-
- }
-
- render(root) {
- const modal = document.createElement("div");
- modal.style.display = "flex";
- modal.style.position = "absolute";
- modal.style.zIndex = 9999;
- modal.style.left = "0";
- modal.style.top = "0";
- modal.style.width = "100%";
- modal.style.height = "100%";
- modal.style.backgroundColor = "rgba(128, 128, 128, 0.5)";
- modal.style.alignItems = "center";
- modal.style.justifyContent = "center";
-
- const container = document.createElement("div");
- container.style.position = "relative";
- container.style.width = "600px";
- container.style.height = "800px";
- container.style.backgroundColor = "#fff";
- container.style.padding = "18px 12px";
- modal.appendChild(container);
-
- const closeBtn = document.createElement("button");
- closeBtn.innerHTML = "×";
- closeBtn.style.position = "absolute";
- closeBtn.style.right = "0";
- closeBtn.style.top = "0";
- closeBtn.addEventListener("click", () => {
- modal.style.display = "none";
- });
-
- const title = document.createElement("h1");
- title.textContent = this._data.code.comments + " comments";
- title.style.textAlign = "center";
- container.appendChild(title);
- container.appendChild(closeBtn);
-
- const commentsBody = document.createElement("div");
- commentsBody.style.width = "100%";
- commentsBody.style.height = "calc(100% - 60px)";
- commentsBody.style.overflowY = "auto";
- container.appendChild(commentsBody);
-
- const renderCreateCommentForm = () => {
- const createCommentForm = document.createElement("div");
- createCommentForm.style.display = "none";
- createCommentForm.style.position = "absolute";
- createCommentForm.style.width = "100%";
-
- const input = document.createElement("textarea");
- input.style.width = "100%";
- input.style.height = "120px";
- input.placeholder = "Write your comment here...";
- createCommentForm.appendChild(input);
-
- const buttonContainer = document.createElement("div");
- createCommentForm.appendChild(buttonContainer);
-
- const postButton = document.createElement("button");
- buttonContainer.appendChild(postButton);
- postButton.textContent = "Post";
-
- const cancelButton = document.createElement("button");
- buttonContainer.appendChild(cancelButton);
- cancelButton.textContent = "Cancel";
-
- return {
- createCommentForm,
- input,
- postButton,
- cancelButton
- };
- }
-
- const createComment = (comment) => {
- const container = document.createElement("div");
- container.style.width = "100%";
-
- const m = new Date(comment.date);
- const dateString = m.getUTCFullYear() + "/" +
- ("0" + (m.getUTCMonth() + 1)).slice(-2) + "/" +
- ("0" + m.getUTCDate()).slice(-2) + " " +
- ("0" + m.getUTCHours()).slice(-2) + ":" +
- ("0" + m.getUTCMinutes()).slice(-2) + ":" +
- ("0" + m.getUTCSeconds()).slice(-2);
-
- container.innerHTML = `<div style="display:flex; gap: 6px; padding: 6px 8px; margin-bottom: 8px;">
- <img style="width: 64px; height: 64px; border-radius: 50%; overflow: hidden; flex-shrink: 0;" src="${comment.avatarUrl}" alt="${comment.userName} - avatar">
- <div style="display: flex; flex-direction: column; flex-grow: 1;">
- <div style="display: flex; direction: row; justify-content: space-between;">
- <div>${comment.userName}</div>
- <div>${dateString}</div>
- </div>
- <div style="white-space: pre-wrap;">${comment.message.trim().replace(/</g, "<").replace(/>/g, ">")}</div>
- <div style="display: flex; justify-content: flex-end;">
- <div style="display: flex; gap: 4px;">
- <button ${comment.parentID === null ? "" : "disabled"} data-id="${comment.id}" class="toggle-replies-btn">${comment.replies} replies</button>
- <button ${comment.parentID === null ? "" : "disabled"} data-id="${comment.id}" class="reply-btn">Reply</button>
- </div>
- </div>
- </div>
- </div>
- <div data-id="${comment.id}" class="replies" style="display: none; border-top: 1px solid #000; border-bottom: 1px solid #000; padding: 4px 0;"></div>
- `;
-
- return container;
- }
-
- const renderLoadButton = (parentId, body) => {
- const container = document.createElement("button");
- container.textContent = "...";
- container.addEventListener("click", () => {
- body.removeChild(container);
- loadComments(body, parentId);
- });
- body.appendChild(container);
- }
-
- const loadComments = (body, parentId = null) => {
- this.loadComments(parentId)
- .then(comments => {
- for (let comment of comments) {
- body.append(createComment(comment));
- }
- if (comments.length) {
- renderLoadButton(parentId, body);
- }
- });
- }
-
- const { createCommentForm, input, postButton, cancelButton } = renderCreateCommentForm();
- container.appendChild(createCommentForm);
-
- const openCommentForm = (parentId = null) => {
- createCommentForm.style.display = "block";
- createCommentForm.dataset.parentId = parentId;
- }
-
- const getRepliesContainer = (commentId) => {
- let out = null;
- const replies = document.querySelectorAll(".replies");
- replies.forEach(elem => {
- if (commentId == elem.dataset.id) {
- out = elem;
- }
- });
- return out;
- }
-
- const showCommentFormButton = document.createElement("button");
- showCommentFormButton.textContent = "Post comment";
- container.appendChild(showCommentFormButton);
- showCommentFormButton.addEventListener("click", () => openCommentForm());
-
- const postComment = () => {
- const parentId = createCommentForm.dataset.parentId == "null" ? null : +createCommentForm.dataset.parentId;
- this.createComment(input.value, parentId)
- .then(comment => {
- input.value = "";
- createCommentForm.style.display = "none";
- comment.userName = Store.profile.name;
- comment.avatarUrl = Store.profile.avatarUrl;
- comment.replies = 0;
- if (parentId === null) {
- commentsBody.prepend(createComment(comment));
- }
- else {
- getRepliesContainer(parentId).append(createComment(comment));
- const toggleReplyButtons = document.querySelectorAll(".toggle-replies-btn");
- toggleReplyButtons.forEach(elem => {
- if (parentId == elem.dataset.id) {
- elem.textContent = (+elem.textContent.split(" ")[0] + 1) + " replies";
- }
- });
- }
- });
- }
-
- postButton.addEventListener("click", () => postComment());
- cancelButton.addEventListener("click", () => createCommentForm.style.display = "none");
-
- loadComments(commentsBody);
-
- root.appendChild(modal);
-
- addEventListener("click", ev => {
- if (ev.target.classList.contains("toggle-replies-btn")) {
- const elem = getRepliesContainer(ev.target.dataset.id);
- if (elem.classList.contains("replies_opened")) {
- elem.style.display = "none";
- }
- else {
- elem.style.display = "block";
- loadComments(elem, ev.target.dataset.id);
- }
- elem.classList.toggle("replies_opened");
- }
- else if (ev.target.classList.contains("reply-btn")) {
- const elem = getRepliesContainer(ev.target.dataset.id);
- if (!elem.classList.contains("replies_opened")) {
- elem.style.display = "block";
- loadComments(elem, ev.target.dataset.id);
- elem.classList.add("replies_opened");
- }
-
- openCommentForm(ev.target.dataset.id);
- }
- });
- return modal;
- }
-
- }
-
- const main = async () => {
-
- const userId = JSON.parse(localStorage.getItem("user")).data.id;
- const accessToken = JSON.parse(localStorage.getItem("accessToken")).data;
- const publicId = window.location.pathname.split("/")[2];
-
- await Store.login(
- userId,
- accessToken
- );
-
- const code = await Code.load(publicId);
- const modal = code.render(document.querySelector(".sl-playground-wrapper"));
- modal.style.display = "none";
-
- const openModalButton = document.createElement("button");
- openModalButton.textContent = "Show comments";
- openModalButton.addEventListener("click", () => modal.style.display = "flex");
- document.querySelector(".sl-playground-left").appendChild(openModalButton);
- }
-
- setTimeout(main, 1000);
-
- function getCookie(cookieName) {
- let cookie = {};
- document.cookie.split(';').forEach(function(el) {
- let [key,value] = el.split('=');
- cookie[key.trim()] = value;
- });
- return cookie[cookieName];
- }
-
- })();