Compare commits

..

No commits in common. "master" and "Bug579-LuKangyang" have entirely different histories.

24 changed files with 158 additions and 483 deletions

View File

@ -18,7 +18,7 @@ picked from articles selected for him to read according his vocabulary level. E
`python3 main.py` `python3 main.py`
Make sure you have put the SQLite database file in the path `app/db` (see below). Make sure you have put the SQLite database file in the path `app/static` (see below).
## Run it as a Docker container ## Run it as a Docker container
@ -214,5 +214,5 @@ Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=215
Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=489 Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=489
*Last modified on 2026-03-12* *Last modified on 2023-01-30*

View File

@ -106,7 +106,7 @@ def get_today_article(user_word_list, visited_articles):
text_level = text_difficulty_level(d['text'], d3) text_level = text_difficulty_level(d['text'], d3)
result_of_generate_article = "found" result_of_generate_article = "found"
today_article = {} today_article = None
if d: if d:
oxford_words = load_oxford_words(oxford_words_path) oxford_words = load_oxford_words(oxford_words_path)
oxford_word_count, total_words = count_oxford_words(d['text'],oxford_words) oxford_word_count, total_words = count_oxford_words(d['text'],oxford_words)

View File

@ -1,8 +1,6 @@
import hashlib import hashlib
import string import string
from datetime import datetime, timedelta from datetime import datetime, timedelta
import unicodedata
def md5(s): def md5(s):
''' '''
@ -13,13 +11,16 @@ def md5(s):
h = hashlib.md5(s.encode(encoding='utf-8')) h = hashlib.md5(s.encode(encoding='utf-8'))
return h.hexdigest() return h.hexdigest()
# import model.user after the defination of md5(s) to avoid circular import # import model.user after the defination of md5(s) to avoid circular import
from model.user import get_user_by_username, insert_user, update_password_by_username from model.user import get_user_by_username, insert_user, update_password_by_username
path_prefix = '/var/www/wordfreq/wordfreq/' path_prefix = '/var/www/wordfreq/wordfreq/'
path_prefix = './' # comment this line in deployment path_prefix = './' # comment this line in deployment
def verify_pass(newpass,oldpass):
if(newpass==oldpass):
return True
def verify_user(username, password): def verify_user(username, password):
user = get_user_by_username(username) user = get_user_by_username(username)
@ -29,7 +30,7 @@ def verify_user(username, password):
def add_user(username, password): def add_user(username, password):
start_date = datetime.now().strftime('%Y%m%d') start_date = datetime.now().strftime('%Y%m%d')
expiry_date = (datetime.now() + timedelta(days=30)).strftime('%Y%m%d') # will expire after 30 days expiry_date = (datetime.now() + timedelta(days=30)).strftime('%Y%m%d') # will expire after 30 days
# 将用户名和密码一起加密,以免暴露不同用户的相同密码 # 将用户名和密码一起加密,以免暴露不同用户的相同密码
password = md5(username + password) password = md5(username + password)
insert_user(username=username, password=password, start_date=start_date, expiry_date=expiry_date) insert_user(username=username, password=password, start_date=start_date, expiry_date=expiry_date)
@ -49,12 +50,12 @@ def change_password(username, old_password, new_password):
:return: 修改成功:True 否则:False :return: 修改成功:True 否则:False
''' '''
if not verify_user(username, old_password): # 旧密码错误 if not verify_user(username, old_password): # 旧密码错误
return {'error':'Old password is wrong.', 'username':username} return False
# 将用户名和密码一起加密,以免暴露不同用户的相同密码 # 将用户名和密码一起加密,以免暴露不同用户的相同密码
if new_password == old_password: #新旧密码一致 if verify_pass(new_password,old_password): #新旧密码一致
return {'error':'New password cannot be the same as the old password.', 'username':username} return False
update_password_by_username(username, new_password) update_password_by_username(username, new_password)
return {'success':'Password changed', 'username':username} return True
def get_expiry_date(username): def get_expiry_date(username):
@ -64,64 +65,30 @@ def get_expiry_date(username):
else: else:
return user.expiry_date return user.expiry_date
class UserName: class UserName:
def __init__(self, username): def __init__(self, username):
self.username = username self.username = username
def contains_chinese(self):
for char in self.username:
# Check if the character is in the CJK (Chinese, Japanese, Korean) Unicode block
if unicodedata.name(char).startswith('CJK UNIFIED IDEOGRAPH'):
return True
return False
def validate(self): def validate(self):
if len(self.username) > 20: if len(self.username) > 20:
return f'{self.username} is too long. The user name cannot exceed 20 characters.' return f'{self.username} is too long. The user name cannot exceed 20 characters.'
if self.username.startswith('.'): # a user name must not start with a dot if self.username.startswith('.'): # a user name must not start with a dot
return 'Period (.) is not allowed as the first letter in the user name.' return 'Period (.) is not allowed as the first letter in the user name.'
if ' ' in self.username: # a user name must not include a whitespace if ' ' in self.username: # a user name must not include a whitespace
return 'Whitespace is not allowed in the user name.' return 'Whitespace is not allowed in the user name.'
for c in self.username: # a user name must not include special characters, except non-leading periods or underscores for c in self.username: # a user name must not include special characters, except non-leading periods or underscores
if c in string.punctuation and c != '.' and c != '_': if c in string.punctuation and c != '.' and c != '_':
return f'{c} is not allowed in the user name.' return f'{c} is not allowed in the user name.'
if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del', if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del', 'admin']:
'admin']:
return 'You used a restricted word as your user name. Please come up with a better one.' return 'You used a restricted word as your user name. Please come up with a better one.'
if self.contains_chinese():
return 'Chinese characters are not allowed in the user name.'
return 'OK'
class Password:
def __init__(self, password):
self.password = password
def contains_chinese(self):
for char in self.password:
# Check if the character is in the CJK (Chinese, Japanese, Korean) Unicode block
if unicodedata.name(char).startswith('CJK UNIFIED IDEOGRAPH'):
return True
return False
def validate(self):
if len(self.password) < 4:
return 'Password must be at least 4 characters long.'
if ' ' in self.password:
return 'Password cannot contain spaces.'
if self.contains_chinese():
return 'Chinese characters are not allowed in the password.'
return 'OK' return 'OK'
class WarningMessage: class WarningMessage:
def __init__(self, s, type='username'): def __init__(self, s):
self.s = s self.s = s
self.type = type
def __str__(self): def __str__(self):
if self.type == 'username': return UserName(self.s).validate()
return UserName(self.s).validate()
if self.type == 'password':
return Password(self.s).validate()

