您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Use comment section features on web version of Sololearn playground
当前为
// ==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]; } })();