Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea57a315b7 | |||
| 8303dffe99 | |||
| ac2ca56a54 | |||
| 3dcc6dc6eb | |||
| f5679dd825 | |||
| dcfe8f3320 | |||
| ea5cd3763d | |||
| 71f43590db | |||
| eb58ac44ca | |||
| b60b3106a0 | |||
| 292d00e664 |
@@ -1,3 +1,11 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or issue with the software.
|
||||
title: '[BUG]'
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Bug Report
|
||||
|
||||
**Description of the Bug**
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or enhancement for the project.
|
||||
title: '[FEATURE]'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Feature Request
|
||||
|
||||
**Description of the Feature**
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
name: Help Request
|
||||
about: Use this template to request help or support.
|
||||
title: '[HELP]'
|
||||
labels: help
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Help Request
|
||||
|
||||
**Issue Summary**
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.1.1
|
||||
|
||||
- Bugs correction
|
||||
- Support for Atto editor
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Bugs correction
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
href="https://www.flaticon.com/free-icons/mortarboard" target="_blank" rel="noopener noreferrer"
|
||||
title="Mortarboard icons created by itim2101 - Flaticon" ><img src="./extension/icon.png" alt="Mortarboard icons created by itim2101 - Flaticon" width="150" style="display:block; margin:auto;"></a></p>
|
||||
|
||||
# MoodleGPT 1.1.0
|
||||
# MoodleGPT 1.1.1
|
||||
|
||||
This extension allows you to hide CHAT-GPT in a Moodle quiz. You just need to click on the question you want to solve, and CHAT-GPT will automatically provide the answer. However, one needs to be careful because as we know, CHAT-GPT can make errors especially in calculations.
|
||||
|
||||
@@ -12,7 +12,7 @@ Find the extension on the Chrome Webstore right [here](https://chrome.google.com
|
||||
|
||||
## Summary
|
||||
|
||||
- [MoodleGPT 1.1.0](#moodlegpt-110)
|
||||
- [MoodleGPT 1.1.1](#moodlegpt-111)
|
||||
- [Chrome Webstore](#chrome-webstore)
|
||||
- [Summary](#summary)
|
||||
- [Disclaimer !](#disclaimer-)
|
||||
@@ -32,6 +32,7 @@ Find the extension on the Chrome Webstore right [here](https://chrome.google.com
|
||||
- [True or false](#true-or-false)
|
||||
- [Number](#number)
|
||||
- [Text](#text)
|
||||
- [Atto](#atto)
|
||||
- [What about if the question can't be autocompleted ?](#what-about-if-the-question-cant-be-autocompleted-)
|
||||
- [Test](#test)
|
||||
- [Beta version with advanced features](#beta-version-with-advanced-features)
|
||||
@@ -145,6 +146,10 @@ Person 2 | Yann | 19/01/2000 | no
|
||||
|
||||

|
||||
|
||||
### Atto
|
||||
|
||||

|
||||
|
||||
## What about if the question can't be autocompleted ?
|
||||
|
||||
To know if the answer has been copied to the clipboard, you can look at the title of the page which will become <b>"Copied to clipboard"</b> for 3 seconds if `Title indication` is on.
|
||||
@@ -153,7 +158,7 @@ To know if the answer has been copied to the clipboard, you can look at the titl
|
||||
|
||||
## Test
|
||||
|
||||
- <b>Solution 1</b>: Go on [this moodle test page](https://school.moodledemo.net/login/index.php) (username: `student`, password: `moodle`) and choose any quiz.
|
||||
- <b>Solution 1</b>: Go on this [moodle demo page](https://moodle.org/demo).
|
||||
- <b>Solution 2</b>: Run the `index.html` file located in the `test/fake-moodle` folder.
|
||||
|
||||
## Beta version with advanced features
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "MoodleGPT",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "Hidden chat-gpt for your moodle quiz",
|
||||
"permissions": ["storage"],
|
||||
"action": {
|
||||
|
||||
@@ -32,9 +32,9 @@ saveBtn.addEventListener('click', function () {
|
||||
return;
|
||||
}
|
||||
|
||||
if (code.length > 0 && code.length < 3) {
|
||||
if (code.length > 0 && code.length < 2) {
|
||||
showMessage({
|
||||
msg: 'The code should at least contain 3 characters',
|
||||
msg: 'The code should at least contain 2 characters',
|
||||
error: true
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const CURRENT_VERSION = '1.1.0';
|
||||
const CURRENT_VERSION = '1.1.1';
|
||||
const versionDisplay = document.querySelector('#version');
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "moodlegpt",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "This extension allows you to hide CHAT-GPT in a Moodle quiz.",
|
||||
"scripts": {
|
||||
"build": "npm run prettier && npm run lint && rollup -c",
|
||||
"build": "npm run prettier && npm run lint && npm run fastBuild",
|
||||
"fastBuild": "rollup -c",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"prettier": "prettier --write ."
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ function setUpMoodleGpt(config: Config) {
|
||||
const inputTypeQuery = ['checkbox', 'radio', 'text', 'number']
|
||||
.map(e => `input[type="${e}"]`)
|
||||
.join(',');
|
||||
const inputQuery = inputTypeQuery + ', textarea, select, [contenteditable]';
|
||||
const inputQuery = inputTypeQuery + ', textarea, select, [contenteditable], .qtype_essay_editor';
|
||||
const forms = document.querySelectorAll('.formulation');
|
||||
|
||||
// For each form we inject a function on the queqtion
|
||||
|
||||
@@ -16,6 +16,10 @@ function createAndNormalizeQuestion(questionContainer: HTMLElement) {
|
||||
for (const useless of accesshideElements) {
|
||||
question = question.replace(useless.innerText, '');
|
||||
}
|
||||
const attoText = questionContainer.querySelector('.qtype_essay_editor');
|
||||
if (attoText) {
|
||||
question = question.replace((attoText as HTMLElement).innerText, '');
|
||||
}
|
||||
|
||||
// Make tables more readable for chat-gpt
|
||||
const tables: NodeListOf<HTMLTableElement> = questionContainer.querySelectorAll('.qtext table');
|
||||
|
||||
@@ -7,6 +7,7 @@ import handleRadio from '@core/questions/radio';
|
||||
import handleCheckbox from '@core/questions/checkbox';
|
||||
import handleSelect from '@core/questions/select';
|
||||
import handleTextbox from '@core/questions/textbox';
|
||||
import handleAtto from '@core/questions/atto';
|
||||
|
||||
type Props = {
|
||||
config: Config;
|
||||
@@ -26,6 +27,7 @@ function autoCompleteMode(props: Props) {
|
||||
if (!props.config.infinite) props.removeListener();
|
||||
|
||||
const handlers = [
|
||||
handleAtto,
|
||||
handleContentEditable,
|
||||
handleTextbox,
|
||||
handleNumber,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import type Config from '@typing/config';
|
||||
import type GPTAnswer from '@typing/gpt-answer';
|
||||
|
||||
/**
|
||||
* Hanlde atto editor
|
||||
* See: https://docs.moodle.org/404/en/Atto_editor#Atto_accessibility
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param gptAnswer
|
||||
* @returns
|
||||
*/
|
||||
function handleAtto(
|
||||
config: Config,
|
||||
inputList: NodeListOf<HTMLElement>,
|
||||
gptAnswer: GPTAnswer
|
||||
): boolean {
|
||||
const input = inputList[0];
|
||||
|
||||
if (!input.classList.contains('qtype_essay_editor')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const iframe = input.querySelector('iframe');
|
||||
if (!iframe || !iframe.contentDocument || !iframe.contentDocument.body || !iframe.contentWindow) {
|
||||
return false;
|
||||
}
|
||||
const iframeBody = iframe.contentDocument.body;
|
||||
|
||||
const textContainer = iframeBody.querySelector('p');
|
||||
if (!textContainer) return false;
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
const eventHandler = function (event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.key === 'Backspace' || index >= gptAnswer.response.length) {
|
||||
iframe.contentWindow!.removeEventListener('keydown', eventHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Append text one character at a time
|
||||
const textNode = document.createTextNode(gptAnswer.response.charAt(index++));
|
||||
textContainer.appendChild(textNode);
|
||||
|
||||
// Move the cursor after the last character
|
||||
const range = iframe.contentDocument!.createRange();
|
||||
range.selectNodeContents(textContainer);
|
||||
range.collapse(false); // Collapse the range to the end point
|
||||
const selection = iframe.contentWindow!.getSelection();
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
iframe.contentWindow!.focus(); // Focus the iframe window to see cursor
|
||||
};
|
||||
|
||||
iframe.contentWindow.addEventListener('keydown', eventHandler);
|
||||
} else {
|
||||
textContainer.textContent += gptAnswer.response;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default handleAtto;
|
||||
@@ -26,11 +26,13 @@ function handleCheckbox(
|
||||
|
||||
const possibleAnswers = Array.from(inputList)
|
||||
.map(inp => ({
|
||||
element: inp,
|
||||
element: inp as HTMLInputElement,
|
||||
value: normalizeText(inp?.parentElement?.textContent ?? '')
|
||||
}))
|
||||
.filter(obj => obj.value !== '');
|
||||
|
||||
// Find the best answers elements
|
||||
const correctElements: Set<HTMLInputElement> = new Set();
|
||||
for (const correct of corrects) {
|
||||
const bestAnswer = pickBestReponse(correct, possibleAnswers);
|
||||
|
||||
@@ -38,13 +40,23 @@ function handleCheckbox(
|
||||
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
|
||||
}
|
||||
|
||||
const correctInput = bestAnswer.element as HTMLInputElement;
|
||||
correctElements.add(bestAnswer.element as HTMLInputElement);
|
||||
}
|
||||
|
||||
// Check if it should be checked or not
|
||||
for (const element of possibleAnswers.map(e => e.element)) {
|
||||
const needAction =
|
||||
(element.checked && !correctElements.has(element)) ||
|
||||
(!element.checked && correctElements.has(element));
|
||||
|
||||
const action = () => needAction && element.click();
|
||||
|
||||
if (config.mouseover) {
|
||||
correctInput.addEventListener('mouseover', () => correctInput.click(), {
|
||||
element.addEventListener('mouseover', action, {
|
||||
once: true
|
||||
});
|
||||
} else {
|
||||
correctInput.click();
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import type Config from '@typing/config';
|
||||
import type GPTAnswer from '@typing/gpt-answer';
|
||||
|
||||
function isContentEditable(element: HTMLElement) {
|
||||
const contenteditable = element.getAttribute('contenteditable');
|
||||
return typeof contenteditable === 'string' && contenteditable !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hanlde contenteditable elements
|
||||
* @param config
|
||||
@@ -17,17 +22,22 @@ function handleContentEditable(
|
||||
|
||||
if (
|
||||
inputList.length !== 1 || // for now we don't handle many input for editable textcontent
|
||||
input.getAttribute('contenteditable') !== 'true'
|
||||
!isContentEditable(input)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
input.addEventListener('keydown', function (event: KeyboardEvent) {
|
||||
if (event.key === 'Backspace') index = gptAnswer.response.length + 1;
|
||||
if (index > gptAnswer.response.length) return;
|
||||
|
||||
const eventHandler = function (event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.key === 'Backspace' || index >= gptAnswer.response.length) {
|
||||
input.removeEventListener('keydown', eventHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
input.textContent = gptAnswer.response.slice(0, ++index);
|
||||
|
||||
// Put the cursor at the end of the typed text
|
||||
@@ -40,7 +50,9 @@ function handleContentEditable(
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', eventHandler);
|
||||
} else {
|
||||
input.textContent = gptAnswer.response;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,20 @@ function handleNumber(
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
input.addEventListener('keydown', function (event: Event) {
|
||||
|
||||
const eventHanlder = function (event: Event) {
|
||||
event.preventDefault();
|
||||
if ((<KeyboardEvent>event).key === 'Backspace') index = number.length + 1;
|
||||
if (index > number.length) return;
|
||||
if ((<KeyboardEvent>event).key === 'Backspace' || index >= number.length) {
|
||||
input.removeEventListener('keydown', eventHanlder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (number.slice(index, index + 1) === '.') ++index;
|
||||
|
||||
input.value = number.slice(0, ++index);
|
||||
});
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', eventHanlder);
|
||||
} else {
|
||||
input.value = number;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,19 @@ function handleTextbox(
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
input.addEventListener('keydown', function (event: Event) {
|
||||
|
||||
const eventHandler = function (event: Event) {
|
||||
event.preventDefault();
|
||||
if ((<KeyboardEvent>event).key === 'Backspace') {
|
||||
index = gptAnswer.response.length + 1;
|
||||
|
||||
if ((<KeyboardEvent>event).key === 'Backspace' || index >= gptAnswer.response.length) {
|
||||
input.removeEventListener('keydown', eventHandler);
|
||||
return;
|
||||
}
|
||||
if (index > gptAnswer.response.length) return;
|
||||
|
||||
input.value = gptAnswer.response.slice(0, ++index);
|
||||
});
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', eventHandler);
|
||||
} else {
|
||||
input.value = gptAnswer.response;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Textbox -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>What is the name of the USA president ?</p>
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
|
||||
Reference in New Issue
Block a user