View File

@ -133,7 +133,10 @@ def reset():
# POST请求用于提交修改后信息 # POST请求用于提交修改后信息
old_password = escape(request.form['old-password']) old_password = escape(request.form['old-password'])
new_password = escape(request.form['new-password']) new_password = escape(request.form['new-password'])
result = change_password(username, old_password, new_password) flag = change_password(username, old_password, new_password) # flag表示是否修改成功
return jsonify(result) if flag:
session['logged_in'] = False
return jsonify({'status':'1'}) # 修改成功
else:
return jsonify({'status':'2'}) # 修改失败

View File

@ -1,31 +0,0 @@
from flask import *
from flask_httpauth import HTTPTokenAuth
from Article import load_freq_history
path_prefix = '/var/www/wordfreq/wordfreq/'
path_prefix = './' # comment this line in deployment
apiService = Blueprint('site',__name__)
auth = HTTPTokenAuth(scheme='Bearer')
tokens = {
"token": "token",
"secret-token": "lanhui" # token, username
}
@auth.verify_token
def verify_token(token):
if token in tokens:
return tokens[token]
@apiService.route('/api/mywords') # HTTPie usage: http -A bearer -a secret-token http://127.0.0.1:5000/api/mywords
@auth.login_required
def show():
username = auth.current_user()
word_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
d = load_freq_history(word_freq_record)
return jsonify(d)

View File

@ -4,18 +4,15 @@
########################################################################### ###########################################################################
from flask import abort, jsonify from flask import abort, jsonify
from markupsafe import escape from markupsafe import escape
from collections import Counter
from Login import * from Login import *
from Article import * from Article import *
import Yaml import Yaml
from user_service import userService from user_service import userService
from account_service import accountService from account_service import accountService
from admin_service import adminService, ADMIN_NAME from admin_service import adminService, ADMIN_NAME
from api_service import apiService
import os import os
from translate import * from translate import *
app = Flask(__name__) app = Flask(__name__)
app.secret_key = os.urandom(32) app.secret_key = os.urandom(32)
@ -23,7 +20,6 @@ app.secret_key = os.urandom(32)
app.register_blueprint(userService) app.register_blueprint(userService)
app.register_blueprint(accountService) app.register_blueprint(accountService)
app.register_blueprint(adminService) app.register_blueprint(adminService)
app.register_blueprint(apiService)
path_prefix = '/var/www/wordfreq/wordfreq/' path_prefix = '/var/www/wordfreq/wordfreq/'
path_prefix = './' # comment this line in deployment path_prefix = './' # comment this line in deployment
@ -59,12 +55,6 @@ def appears_in_test(word, d):
else: else:
return ','.join(d[word]) return ','.join(d[word])
def good_word(word):
return len(word) < len('Pneumonoultramicroscopicsilicovolcanoconiosis') \
and Counter(word).most_common(1)[0][1] <= 4
@app.route("/mark", methods=['GET', 'POST']) @app.route("/mark", methods=['GET', 'POST'])
def mark_word(): def mark_word():
''' '''
@ -106,7 +96,7 @@ def mainpage():
if request.method == 'POST': # when we submit a form if request.method == 'POST': # when we submit a form
content = escape(request.form['content']) content = escape(request.form['content'])
f = WordFreq(content) f = WordFreq(content)
lst = [ t for t in f.get_freq() if good_word(t[0]) ] # only keep normal words lst = f.get_freq()
# save history # save history
d = load_freq_history(path_prefix + 'static/frequency/frequency.p') d = load_freq_history(path_prefix + 'static/frequency/frequency.p')
lst_history = pickle_idea.dict2lst(d) lst_history = pickle_idea.dict2lst(d)
@ -144,8 +134,8 @@ if __name__ == '__main__':
运行程序 运行程序
''' '''
# app.secret_key = os.urandom(16) # app.secret_key = os.urandom(16)
app.run(debug=True, port=5000) # app.run(debug=False, port='6000')
# app.run(debug=True) app.run(debug=True)
# app.run(debug=True, port='6000') # app.run(debug=True, port='6000')
# app.run(host='0.0.0.0', debug=True, port='6000') # app.run(host='0.0.0.0', debug=True, port='6000')
# print(mod5('123')) # print(mod5('123'))

