-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Start fixing class hashes -Removes BUTTON_CLASSES, used for storing the old class names. We're trying to move away from these because they are unstable. -Add a "kae-program-button" class to the Copy Link and vote buttons, which is currently unused. -Get program buttons by `a[role="button"]` -Move styles for all extension custom buttons into general.css (instead of trying to set a class that KA will match) -Update the class used to find and place the Editor Settings Toggle button, -Refactor the Editor Settings Toggle button so that if that class changes, stored settings will still be applied. * Grab buttons in TS and add a class Instead of trying to do trash CSS selectors that didn't even work, I grabbed the program buttons in TypeScript and added the ".kae-program-button" class to them. Simplifies the CSS accordingly. * Refactor buttons Refactor buttons.ts to export one function, which is responsible for finding the buttons wrap and calling other modifications. * Update programdata Tie to the user link instead of the updated class. Confirmed to work even with child account programs. Unconfirmed with anonymous programs. Also adds the table a sibling to the program buttons and user info, instead of as a child of the updated element or one higher than whereever it was. Fixes a bug with the toggle editor settings button where it thought it was an anchor but was previously updated to a button. * Move flag button code into buttons.ts Move flag button code into buttons.ts, fixing it at the same time to run off the same querySelectorPromise as the other button events. Removes the flag file! * Remove old button css * Move report button from report.ts to buttons.ts Moved it onto the same querySelectorPromise as everything else! * Update discussion page check and program button spacing * Fix lint errors * Add some discussion buttons listeners Find all discussion tab buttons (Questions, T&T, Help Requests), and add event listeners to them to trigger looking for new comments. Find the Load More Comments button and attach a listener to that. * Add check for replies button & refactor -Add an event listener to the show replies button -Move code that handles top-level comments into a specific check -Add the number of flags to a comment even if there are no flags (makes it clear when the Exension's checking flags) -Add a bunch of comments * Refactor load more comments button listener -We assume that there is at least 1 unalteredComments, because the querySelector fired, and then assume it has a 4 times removed parent (reasonable, if even if we find the wrong element, given the depth of KA's DOM tree). We then grab the last of it's children. If that's a button, we add an event listener, otherwise we exit silently. -Increase time we search for new comments from 1sec to 2sec, I had it fail loading questions once, and I've cut down on the number times where it shouldn't find comments. * Listener for answers -Add an event listener for the button to show answers to questions -Update error handling to ignore the promise rejection error and still console.error other, legitimate, errors * Fix lint errors * 4.6.3 -Remove debug code -Increase comment searching timer (2s -> 4s) -Changelog and version bumps to 4.6.3
- Loading branch information
1 parent
37e5cee
commit 74a6c27
Showing
11 changed files
with
305 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,109 +1,184 @@ | ||
import { Program } from "./types/data"; | ||
import { querySelectorPromise } from "./util/promise-util"; | ||
import { QUEUE_ROOT } from "./types/names"; | ||
import { getCSRF } from "./util/cookie-util"; | ||
|
||
const enum BUTTON_CLASSES { | ||
default = "link_1uvuyao-o_O-computing_77ub1h", | ||
} | ||
import { buildQuery } from "./util/text-util"; | ||
|
||
//Replace KA's vote button with one that updates after you vote and allows undoing votes | ||
function replaceVoteButton (program: Program): void { | ||
querySelectorPromise(".voting-wrap .discussion-meta-controls span") | ||
.then(wrap => { | ||
if (!(wrap instanceof HTMLElement) || !wrap.innerText.includes("Vote")) { | ||
console.log("Voting failed to load.", wrap, wrap.firstChild); | ||
return; | ||
} | ||
|
||
const VOTE_URL = "/api/internal/discussions/voteentity"; | ||
|
||
let voted = wrap.innerText.includes("Voted Up"); | ||
|
||
const orgVotes = program.sumVotesIncremented - (voted ? 1 : 0); | ||
|
||
const newWrap = document.createElement("span"); | ||
const voteButton = document.createElement("a"); | ||
const voteText = document.createElement("span"); | ||
voteButton.appendChild(voteText); | ||
voteButton.classList.add(BUTTON_CLASSES.default); | ||
newWrap.appendChild(voteButton); | ||
|
||
function updateVoteDisplay () { | ||
voteText.innerText = (voted ? "Voted Up!" : "Vote Up") + " • " + (orgVotes + (voted ? 1 : 0)); | ||
} | ||
function replaceVoteButton (buttons: HTMLDivElement, program: Program): void { | ||
const wrap = buttons.querySelector(".voting-wrap .discussion-meta-controls span"); | ||
|
||
//TODO: Handle non-English | ||
if (!(wrap instanceof HTMLElement) || !wrap.innerText.includes("Vote")) { | ||
console.log("Voting failed to load.", buttons, wrap, wrap && wrap.firstChild); | ||
return; | ||
} | ||
|
||
const VOTE_URL = "/api/internal/discussions/voteentity"; | ||
|
||
let voted = wrap.innerText.includes("Voted Up"); | ||
|
||
const orgVotes = program.sumVotesIncremented - (voted ? 1 : 0); | ||
|
||
const newWrap = document.createElement("span"); | ||
const voteButton = document.createElement("a"); | ||
const voteText = document.createElement("span"); | ||
voteButton.setAttribute("role", "button"); | ||
voteButton.classList.add("kae-program-button"); | ||
voteButton.appendChild(voteText); | ||
newWrap.appendChild(voteButton); | ||
|
||
function updateVoteDisplay () { | ||
voteText.innerText = (voted ? "Voted Up!" : "Vote Up") + " • " + (orgVotes + (voted ? 1 : 0)); | ||
} | ||
updateVoteDisplay(); | ||
|
||
const profileData = window.KA._userProfileData; | ||
if (!profileData || profileData.isPhantom) { | ||
console.log("Not logged in.", voteButton); | ||
voteButton.setAttribute("style", "cursor: default !important"); | ||
voteButton.addEventListener("click", function () { | ||
alert("You must be logged in in order to vote."); | ||
}); | ||
} else { | ||
newWrap.addEventListener("click", function () { | ||
voted = !voted; | ||
updateVoteDisplay(); | ||
|
||
const profileData = window.KA._userProfileData; | ||
if (!profileData || profileData.isPhantom) { | ||
console.log("Not logged in.", voteButton); | ||
voteButton.setAttribute("style", "cursor: default !important"); | ||
voteButton.addEventListener("click", function () { | ||
alert("You must be logged in in order to vote."); | ||
}); | ||
} else { | ||
newWrap.addEventListener("click", function () { | ||
fetch(`${VOTE_URL}?entity_key=${program.key}&vote_type=${voted ? 1 : 0}`, { | ||
method: "POST", | ||
headers: { "X-KA-FKey": getCSRF() }, | ||
credentials: "same-origin" | ||
}).then((response: Response): void => { | ||
//If there's an error, undo the vote | ||
if (response.status !== 204) { | ||
response.json().then((res: { error?: string }): void => { | ||
if (res.error) { | ||
alert("Failed with error:\n\n" + res.error); | ||
} | ||
}).catch(() => { | ||
alert(`Voting failed with status ${response.status}`); | ||
}); | ||
voted = !voted; | ||
updateVoteDisplay(); | ||
} | ||
}).catch(console.error); | ||
}); | ||
} | ||
|
||
fetch(`${VOTE_URL}?entity_key=${program.key}&vote_type=${voted ? 1 : 0}`, { | ||
method: "POST", | ||
headers: { "X-KA-FKey": getCSRF() }, | ||
credentials: "same-origin" | ||
}).then((response: Response): void => { | ||
if (response.status === 204) { return; } | ||
voted = !voted; | ||
updateVoteDisplay(); | ||
}).catch(console.error); | ||
}); | ||
} | ||
if (wrap.parentNode) { | ||
wrap.parentNode.insertBefore(newWrap, wrap); | ||
wrap.parentNode.removeChild(wrap); | ||
} | ||
} | ||
|
||
if (wrap.parentNode) { | ||
wrap.parentNode.insertBefore(newWrap, wrap); | ||
wrap.parentNode.removeChild(wrap); | ||
//Add a "Copy Link" button | ||
function addLinkButton (buttons: HTMLDivElement, program: Program): void { | ||
const copyLinkButton: HTMLAnchorElement = document.createElement("a"); | ||
copyLinkButton.id = "kae-link-button"; | ||
|
||
copyLinkButton.setAttribute("role", "button"); | ||
copyLinkButton.innerHTML = "<span>Copy Link</span>"; | ||
copyLinkButton.classList.add("kae-program-button"); | ||
copyLinkButton.addEventListener("click", function () { | ||
if (window.navigator.hasOwnProperty("clipboard")) { | ||
/* tslint:disable:no-any */ | ||
(window.navigator as any).clipboard.writeText(`https://khanacademy.org/cs/i/${program.id}`).catch((err: Error) => { | ||
alert("Copying failed with error:\n" + err); | ||
}); | ||
/* tslint:enable:no-any */ | ||
} else { | ||
try { | ||
const textArea = document.createElement("textarea"); | ||
textArea.value = `https://khanacademy.org/cs/i/${program.id}`; | ||
copyLinkButton.parentElement!.insertBefore(textArea, copyLinkButton); | ||
textArea.focus(); | ||
textArea.select(); | ||
|
||
document.execCommand("copy"); | ||
|
||
copyLinkButton.parentElement!.removeChild(textArea); | ||
} catch (err) { | ||
alert("Copying failed with error:\n" + err); | ||
} | ||
}).catch(console.error); | ||
} | ||
}); | ||
|
||
buttons.insertBefore(copyLinkButton, buttons.children[buttons.children.length - 1]); | ||
buttons.insertBefore(document.createTextNode(" "), copyLinkButton.nextSibling); | ||
} | ||
|
||
//Add a "Copy Link" button | ||
function addLinkButton (program: Program): void { | ||
querySelectorPromise(".buttons_vponqv") | ||
//Add the number of flags and title text to the program flag button | ||
function addProgramFlags (buttons: HTMLDivElement, program: Program) { | ||
const controls = buttons.querySelector(".discussion-meta-controls"); | ||
|
||
if (!controls) { | ||
console.log(buttons); | ||
throw new Error("Button controls should be loaded."); | ||
} | ||
|
||
const programFlags: string[] = program.flags; | ||
const flagButton: HTMLElement = <HTMLElement>controls.childNodes[2]; | ||
const reasons: string = programFlags.length > 0 ? programFlags.reduce((total, current) => total += `${current}\n`) : "No flags here!"; | ||
const profileData = window.KA._userProfileData; | ||
//TODO: Allow viewing flags on your own program (where there's normally not a flag button) | ||
//TODO: Bug: errors on offical programs (no flag button) | ||
if (profileData && profileData.kaid !== program.kaid && profileData.isModerator === false) { | ||
flagButton.textContent += ` • ${programFlags.length}`; | ||
flagButton.title = reasons; | ||
} | ||
} | ||
|
||
//Add a button to report the program | ||
function addProgramReportButton (buttons: HTMLDivElement, program: Program, kaid: string) { | ||
if (kaid !== program.kaid) { | ||
const reportButton: HTMLAnchorElement = document.createElement("a"); | ||
reportButton.id = "kae-report-button"; | ||
reportButton.classList.add("kae-program-button"); | ||
reportButton.href = `${QUEUE_ROOT}submit?${buildQuery({ | ||
type: "program", | ||
id: program.id.toString(), | ||
callback: window.location.href | ||
})}`; | ||
reportButton.setAttribute("role", "button"); | ||
reportButton.innerHTML = "<span>Report</span>"; | ||
buttons.insertBefore(reportButton, buttons.children[1]); | ||
buttons.insertBefore(document.createTextNode(" "), reportButton.nextSibling); | ||
} | ||
} | ||
|
||
function findOtherButtons (buttons: HTMLDivElement): void { | ||
/*Add the kae-program-button class to all other program buttons so we can restyle them */ | ||
Array.from(buttons.querySelectorAll("a[role=\"button\"]")).forEach(el => el.classList.add("kae-program-button")); | ||
} | ||
|
||
function loadButtonMods (program: Program): void { | ||
const kaid:string = window.KA.kaid; | ||
|
||
querySelectorPromise(".voting-wrap") | ||
.then(votingWrap => votingWrap.parentNode) | ||
.then(buttons => buttons as HTMLDivElement) | ||
.then(buttons => { | ||
console.log(buttons); | ||
const copyLinkButton: HTMLAnchorElement = document.createElement("a"); | ||
copyLinkButton.id = "kae-link-button"; | ||
|
||
copyLinkButton.setAttribute("role", "button"); | ||
copyLinkButton.innerHTML = "<span>Copy Link</span>"; | ||
copyLinkButton.classList.add(BUTTON_CLASSES.default); | ||
|
||
copyLinkButton.addEventListener("click", function () { | ||
if (window.navigator.hasOwnProperty("clipboard")) { | ||
/* tslint:disable:no-any */ | ||
(window.navigator as any).clipboard.writeText(`https://khanacademy.org/cs/i/${program.id}`).catch((err: Error) => { | ||
alert("Copying failed with error:\n" + err); | ||
}); | ||
/* tslint:enable:no-any */ | ||
} else { | ||
try { | ||
const textArea = document.createElement("textarea"); | ||
textArea.value = `https://khanacademy.org/cs/i/${program.id}`; | ||
copyLinkButton.parentElement!.insertBefore(textArea, copyLinkButton); | ||
textArea.focus(); | ||
textArea.select(); | ||
|
||
document.execCommand("copy"); | ||
|
||
copyLinkButton.parentElement!.removeChild(textArea); | ||
} catch (err) { | ||
alert("Copying failed with error:\n" + err); | ||
} | ||
} | ||
}); | ||
findOtherButtons(buttons); | ||
addLinkButton(buttons, program); | ||
replaceVoteButton(buttons, program); | ||
addProgramFlags(buttons, program); | ||
addProgramReportButton(buttons, program, kaid); | ||
}); | ||
|
||
buttons.insertBefore(copyLinkButton, buttons.children[buttons.children.length - 1]); | ||
buttons.insertBefore(document.createTextNode(" "), copyLinkButton.nextSibling); | ||
querySelectorPromise("#child-account-notice") | ||
.then(childNotice => childNotice as HTMLSpanElement) | ||
.then(childNotice => childNotice.parentNode) | ||
.then(buttons => buttons as HTMLDivElement) | ||
.then(buttons => { | ||
findOtherButtons(buttons); | ||
addLinkButton(buttons, program); | ||
//TODO, let voting run here too | ||
}); | ||
|
||
//TODO: | ||
//Find the buttons wrap on offical project pages (maybe for voting or link copying. Might not be useful) | ||
//https://www.khanacademy.org/computing/computer-programming/programming/drawing-basics/pc/challenge-waving-snowman | ||
} | ||
|
||
export { BUTTON_CLASSES, addLinkButton, replaceVoteButton }; | ||
export { loadButtonMods }; |
Oops, something went wrong.