View File

@ -6,7 +6,6 @@
# Purpose: dictionary & pickle as a simple means of database. # Purpose: dictionary & pickle as a simple means of database.
# Task: incorporate the functions into wordfreqCMD.py such that it will also show cumulative frequency. # Task: incorporate the functions into wordfreqCMD.py such that it will also show cumulative frequency.
import os
import pickle import pickle
from datetime import datetime from datetime import datetime
@ -56,13 +55,11 @@ def save_frequency_to_pickle(d, pickle_fname):
f.close() f.close()
def unfamiliar(path,word): def unfamiliar(path,word):
if not os.path.exists(path): f = open(path,"rb")
return None dic = pickle.load(f)
with open(path,"rb") as f: dic[word] += [datetime.now().strftime('%Y%m%d%H%M')]
dic = pickle.load(f) fp = open(path,"wb")
dic[word] += [datetime.now().strftime('%Y%m%d%H%M')] pickle.dump(dic,fp)
with open(path,"wb") as fp:
pickle.dump(dic,fp)
def familiar(path,word): def familiar(path,word):
f = open(path,"rb") f = open(path,"rb")

View File

@ -64,6 +64,7 @@ def load_record(pickle_fname):
def save_frequency_to_pickle(d, pickle_fname): def save_frequency_to_pickle(d, pickle_fname):
f = open(pickle_fname, 'wb') f = open(pickle_fname, 'wb')
exclusion_lst = ['one', 'no', 'has', 'had', 'do', 'that', 'have', 'by', 'not', 'but', 'we', 'this', 'my', 'him', 'so', 'or', 'as', 'are', 'it', 'from', 'with', 'be', 'can', 'for', 'an', 'if', 'who', 'whom', 'whose', 'which', 'the', 'to', 'a', 'of', 'and', 'you', 'i', 'he', 'she', 'they', 'me', 'was', 'were', 'is', 'in', 'at', 'on', 'their', 'his', 'her', 's', 'said', 'all', 'did', 'been', 'w']
d2 = {} d2 = {}
for k in d: for k in d:
if not k in exclusion_lst and not k.isnumeric() and not len(k) < 2: if not k in exclusion_lst and not k.isnumeric() and not len(k) < 2:
@ -72,7 +73,6 @@ def save_frequency_to_pickle(d, pickle_fname):
f.close() f.close()
exclusion_lst = ['one', 'no', 'has', 'had', 'do', 'that', 'have', 'by', 'not', 'but', 'we', 'this', 'my', 'him', 'so', 'or', 'as', 'are', 'it', 'from', 'with', 'be', 'can', 'for', 'an', 'if', 'who', 'whom', 'whose', 'which', 'the', 'to', 'a', 'of', 'and', 'you', 'i', 'he', 'she', 'they', 'me', 'was', 'were', 'is', 'in', 'at', 'on', 'their', 'his', 'her', 's', 'said', 'all', 'did', 'been', 'w']
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -21,19 +21,14 @@ function fillInWord() {
localStorage.setItem('selectedWords', element.value); localStorage.setItem('selectedWords', element.value);
} }
if (document.getElementById("text-content")) { document.getElementById("text-content").addEventListener("click", fillInWord, false);
document.getElementById("text-content").addEventListener("click", fillInWord, false);
}
const sliderValue = document.getElementById("rangeValue"); const sliderValue = document.getElementById("rangeValue");
const inputSlider = document.getElementById("rangeComponent"); const inputSlider = document.getElementById("rangeComponent");
inputSlider.oninput = () => {
if (inputSlider) { let value = inputSlider.value;
inputSlider.oninput = () => { sliderValue.textContent = value + '×';
let value = inputSlider.value; };
sliderValue.textContent = value + '×';
};
}
function onReadClick() { function onReadClick() {
isRead = !isRead; isRead = !isRead;

View File

@ -9,13 +9,11 @@ function cancelBtnHandler() {
} }
function showBtnHandler() { function showBtnHandler() {
if (document.getElementById("text-content")) { document.getElementById("text-content").removeEventListener("click", fillInWord2, false);
document.getElementById("text-content").removeEventListener("click", fillInWord2, false); document.getElementById("text-content").removeEventListener("touchstart", fillInWord2, false);
document.getElementById("text-content").removeEventListener("touchstart", fillInWord2, false); document.getElementById("text-content").addEventListener("click", fillInWord, false);
document.getElementById("text-content").addEventListener("click", fillInWord, false); document.getElementById("text-content").addEventListener("touchstart", fillInWord, false);
document.getElementById("text-content").addEventListener("touchstart", fillInWord, false); highLight();
highLight();
}
} }
function replaceWords(str, word) { function replaceWords(str, word) {
let count = 0; let count = 0;

View File

@ -1,20 +0,0 @@
function containsDigitsLettersSpecialCharacters(s) {
let resultD = 0, resultL = 0, resultS = 0;
// Digit test
'0123456789'.split('').forEach((x) => {
if (s.includes(x))
resultD = 1;
});
// Letter test
resultL = /[a-z]/i.test(s);
// Special charater test
'+-*/,.:;/\[]<>$%&()!?^~'.split('').forEach((x) => {
if (s.includes(x))
resultS = 1;
});
return resultD + resultL + resultS == 3;
}

View File

@ -3,7 +3,6 @@ var Reader = (function() {
let current_position = 0; let current_position = 0;
let original_position = 0; let original_position = 0;
let to_speak = ""; let to_speak = "";
let current_rate = 1; // 添加这一行,设置默认速率为 1
function makeUtterance(str, rate) { function makeUtterance(str, rate) {
let msg = new SpeechSynthesisUtterance(str); let msg = new SpeechSynthesisUtterance(str);
@ -25,24 +24,12 @@ var Reader = (function() {
reader.speak(msg); reader.speak(msg);
} }
function updateRate(rate) {
// 停止当前的朗读
stopRead();
// 更新当前速率
current_rate = rate;
// 重新开始朗读
read(to_speak, current_rate);
}
function stopRead() { function stopRead() {
reader.cancel(); reader.cancel();
} }
return { return {
read: read, read: read,
stopRead: stopRead, stopRead: stopRead
updateRate: updateRate // 添加这一行,将 updateRate 方法暴露出去
}; };
}) (); }) ();

View File

@ -1,9 +1,7 @@
function familiar(theWord) { function familiar(theWord) {
let username = $("#username").text(); let username = $("#username").text();
let word = document.getElementById(`word_${theWord}`).innerText; let word = $("#word_" + theWord).text();
let freq = document.getElementById(`freq_${theWord}`).innerText; let freq = $("#freq_" + theWord).text();
console.log(theWord);
console.log(word);
$.ajax({ $.ajax({
type:"GET", type:"GET",
url:"/" + username + "/" + word + "/familiar", url:"/" + username + "/" + word + "/familiar",
@ -29,10 +27,8 @@ function familiar(theWord) {
function unfamiliar(theWord) { function unfamiliar(theWord) {
let username = $("#username").text(); let username = $("#username").text();
let word = document.getElementById(`word_${theWord}`).innerText; let word = $("#word_" + theWord).text();
let freq = document.getElementById(`freq_${theWord}`).innerText; let freq = $("#freq_" + theWord).text();
console.log(theWord);
console.log(word);
$.ajax({ $.ajax({
type:"GET", type:"GET",
url:"/" + username + "/" + word + "/unfamiliar", url:"/" + username + "/" + word + "/unfamiliar",
@ -60,13 +56,7 @@ function delete_word(theWord) {
removeWord(theWord); removeWord(theWord);
} else { } else {
$("#p_" + theWord).remove(); $("#p_" + theWord).remove();
} }
// remove highlighting for the word
let highlightedWords = document.querySelectorAll('.highlighted');
for (let x of highlightedWords) {
if (x.innerHTML == word)
x.replaceWith(x.innerHTML);
}
} }
}); });
} }
@ -93,9 +83,7 @@ function parseWord(element) {
const word = element const word = element
.querySelector("a.btn.btn-light[role=button]") // 获取当前词频元素的词汇元素 .querySelector("a.btn.btn-light[role=button]") // 获取当前词频元素的词汇元素
.innerText // 获取词汇值; .innerText // 获取词汇值;
let freqId = `freq_${word}`; const freq = Number.parseInt(element.querySelector(`#freq_${word}`).innerText); // 获取词汇的数量
freqId = CSS.escape(freqId); // for fixing bug 580, escape the apostrophe in the word
const freq = Number.parseInt(element.querySelector("#"+freqId).innerText); // 获取词汇的数量
return { return {
word, word,
freq freq
@ -107,14 +95,14 @@ function parseWord(element) {
*/ */
function wordTemplate(word) { function wordTemplate(word) {
// 这个模板应当与 templates/userpage_get.html 中的 <p id='p_${word.word}' class="new-word" > ... </p> 保持一致 // 这个模板应当与 templates/userpage_get.html 中的 <p id='p_${word.word}' class="new-word" > ... </p> 保持一致
return `<p id="p_${word.word}" class="new-word" > return `<p id='p_${word.word}' class="new-word" >
<a id="word_${word.word}" class="btn btn-light" href='http://youdao.com/w/eng/${word.word}/#keyfrom=dict2.index' <a id="word_${word.word}" class="btn btn-light" href='http://youdao.com/w/eng/${word.word}/#keyfrom=dict2.index'
role="button">${word.word}</a> role="button">${word.word}</a>
( <a id="freq_${word.word}" title="${word.word}">${word.freq}</a> ) ( <a id="freq_${word.word}" title="${word.word}">${word.freq}</a> )
<a class="btn btn-success" onclick=familiar("${word.word}") role="button">熟悉</a> <a class="btn btn-success" onclick="familiar('${word.word}')" role="button">熟悉</a>
<a class="btn btn-warning" onclick=unfamiliar("${word.word}") role="button">不熟悉</a> <a class="btn btn-warning" onclick="unfamiliar('${word.word}')" role="button">不熟悉</a>
<a class="btn btn-danger" onclick=delete_word("${word.word}") role="button">删除</a> <a class="btn btn-danger" onclick="delete_word('${word.word}')" role="button">删除</a>
<a class="btn btn-info" onclick=read_word("${word.word}") role="button">朗读</a> <a class="btn btn-info" onclick="read_word('${word.word}')" role="button">朗读</a>
<a class="btn btn-primary" onclick="addNote('{{ word }}'); saveNote('{{ word }}')" role="button">笔记</a> <!-- Modify to call addNote and then saveNote --> <a class="btn btn-primary" onclick="addNote('{{ word }}'); saveNote('{{ word }}')" role="button">笔记</a> <!-- Modify to call addNote and then saveNote -->
<input type="text" id="note_{{ word }}" class="note-input" placeholder="输入笔记内容" style="display:none;" oninput="saveNote('{{ word }}')"> <!-- Added oninput event --> <input type="text" id="note_{{ word }}" class="note-input" placeholder="输入笔记内容" style="display:none;" oninput="saveNote('{{ word }}')"> <!-- Added oninput event -->
</p>`; </p>`;

View File

@ -31,7 +31,7 @@
<p><a href="/login">登录</a> <a href="/signup">注册</a> <a href="/static/usr/instructions.html">使用说明</a></p > <p><a href="/login">登录</a> <a href="/signup">注册</a> <a href="/static/usr/instructions.html">使用说明</a></p >
<p><b> {{ random_ads }}。 <a href="/signup">试试</a>吧!</b></p> <p><b> {{ random_ads }}。 <a href="/signup">试试</a>吧!</b></p>
{% endif %} {% endif %}
<div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span> 篇,Oxford 5000 单词占比 <span class="badge bg-success"> {{ (ratio * 100) | int }}% </span> </div> <div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span> 篇,覆盖 <span class="badge bg-success"> {{ (ratio * 100) | int }}% </span> 的 Oxford5000 单词</div>
<p>粘贴1篇文章 (English only)</p> <p>粘贴1篇文章 (English only)</p>
<form method="post" action="/"> <form method="post" action="/">
<textarea name="content" id="article" rows="10" cols="120"></textarea><br/> <textarea name="content" id="article" rows="10" cols="120"></textarea><br/>

View File

@ -3,7 +3,6 @@
content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes"/> content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=3.0, user-scalable=yes"/>
<link rel="stylesheet" href="static/css/login_service.css"> <link rel="stylesheet" href="static/css/login_service.css">
<script src="static/js/jquery.js"></script> <script src="static/js/jquery.js"></script>
<script src="static/js/password.js"></script>
<script> <script>
function reset() { function reset() {
let old_password = $("#old-password").val(); let old_password = $("#old-password").val();
@ -25,19 +24,15 @@
alert('密码过于简单。(密码长度至少4位)'); alert('密码过于简单。(密码长度至少4位)');
return false; return false;
} }
if (!containsDigitsLettersSpecialCharacters(new_password)) {
alert('密码过于简单。(密码要包括数字,字母,特殊符号)');
return false;
}
$.post("/reset", {'old-password': old_password, 'new-password': new_password}, $.post("/reset", {'old-password': old_password, 'new-password': new_password},
function (response) { function (response) {
console.log(response); if (response.status === '1') {
if ('success' in response) { alert('密码修改成功,请重新登录。');
alert('密码修改成功。'); window.location.href = "/login";
} else if ('error' in response) { } else if (response.status === '2') {
alert(`密码修改失败 ${response.error}`); alert('密码修改失败');
window.location.href = "/reset";
} }
window.location.href = `/${response.username}/userpage`;
} }
) )
return false; return false;
@ -57,4 +52,4 @@
<button class="btn" onclick="window.location.href='/{{ username }}/userpage'">放弃修改</button> <button class="btn" onclick="window.location.href='/{{ username }}/userpage'">放弃修改</button>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -7,7 +7,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE-edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE-edge,chrome=1">
<link href="static/css/slide-unlock.css" rel="stylesheet"> <link href="static/css/slide-unlock.css" rel="stylesheet">
<script src="static/js/password.js"></script>
<script src="static/js/jquery.js"></script> <script src="static/js/jquery.js"></script>
<script src="static/js/jquery.slideunlock.js"></script> <script src="static/js/jquery.slideunlock.js"></script>
<script> <script>
@ -45,10 +44,6 @@
alert('密码过于简单。(密码长度至少4位)'); alert('密码过于简单。(密码长度至少4位)');
return false; return false;
} }
if (!containsDigitsLettersSpecialCharacters(password)) {
alert('密码过于简单。(密码要包括数字,字母,特殊符号)');
return false;
}
is_ok = slider.getIsOk(); is_ok = slider.getIsOk();
if(!is_ok){ if(!is_ok){
alert('没有滑动验证'); alert('没有滑动验证');

View File

@ -84,18 +84,10 @@
</div> </div>
<p><b>阅读文章并回答问题</b></p> <p><b>阅读文章并回答问题</b></p>
<div id="text-content"> <div id="text-content">
<div id="found"> <div id="found">
<div class="alert alert-success" role="alert">According to your word list, your level is <span class="text-decoration-underline" id="user_level">{{ today_article["user_level"] }}</span> and we have chosen an article with a difficulty level of <span class="text-decoration-underline" id="text_level">{{ today_article["text_level"] }}</span> for you. <span class="text-decoration-underline" id="ratio">{{ (today_article["ratio"] * 100) | int }}%</span> of the words in this article are in Oxford Word 5000.</div> <div class="alert alert-success" role="alert">According to your word list, your level is <span class="text-decoration-underline" id="user_level">{{ today_article["user_level"] }}</span> and we have chosen an article with a difficulty level of <span class="text-decoration-underline" id="text_level">{{ today_article["text_level"] }}</span> for you. The Oxford word coverage is <span class="text-decoration-underline" id="ratio">{{ (today_article["ratio"] * 100) | int }}%.</span></div>
<p class="text-muted" id="date">Article added on: {{ today_article["date"] }}</p><br/> <p class="text-muted" id="date">Article added on: {{ today_article["date"] }}</p><br/>
<button onclick="saveArticle()" >标记文章</button>
<select id="saved_articles_dropdown">
<!-- 这里将显示已经保存的文章 -->
<option></option>
</select>
<div class="p-3 mb-2 bg-light text-dark" style="margin: 0 0.5%;"><br/> <div class="p-3 mb-2 bg-light text-dark" style="margin: 0 0.5%;"><br/>
<p class="display-6" id="article_title">{{ today_article["article_title"] }}</p><br/> <p class="display-6" id="article_title">{{ today_article["article_title"] }}</p><br/>
<p class="lead"><font id="article">{{ today_article["article_body"] }}</font></p><br/> <p class="lead"><font id="article">{{ today_article["article_body"] }}</font></p><br/>
@ -179,10 +171,10 @@
<a id="word_{{ word }}" class="btn btn-light" href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index' <a id="word_{{ word }}" class="btn btn-light" href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'
role="button">{{ word }}</a> role="button">{{ word }}</a>
( <a id="freq_{{ word }}" title="{{ word }}">{{ freq }}</a> ) ( <a id="freq_{{ word }}" title="{{ word }}">{{ freq }}</a> )
<a class="btn btn-success" onclick=familiar("{{ word }}") role="button">熟悉</a> <a class="btn btn-success" onclick="familiar('{{ word }}')" role="button">熟悉</a>
<a class="btn btn-warning" onclick=unfamiliar("{{ word }}") role="button">不熟悉</a> <a class="btn btn-warning" onclick="unfamiliar('{{ word }}')" role="button">不熟悉</a>
<a class="btn btn-danger" onclick=delete_word("{{ word }}") role="button">删除</a> <a class="btn btn-danger" onclick="delete_word('{{ word }}')" role="button">删除</a>
<a class="btn btn-info" onclick=read_word("{{ word }}") role="button">朗读</a> <a class="btn btn-info" onclick="read_word('{{ word }}')" role="button">朗读</a>
<a class="btn btn-primary" onclick="addNote('{{ word }}'); saveNote('{{ word }}')" role="button">笔记</a> <!-- Modify to call addNote and then saveNote --> <a class="btn btn-primary" onclick="addNote('{{ word }}'); saveNote('{{ word }}')" role="button">笔记</a> <!-- Modify to call addNote and then saveNote -->
<input type="text" id="note_{{ word }}" class="note-input" placeholder="输入笔记内容" style="display:none;" oninput="saveNote('{{ word }}')"> <!-- Added oninput event --> <input type="text" id="note_{{ word }}" class="note-input" placeholder="输入笔记内容" style="display:none;" oninput="saveNote('{{ word }}')"> <!-- Added oninput event -->
</p> </p>
@ -284,7 +276,6 @@
update(data['today_article']); update(data['today_article']);
check_pre(data['visited_articles']); check_pre(data['visited_articles']);
check_next(data['result_of_generate_article']); check_next(data['result_of_generate_article']);
toggleHighlighting();
} }
} }
}); });
@ -301,7 +292,6 @@
e.style.display = 'none'; e.style.display = 'none';
update(data['today_article']); update(data['today_article']);
check_pre(data['visited_articles']); check_pre(data['visited_articles']);
toggleHighlighting();
} }
} }
}); });
@ -344,90 +334,6 @@
$('#read_all').show(); $('#read_all').show();
} }
} }
function saveArticle() {
const article = {
user_level: document.getElementById('user_level').innerText,
text_level: document.getElementById('text_level').innerText,
date: document.getElementById('date').innerText.replace('Article added on: ', ''),
article_title: document.getElementById('article_title').innerText,
article_body: document.getElementById('article').innerText,
source: document.getElementById('source').innerText,
question: document.getElementById('question').innerText,
answer: document.getElementById('answer').innerText
};
const articleJSON = JSON.stringify(article);
const articleTitle = article.article_title;
const savedArticlesDropdown = document.getElementById('saved_articles_dropdown');
var option = document.createElement('option');
option.text = articleTitle;
option.value = articleJSON; // 存储序列化的JSON字符串
option.title = article.article_title;
savedArticlesDropdown.appendChild(option);
localStorage.setItem(articleTitle, articleJSON); // 以文章标题为键序列化的JSON字符串为值存储
}
function loadSelectedArticle() {
const selectedOption = document.getElementById('saved_articles_dropdown');
const selectedTitle = selectedOption.options[selectedOption.selectedIndex].text;
const articleJSON = localStorage.getItem(selectedTitle);
if (articleJSON) {
const today_article = JSON.parse(articleJSON); // 解析JSON字符串为对象
update(today_article); // 使用解析出的对象更新页面
}
}
window.onload = function() {
const savedArticlesDropdown = document.getElementById('saved_articles_dropdown');
savedArticlesDropdown.addEventListener('change', loadSelectedArticle);
// 先清空dropdown以防有多余的选项或重新加载页面时出现重复
savedArticlesDropdown.innerHTML = '';
// 获取localStorage中最后一个最新的键值对
let latestKey, latestValue;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
if (!latestKey) { // 第一次迭代时设置最新文章
latestKey = key;
latestValue = value;
}
}
// 首先添加最新保存的文章到下拉菜单
if (latestKey && latestValue) {
var latestOption = document.createElement('option');
latestOption.text = latestKey;
latestOption.value = latestValue;
latestOption.title = latestValue;
savedArticlesDropdown.appendChild(latestOption);
}
// 接着遍历其余文章并添加到下拉菜单
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
// 确保不重复添加最新文章
if (key !== latestKey && key !== 'selectedWords') {
var option = document.createElement('option');
option.text = key;
option.value = value;
option.title = value;
savedArticlesDropdown.appendChild(option);
}
}
savedArticlesDropdown.selectedIndex = -1;
}
document.getElementById('rangeComponent').addEventListener('input', function() {
var rate = this.value;
Reader.updateRate(rate);
});
</script> </script>
</body> </body>
<style> <style>

View File

@ -1,88 +0,0 @@
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 对用户名不能为中文进行测试
def test_register_username_with_chinese(driver, URL):
try:
driver.get(URL + "/signup")
# 等待用户名输入框出现
username_elem = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'username'))
)
username_elem.send_keys("测试用户") # 输入中文用户名
# 等待密码输入框出现
password_elem = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'password'))
)
password_elem.send_keys("validPassword123") # 输入有效密码
# 等待确认密码输入框出现
password2_elem = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'password2'))
)
password2_elem.send_keys("validPassword123") # 输入有效确认密码
# 等待注册按钮出现并点击
signup_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, '//button[@onclick="signup()"]'))
)
signup_button.click()
# 等待警告框出现并接受
WebDriverWait(driver, 10).until(EC.alert_is_present())
alert = driver.switch_to.alert
alert_text = alert.text
print(f"警告文本: {alert_text}")
assert alert_text == "Chinese characters are not allowed in the user name." # 根据实际的警告文本进行断言
alert.accept()
except Exception as e:
print(f"发生错误: {e}")
raise
# 对注册时密码不能是中文进行测试
def test_register_password_with_chinese(driver, URL):
try:
driver.get(URL + "/signup")
# 等待用户名输入框出现
username_elem = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'username'))
)
username_elem.send_keys("validUsername123") # 输入有效用户名
# 等待密码输入框出现
password_elem = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'password'))
)
password_elem.send_keys("测试密码") # 输入中文密码
# 等待确认密码输入框出现
password2_elem = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, 'password2'))
)
password2_elem.send_keys("测试密码") # 输入中文确认密码
# 等待注册按钮出现并点击
signup_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, '//button[@onclick="signup()"]'))
)
signup_button.click()
# 等待警告框出现并接受
WebDriverWait(driver, 10).until(EC.alert_is_present())
alert = driver.switch_to.alert
alert_text = alert.text
print(f"警告文本: {alert_text}")
assert alert_text == "Chinese characters are not allowed in the password." # 根据实际的警告文本进行断言
alert.accept()
except Exception as e:
print(f"发生错误: {e}")
raise

View File

@ -1,45 +0,0 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import logging
from helper import signup
def login(driver, home, uname, password):
driver.get(home)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, '登录'))).click()
driver.find_element(By.ID, 'username').send_keys(uname)
driver.find_element(By.ID, 'password').send_keys(password)
driver.find_element(By.XPATH, '//button[text()="登录"]').click()
WebDriverWait(driver, 10).until(EC.title_is(f"EnglishPal Study Room for {uname}"))
def logout(driver):
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, '退出'))).click()
# 标记文章
def collect_article(driver):
driver.find_element(By.XPATH, '//button[text()="标记文章"]').click()
def test_collect_article(driver, URL):
try:
username, password = signup(URL, driver)
title = driver.find_element(By.ID, 'article_title').text
article = driver.find_element(By.ID, 'article').text
collect_article(driver)
collected_title = driver.execute_script('return localStorage.getItem("articleTitle");')
assert title == collected_title, "Unable to add the article to your collection."
# 退出登录
logout(driver)
# 再次登录并检查收藏状态
login(driver, URL, username, password)
rechecked_title = driver.execute_script('return localStorage.getItem("articleTitle");')
assert title == rechecked_title, "Collected article not found after re-login."
except Exception as e:
# 输出异常信息
logging.error(e)
finally:
driver.quit()

View File

@ -1,43 +0,0 @@
import time
import pytest
import uuid
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import UnexpectedAlertPresentException, NoAlertPresentException, NoSuchElementException, \
TimeoutException
from conftest import URL
driver = webdriver.Chrome()
def test_bug555():
try:
driver.maximize_window()
base_url = "http://127.0.0.1:5000"
driver.get(base_url)
article = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'article')))
perform_actions_on_article(driver, article)
next_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'load_next_article')))
next_button.click()
print("Clicked next article button.")
prev_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, 'load_pre_article')))
prev_button.click()
print("Clicked previous article button.")
except (TimeoutException, NoSuchElementException) as e:
print(f"An error occurred: {e}")
finally:
driver.quit()
print("Driver closed.")
def perform_actions_on_article(driver, article):
actions = ActionChains(driver)
actions.move_to_element(article)
actions.click_and_hold()
actions.move_by_offset(450, 200)
actions.release()
actions.perform()
print("Performed actions on article.")

View File

@ -0,0 +1,85 @@
''' Contributed by Lin Junhong et al. 2023-06.'''
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import UnexpectedAlertPresentException, NoAlertPresentException
import random, time
import string
# 初始化webdriver
# driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.CHROME)
# driver.implicitly_wait(10)
driver = webdriver.Chrome("C:\\Users\\12993\AppData\Local\Programs\Python\Python38\\chromedriver.exe")
def test_next_article():
try:
driver.get("http://118.25.96.118:90")
assert 'English Pal -' in driver.page_source
# login
elem = driver.find_element_by_link_text('登录')
elem.click()
uname = 'abcdefg'
password = 'abcdefg'
elem = driver.find_element_by_id('username')
elem.send_keys(uname)
elem = driver.find_element_by_id('password')
elem.send_keys(password)
elem = driver.find_element_by_xpath('/html/body/div/button') # 找到登录按钮
elem.click()
time.sleep(0.5)
assert 'EnglishPal Study Room for ' + uname in driver.title
for i in range(50):
time.sleep(0.1)
# 找到固定按钮
elem = driver.find_element_by_xpath('//*[@id="load_next_article"]')
elem.click()
except Exception as e:
print(e)
def test_local_next_article():
try:
driver.get("http://127.0.0.1:5000")
assert 'English Pal -' in driver.page_source
# login
elem = driver.find_element_by_link_text('注册')
elem.click()
uname = 'abcdefg'
password = 'abcdefg'
elem = driver.find_element_by_id('username')
elem.send_keys(uname)
elem = driver.find_element_by_id('password')
elem.send_keys(password)
elem = driver.find_element_by_id('password2')
elem.send_keys(password)
time.sleep(0.5)
elem = driver.find_element_by_class_name('btn') # 找到提交按钮
elem.click()
time.sleep(0.5)
try:
WebDriverWait(driver, 1).until(EC.alert_is_present())
driver.switch_to.alert.accept()
except (UnexpectedAlertPresentException, NoAlertPresentException):
pass
time.sleep(0.5)
assert 'EnglishPal Study Room for ' + uname in driver.title
for i in range(50):
time.sleep(0.1)
# 找到固定按钮
elem = driver.find_element_by_xpath('//*[@id="load_next_article"]')
elem.click()
except Exception as e:
print(e)

View File

@ -178,17 +178,14 @@ def user_mark_word(username):
d = load_freq_history(user_freq_record) d = load_freq_history(user_freq_record)
lst_history = pickle_idea2.dict2lst(d) lst_history = pickle_idea2.dict2lst(d)
lst = [] lst = []
lst2 = []
for word in request.form.getlist('marked'): for word in request.form.getlist('marked'):
if not word in pickle_idea2.exclusion_lst and len(word) > 2: lst.append((word, [get_time()]))
lst.append((word, [get_time()]))
lst2.append(word)
d = pickle_idea2.merge_frequency(lst, lst_history) d = pickle_idea2.merge_frequency(lst, lst_history)
if len(lst_history) > 999: if len(lst_history) > 999:
flash('You have way too many words in your difficult-words book. Delete some first.') flash('You have way too many words in your difficult-words book. Delete some first.')
else: else:
pickle_idea2.save_frequency_to_pickle(d, user_freq_record) pickle_idea2.save_frequency_to_pickle(d, user_freq_record)
flash('Added %s.' % ', '.join(lst2)) flash('Added %s.' % (', '.join(request.form.getlist('marked'))))
return redirect(url_for('user_bp.userpage', username=username)) return redirect(url_for('user_bp.userpage', username=username))
else: else:
return 'Under construction' return 'Under construction'

View File

@ -66,7 +66,7 @@ def file2str(fname):#文件转字符
def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。 def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。
special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|,。?!¥……()、《》【】:;·' # 把里面的字符都去掉 special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|,。?!¥……()、《》:;·' # 把里面的字符都去掉
s = html.unescape(s) # 将HTML实体转换为对应的字符比如<会被识别为小于号 s = html.unescape(s) # 将HTML实体转换为对应的字符比如<会被识别为小于号
for c in special_characters: for c in special_characters:
s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况 s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况

View File

@ -6,4 +6,3 @@ snowballstemmer==2.2.0
Werkzeug==2.2.2 Werkzeug==2.2.2
requests requests
pytest~=8.1.1 pytest~=8.1.1
Flask-HTTPAuth==4.4.0