1
0
Fork 0

Compare commits

..

4 Commits

17 changed files with 481 additions and 267 deletions

View File

@ -1,8 +1,14 @@
from WordFreq import WordFreq
from wordfreqCMD import youdao_link, sort_in_descending_order
from UseSqlite import InsertQuery, RecordQuery
import pickle_idea, pickle_idea2
import os import os
import random import random, glob
import pickle_idea import hashlib
from datetime import datetime
from flask import Flask, request, redirect, render_template, url_for, session, abort, flash, get_flashed_messages
from difficulty import get_difficulty_level_for_user, text_difficulty_level, user_difficulty_level from difficulty import get_difficulty_level_for_user, text_difficulty_level, user_difficulty_level
from UseSqlite import RecordQuery
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
@ -27,26 +33,19 @@ def get_article_body(s):
def get_today_article(user_word_list, visited_articles): def get_today_article(user_word_list, visited_articles):
'''
根据用户的单词列表和阅读过的文章返回需要的文章的全部信息
'''
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db') rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
if visited_articles is None: if visited_articles is None:
visited_articles = { visited_articles = {
"index": 0, # 为 article_ids 的索引 "index" : 0, # 为 article_ids 的索引
"article_ids": [] # 之前显示文章的id列表越后越新 "article_ids": [] # 之前显示文章的id列表越后越新
} }
if visited_articles["index"] > len(visited_articles["article_ids"])-1: # 生成新的文章,因此查找所有的文章 if visited_articles["index"] > len(visited_articles["article_ids"])-1: # 生成新的文章,因此查找所有的文章
rq.instructions("SELECT * FROM article") rq.instructions("SELECT * FROM article")
else: # 生成阅读过的文章,因此查询指定 article_id 的文章 else: # 生成阅读过的文章,因此查询指定 article_id 的文章
# 可能因为直接刷新页面导致直接去查询了'null',因此当刷新的页面的时候,需要直接进行“上一篇”操作 if visited_articles["article_ids"][visited_articles["index"]] == 'null': # 可能因为直接刷新页面导致直接去查询了'null',因此当刷新的页面的时候,需要直接进行“上一篇”操作
if visited_articles["article_ids"][visited_articles["index"]] == 'null':
visited_articles["index"] -= 1 visited_articles["index"] -= 1
visited_articles["article_ids"].pop() visited_articles["article_ids"].pop()
rq.instructions( rq.instructions('SELECT * FROM article WHERE article_id=%d' % (visited_articles["article_ids"][visited_articles["index"]]))
f'SELECT * FROM article WHERE article_id='
f'{visited_articles["article_ids"][visited_articles["index"]]}'
)
rq.do() rq.do()
result = rq.get_results() result = rq.get_results()
random.shuffle(result) random.shuffle(result)
@ -59,23 +58,19 @@ def get_today_article(user_word_list, visited_articles):
d = None d = None
result_of_generate_article = "not found" result_of_generate_article = "not found"
d_user = load_freq_history(user_word_list) d_user = load_freq_history(user_word_list)
# 更多的考虑,因为用户的行为是动态的。应考虑时间因素。 user_level = user_difficulty_level(d_user, d3) # more consideration as user's behaviour is dynamic. Time factor should be considered.
user_level = user_difficulty_level(d_user, d3)
text_level = 0 text_level = 0
if visited_articles["index"] > len(visited_articles["article_ids"])-1: # 生成新的文章 if visited_articles["index"] > len(visited_articles["article_ids"])-1: # 生成新的文章
amount_of_visited_articles = len(visited_articles["article_ids"]) amount_of_visited_articles = len(visited_articles["article_ids"])
amount_of_existing_articles = len(result) amount_of_existing_articles = result.__len__()
# 如果当前阅读过的文章的数量 == 存在的文章的数量,即所有的书本都阅读过了 if amount_of_visited_articles == amount_of_existing_articles: # 如果当前阅读过的文章的数量 == 存在的文章的数量,即所有的书本都阅读过了
if amount_of_visited_articles == amount_of_existing_articles:
result_of_generate_article = "had read all articles" result_of_generate_article = "had read all articles"
else: else:
for k in range(3): # 最多尝试3次 for k in range(3): # 最多尝试3次
for reading in result: for reading in result:
text_level = text_difficulty_level(reading['text'], d3) text_level = text_difficulty_level(reading['text'], d3)
# 从高斯分布中得出的平均值为 0.8,站位偏差为 1 的数字 factor = random.gauss(0.8, 0.1) # a number drawn from Gaussian distribution with a mean of 0.8 and a stand deviation of 1
factor = random.gauss(0.8, 0.1) if reading['article_id'] not in visited_articles["article_ids"] and within_range(text_level, user_level, (8.0 - user_level) * factor): # 新的文章之前没有出现过且符合一定范围的水平
# 新的文章之前没有出现过且符合一定范围的水平
if reading['article_id'] not in visited_articles["article_ids"] and within_range(text_level, user_level, (8.0 - user_level) * factor):
d = reading d = reading
visited_articles["article_ids"].append(d['article_id']) # 列表添加新的文章id下面进行 visited_articles["article_ids"].append(d['article_id']) # 列表添加新的文章id下面进行
result_of_generate_article = "found" result_of_generate_article = "found"
@ -92,8 +87,8 @@ def get_today_article(user_word_list, visited_articles):
today_article = None today_article = None
if d: if d:
today_article = { today_article = {
"user_level": f'{user_level:4.2f}', "user_level": '%4.2f' % user_level,
"text_level": f'{text_level:4.2f}', "text_level": '%4.2f' % text_level,
"date": d['date'], "date": d['date'],
"article_title": get_article_title(d['text']), "article_title": get_article_title(d['text']),
"article_body": get_article_body(d['text']), "article_body": get_article_body(d['text']),

View File

@ -1,6 +1,7 @@
import hashlib import hashlib
import string import string
from datetime import datetime, timedelta from datetime import datetime, timedelta
from UseSqlite import InsertQuery, RecordQuery
def md5(s): def md5(s):
''' '''
@ -11,13 +12,14 @@ 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()
from app.model.user import get_user_by_username, insert_user, update_password_by_username # 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
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): def verify_pass(newpass,oldpass):
if newpass==oldpass: if(newpass==oldpass):
return True return True
@ -61,7 +63,8 @@ def get_expiry_date(username):
user = get_user_by_username(username) user = get_user_by_username(username)
if user is None: if user is None:
return '20191024' return '20191024'
return user.expiry_date else:
return user.expiry_date
class UserName: class UserName:
def __init__(self, username): def __init__(self, username):
@ -74,7 +77,6 @@ class UserName:
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.'
@ -89,4 +91,5 @@ class WarningMessage:
self.s = s self.s = s
def __str__(self): def __str__(self):
return UserName(self.s).validate() return UserName(self.s).validate()

View File

@ -2,9 +2,9 @@
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net> # Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
# Written permission must be obtained from the author for commercial uses. # Written permission must be obtained from the author for commercial uses.
########################################################################### ###########################################################################
import string
from wordfreqCMD import remove_punctuation, freq, sort_in_descending_order
from wordfreqCMD import remove_punctuation, freq, sort_in_descending_order
import string
class WordFreq: class WordFreq:
def __init__(self, s): def __init__(self, s):
@ -17,7 +17,7 @@ class WordFreq:
if len(word) > 0 and word[0] in string.ascii_letters: if len(word) > 0 and word[0] in string.ascii_letters:
lst.append(t) lst.append(t)
return sort_in_descending_order(lst) return sort_in_descending_order(lst)
if __name__ == '__main__': if __name__ == '__main__':
f = WordFreq('BANANA; Banana, apple ORANGE Banana banana.') f = WordFreq('BANANA; Banana, apple ORANGE Banana banana.')

View File

@ -6,6 +6,7 @@ Yaml.py
./layout/partial/footer.html ./layout/partial/footer.html
''' '''
import yaml as YAML import yaml as YAML
import os
path_prefix = './' # comment this line in deployment path_prefix = './' # comment this line in deployment

View File

@ -1,44 +1,46 @@
from flask import Blueprint, request, render_template, jsonify,session,redirect, url_for,escape from flask import *
from Login import check_username_availability, verify_user, add_user,get_expiry_date, change_password, WarningMessage from Login import check_username_availability, verify_user, add_user, get_expiry_date, change_password, WarningMessage
# 初始化蓝图 # 初始化蓝图
accountService = Blueprint("accountService", __name__) accountService = Blueprint("accountService", __name__)
### sign-up, login, logout, reset### ### Sign-up, login, logout ###
@accountService.route("/signup", methods=['GET', 'POST']) @accountService.route("/signup", methods=['GET', 'POST'])
def signup(): def signup():
''' '''
注册 注册
:return: 根据注册是否成功返回不同界面 :return: 根据注册是否成功返回不同界面
''' '''
# GET方法直接返回注册页面
if request.method == 'GET': if request.method == 'GET':
# GET方法直接返回注册页面
return render_template('signup.html') return render_template('signup.html')
elif request.method == 'POST':
# POST方法需判断是否注册成功再根据结果返回不同的内容 # POST方法需判断是否注册成功再根据结果返回不同的内容
username = escape(request.form['username']) username = escape(request.form['username'])
password = escape(request.form['password']) password = escape(request.form['password'])
#! 添加如下代码为了过滤注册时的非法字符
warn = WarningMessage(username) #! 添加如下代码为了过滤注册时的非法字符
if str(warn) != 'OK': warn = WarningMessage(username)
return jsonify({'status': '3', 'warn': str(warn)}) if str(warn) != 'OK':
available = check_username_availability(username) return jsonify({'status': '3', 'warn': str(warn)})
# 用户名不可用
if not available: available = check_username_availability(username)
return jsonify({'status': '0'}) if not available: # 用户名不可用
# 用户名可用,添加账户信息 return jsonify({'status': '0'})
add_user(username, password) else: # 添加账户信息
verified = verify_user(username, password) add_user(username, password)
# 注册成功写入session verified = verify_user(username, password)
if verified: if verified:
session['logged_in'] = True # 写入session
session[username] = username session['logged_in'] = True
session['username'] = username session[username] = username
session['expiry_date'] = get_expiry_date(username) session['username'] = username
session['visited_articles'] = None session['expiry_date'] = get_expiry_date(username)
return jsonify({'status': '2'}) session['visited_articles'] = None
# 验证失败 return jsonify({'status': '2'})
return jsonify({'status': '1'}) else:
return jsonify({'status': '1'})
@ -48,25 +50,26 @@ def login():
登录 登录
:return: 根据登录是否成功返回不同页面 :return: 根据登录是否成功返回不同页面
''' '''
# GET方法直接返回登录页面
if request.method == 'GET': if request.method == 'GET':
# GET请求
return render_template('login.html') return render_template('login.html')
elif request.method == 'POST':
# POST方法用于判断登录是否成功检查数据库并验证用户再根据结果返回不同的内容 # POST方法用于判断登录是否成功
username = escape(request.form['username']) # check database and verify user
password = escape(request.form['password']) username = escape(request.form['username'])
verified = verify_user(username, password) password = escape(request.form['password'])
# 登录成功写入session verified = verify_user(username, password)
if verified: if verified:
session['logged_in'] = True # 登录成功写入session
session[username] = username session['logged_in'] = True
session['username'] = username session[username] = username
user_expiry_date = get_expiry_date(username) session['username'] = username
session['expiry_date'] = user_expiry_date user_expiry_date = get_expiry_date(username)
session['visited_articles'] = None session['expiry_date'] = user_expiry_date
return jsonify({'status': '1'}) session['visited_articles'] = None
#验证失败 return jsonify({'status': '1'})
return jsonify({'status': '0'}) else:
return jsonify({'status': '0'})
@accountService.route("/logout", methods=['GET', 'POST']) @accountService.route("/logout", methods=['GET', 'POST'])
@ -92,18 +95,16 @@ def reset():
username = session['username'] username = session['username']
if username == '': if username == '':
return redirect('/login') return redirect('/login')
# GET请求返回修改密码页面
if request.method == 'GET': if request.method == 'GET':
# GET请求返回修改密码页面
return render_template('reset.html', username=session['username'], state='wait') return render_template('reset.html', username=session['username'], state='wait')
else:
# 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'])
flag = change_password(username, old_password, new_password) # flag表示是否修改成功 flag = change_password(username, old_password, new_password) # flag表示是否修改成功
# 修改成功 if flag:
if flag: session['logged_in'] = False
session['logged_in'] = False return jsonify({'status':'1'}) # 修改成功
return jsonify({'status':'1'}) else:
# 修改失败 return jsonify({'status':'2'}) # 修改失败
return jsonify({'status':'2'})

View File

@ -1,17 +1,17 @@
# System Library # System Library
from flask import Blueprint, session, render_template, request, flash from flask import *
# Personal library # Personal library
from Yaml import yml from Yaml import yml
from model.user import get_users, update_password_by_username from model.user import *
from model.user import update_expiry_time_by_username, get_user_by_username from model.article import *
from model.article import get_number_of_articles, get_page_articles
from model.article import delete_article_by_id, add_article
ADMIN_NAME = "lanhui" # unique admin name ADMIN_NAME = "lanhui" # unique admin name
_cur_page = 1 # current article page _cur_page = 1 # current article page
_page_size = 5 # article sizes per page _page_size = 5 # article sizes per page
adminService = Blueprint("admin_service", __name__) adminService = Blueprint("admin_service", __name__)
def check_is_admin(): def check_is_admin():
# 未登录,跳转到未登录界面 # 未登录,跳转到未登录界面
if not session.get("logged_in"): if not session.get("logged_in"):
@ -26,10 +26,6 @@ def check_is_admin():
@adminService.route("/admin", methods=["GET"]) @adminService.route("/admin", methods=["GET"])
def admin(): def admin():
'''
判断是否是管理员登录
return不同页面
'''
is_admin = check_is_admin() is_admin = check_is_admin()
if is_admin != "pass": if is_admin != "pass":
return is_admin return is_admin
@ -41,11 +37,6 @@ def admin():
@adminService.route("/admin/article", methods=["GET", "POST"]) @adminService.route("/admin/article", methods=["GET", "POST"])
def article(): def article():
'''
管理文章
可添加文章删除文章
return:不同页面
'''
global _cur_page, _page_size global _cur_page, _page_size
is_admin = check_is_admin() is_admin = check_is_admin()
@ -58,15 +49,16 @@ def article():
max(1, int(request.args.get("size", 5))), _article_number max(1, int(request.args.get("size", 5))), _article_number
) # 最小的size是1 ) # 最小的size是1
_cur_page = min( _cur_page = min(
max(1, int(request.args.get("page", 1))), max(1, int(request.args.get("page", 1))), _article_number // _page_size + (_article_number % _page_size > 0)
_article_number // _page_size + (_article_number % _page_size > 0)
) # 最小的page是1 ) # 最小的page是1
except ValueError: except ValueError:
return "page parmas must be int!" return "page parmas must be int!"
_articles = get_page_articles(_cur_page, _page_size) _articles = get_page_articles(_cur_page, _page_size)
for _article in _articles: # 获取每篇文章的title for article in _articles: # 获取每篇文章的title
_article.title = _article.text.split("\n")[0] article.title = article.text.split("\n")[0]
_article.content = '<br/>'.join(_article.text.split("\n")[1:]) article.content = '<br/>'.join(article.text.split("\n")[1:])
context = { context = {
"article_number": _article_number, "article_number": _article_number,
"text_list": _articles, "text_list": _articles,
@ -80,19 +72,19 @@ def article():
context["article_number"] = article_len context["article_number"] = article_len
context["text_list"] = get_page_articles(_cur_page, _page_size) context["text_list"] = get_page_articles(_cur_page, _page_size)
_articles = get_page_articles(_cur_page, _page_size) _articles = get_page_articles(_cur_page, _page_size)
for _article in _articles: # 获取每篇文章的title for article in _articles: # 获取每篇文章的title
_article.title = _article.text.split("\n")[0] article.title = article.text.split("\n")[0]
context["text_list"] = _articles context["text_list"] = _articles
if request.method == "GET": if request.method == "GET":
try: try:
delete_id = int(request.args.get("delete_id", 0)) delete_id = int(request.args.get("delete_id", 0))
except ValueError: except:
return "Delete article ID must be int!" return "Delete article ID must be int!"
if delete_id: # delete article if delete_id: # delete article
delete_article_by_id(delete_id) delete_article_by_id(delete_id)
_update_context() _update_context()
else: elif request.method == "POST":
data = request.form data = request.form
content = data.get("content", "") content = data.get("content", "")
source = data.get("source", "") source = data.get("source", "")
@ -110,21 +102,17 @@ def article():
@adminService.route("/admin/user", methods=["GET", "POST"]) @adminService.route("/admin/user", methods=["GET", "POST"])
def user(): def user():
'''
用户管理
可修改用户密码过期日期
return不同页面
'''
is_admin = check_is_admin() is_admin = check_is_admin()
if is_admin != "pass": if is_admin != "pass":
return is_admin return is_admin
context = { context = {
"user_list": get_users(), "user_list": get_users(),
"username": session.get("username"), "username": session.get("username"),
} }
if request.method == "POST": if request.method == "POST":
data = request.form data = request.form
username = data.get("username", "") username = data.get("username","")
new_password = data.get("new_password", "") new_password = data.get("new_password", "")
expiry_time = data.get("expiry_time", "") expiry_time = data.get("expiry_time", "")
if username: if username:
@ -139,9 +127,6 @@ def user():
@adminService.route("/admin/expiry", methods=["GET"]) @adminService.route("/admin/expiry", methods=["GET"])
def user_expiry_time(): def user_expiry_time():
'''
返回用户的过期日期
'''
is_admin = check_is_admin() is_admin = check_is_admin()
if is_admin != "pass": if is_admin != "pass":
return is_admin return is_admin
@ -150,7 +135,8 @@ def user_expiry_time():
if not username: if not username:
return "Username can't be empty." return "Username can't be empty."
existed_user = get_user_by_username(username) user = get_user_by_username(username)
if not existed_user: if not user:
return "User does not exist." return "User does not exist."
return existed_user.expiry_date
return user.expiry_date

View File

@ -6,14 +6,15 @@
# Purpose: compute difficulty level of a English text # Purpose: compute difficulty level of a English text
import pickle import pickle
import snowballstemmer import math
from wordfreqCMD import remove_punctuation, freq, sort_in_descending_order, sort_in_ascending_order from wordfreqCMD import remove_punctuation, freq, sort_in_descending_order, sort_in_ascending_order
import snowballstemmer
def load_record(pickle_fname): def load_record(pickle_fname):
with open(pickle_fname, 'rb') as f: f = open(pickle_fname, 'rb')
d = pickle.load(f) d = pickle.load(f)
f.close()
return d return d
@ -52,7 +53,9 @@ def get_difficulty_level_for_user(d1, d2):
stemmer = snowballstemmer.stemmer('english') stemmer = snowballstemmer.stemmer('english')
for k in d1: # 用户的词 for k in d1: # 用户的词
if k not in d2: # 如果用户的词以原型的形式不存在于词库d2中 if k in d2: # 如果用户的词以原型的形式存在于词库d2中
continue # 无需评级,跳过
else:
stem = stemmer.stemWord(k) stem = stemmer.stemWord(k)
if stem in d2: # 如果用户的词的词根存在于词库d2的词根库中 if stem in d2: # 如果用户的词的词根存在于词库d2的词根库中
d2[k] = d2[stem] # 按照词根进行评级 d2[k] = d2[stem] # 按照词根进行评级
@ -85,9 +88,6 @@ def revert_dict(d):
def user_difficulty_level(d_user, d): def user_difficulty_level(d_user, d):
'''
得到用户词汇的难度水平
'''
d_user2 = revert_dict(d_user) # key is date, and value is a list of words added in that date d_user2 = revert_dict(d_user) # key is date, and value is a list of words added in that date
count = 0 count = 0
geometric = 1 geometric = 1
@ -242,7 +242,10 @@ We need — for our farmers, our manufacturers, for, frankly, unions and non-uni
''' '''
# f = open('bbc-fulltext/bbc/entertainment/001.txt') # f = open('bbc-fulltext/bbc/entertainment/001.txt')
with open('wordlist.txt', encoding='utf-8') as f: f = open('wordlist.txt')
s = f.read() s = f.read()
f.close()
print(text_difficulty_level(s, d3))
print(text_difficulty_level(s, d3))

View File

@ -1,24 +1,17 @@
#! /usr/bin/python3 #! /usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import random
########################################################################### ###########################################################################
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net> # Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
# Written permission must be obtained from the author for commercial uses. # Written permission must be obtained from the author for commercial uses.
########################################################################### ###########################################################################
import glob from flask import escape
from flask import Flask, request, redirect, render_template, url_for, escape from Login import *
from WordFreq import WordFreq from Article import *
import pickle_idea
from wordfreqCMD import sort_in_descending_order
from Article import load_freq_history, total_number_of_essays
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
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'lunch.time!' app.secret_key = 'lunch.time!'
@ -58,7 +51,8 @@ def appears_in_test(word, d):
''' '''
if not word in d: if not word in d:
return '' return ''
return ','.join(d[word]) else:
return ','.join(d[word])
@app.route("/mark", methods=['GET', 'POST']) @app.route("/mark", methods=['GET', 'POST'])
@ -76,8 +70,8 @@ def mark_word():
d = pickle_idea.merge_frequency(lst, lst_history) d = pickle_idea.merge_frequency(lst, lst_history)
pickle_idea.save_frequency_to_pickle(d, path_prefix + 'static/frequency/frequency.p') pickle_idea.save_frequency_to_pickle(d, path_prefix + 'static/frequency/frequency.p')
return redirect(url_for('mainpage')) return redirect(url_for('mainpage'))
# 不回应GET请求 else: # 不回应GET请求
return 'Under construction' return 'Under construction'
@app.route("/", methods=['GET', 'POST']) @app.route("/", methods=['GET', 'POST'])
@ -97,7 +91,7 @@ def mainpage():
pickle_idea.save_frequency_to_pickle(d, path_prefix + 'static/frequency/frequency.p') pickle_idea.save_frequency_to_pickle(d, path_prefix + 'static/frequency/frequency.p')
return render_template('mainpage_post.html', lst=lst, yml=Yaml.yml) return render_template('mainpage_post.html', lst=lst, yml=Yaml.yml)
if request.method == 'GET': # when we load a html page elif request.method == 'GET': # when we load a html page
random_ads = get_random_ads() random_ads = get_random_ads()
number_of_essays = total_number_of_essays() number_of_essays = total_number_of_essays()
d = load_freq_history(path_prefix + 'static/frequency/frequency.p') d = load_freq_history(path_prefix + 'static/frequency/frequency.p')

View File

@ -19,15 +19,15 @@ def lst2dict(lst, d):
for x in lst: for x in lst:
word = x[0] word = x[0]
freq = x[1] freq = x[1]
if word not in d: if not word in d:
d[word] = freq d[word] = freq
else: else:
d[word] += freq d[word] += freq
def dict2lst(d): def dict2lst(d):
return list(d.items()) # a list of (key, value) pairs return list(d.items()) # a list of (key, value) pairs
def merge_frequency(lst1, lst2): def merge_frequency(lst1, lst2):
d = {} d = {}
@ -37,27 +37,29 @@ def merge_frequency(lst1, lst2):
def load_record(pickle_fname): def load_record(pickle_fname):
with open(pickle_fname, 'rb') as f: f = open(pickle_fname, 'rb')
d = pickle.load(f) d = pickle.load(f)
f.close()
return d return d
def save_frequency_to_pickle(d, pickle_fname): def save_frequency_to_pickle(d, pickle_fname):
with open(pickle_fname, 'wb') as f: 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'] #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']
exclusion_lst = [] exclusion_lst = []
d2 = {} d2 = {}
for k in d: for k in d:
if not k in exclusion_lst and not k.isnumeric() and len(k) > 1: if not k in exclusion_lst and not k.isnumeric() and len(k) > 1:
d2[k] = d[k] d2[k] = d[k]
pickle.dump(d2, f) pickle.dump(d2, f)
f.close()
def unfamiliar(path,word): def unfamiliar(path,word):
with open(path,"rb") as f: f = open(path,"rb")
dic = pickle.load(f) dic = pickle.load(f)
dic[word] += [datetime.now().strftime('%Y%m%d%H%M')] dic[word] += [datetime.now().strftime('%Y%m%d%H%M')]
with open(path,"wb") as fp: fp = open(path,"wb")
pickle.dump(dic,fp) pickle.dump(dic,fp)
def familiar(path,word): def familiar(path,word):
f = open(path,"rb") f = open(path,"rb")
@ -66,8 +68,8 @@ def familiar(path,word):
del dic[word][0] del dic[word][0]
else: else:
dic.pop(word) dic.pop(word)
with open(path,"wb") as f: fp = open(path,"wb")
pickle.dump(dic,f) pickle.dump(dic,fp)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -33,7 +33,7 @@ def deleteRecord(path,word):
except KeyError: except KeyError:
print("sorry") print("sorry")
with open(path, 'wb') as ff: with open(path, 'wb') as ff:
pickle.dump(db, ff) pickle.dump(db, ff)
def dict2lst(d): def dict2lst(d):
if len(d) > 0: if len(d) > 0:
@ -43,7 +43,7 @@ def dict2lst(d):
for k in d: for k in d:
lst.append((k, [datetime.now().strftime('%Y%m%d%H%M')])) lst.append((k, [datetime.now().strftime('%Y%m%d%H%M')]))
return lst return lst
if isinstance(d[keys[0]], list): elif isinstance(d[keys[0]], list):
return list(d.items()) # a list of (key, value) pairs return list(d.items()) # a list of (key, value) pairs
return [] return []
@ -56,19 +56,21 @@ def merge_frequency(lst1, lst2):
def load_record(pickle_fname): def load_record(pickle_fname):
with open(pickle_fname, 'rb') as f: f = open(pickle_fname, 'rb')
d = pickle.load(f) d = pickle.load(f)
f.close()
return d return d
def save_frequency_to_pickle(d, pickle_fname): def save_frequency_to_pickle(d, pickle_fname):
with open(pickle_fname, 'wb') as f: 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'] 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:
d2[k] = list(sorted(d[k])) # 原先这里是d2[k] = list(sorted(set(d[k]))) d2[k] = list(sorted(d[k])) # 原先这里是d2[k] = list(sorted(set(d[k])))
pickle.dump(d2, f) pickle.dump(d2, f)
f.close()
@ -84,4 +86,4 @@ if __name__ == '__main__':
d = load_record('frequency.p') d = load_record('frequency.p')
lst1 = dict2lst(d) lst1 = dict2lst(d)
d = merge_frequency(lst2, lst1) d = merge_frequency(lst2, lst1)
print(d) print(d)

View File

@ -29,7 +29,3 @@ function onReadClick() {
function onChooseClick() { function onChooseClick() {
isChoose = !isChoose; isChoose = !isChoose;
} }
if (performance.navigation.type == 1) { //如果网页刷新,停止播放声音
Reader.stopRead();
}

View File

@ -22,19 +22,33 @@ function getWord() {
function highLight() { function highLight() {
if (!isHighlight) return; if (!isHighlight) return;
let articleContent = document.getElementById("article").innerText; let articleContent = document.getElementById("article").innerText; //将原来的.innerText改为.innerHtml使用innerText会把原文章中所包含的<br>标签去除,导致处理后的文章内容失去了原来的格式
let pickedWords = document.getElementById("selected-words"); // words picked to the text area let pickedWords = document.getElementById("selected-words"); // words picked to the text area
let dictionaryWords = document.getElementById("selected-words2"); // words appearing in the user's new words list let dictionaryWords = document.getElementById("selected-words2"); // words appearing in the user's new words list
let allWords = dictionaryWords === null ? pickedWords.value + " " : pickedWords.value + " " + dictionaryWords.value; let allWords = ""; //初始化allWords的值避免进入判断后编译器认为allWords未初始化的问题
const list = allWords.split(" "); // 将所有的生词放入一个list中用于后续处理 if(dictionaryWords != null){//增加一个判断检查生词本里面是否为空如果为空allWords只添加选中的单词
allWords = pickedWords.value + " " + dictionaryWords.value;
}
else{
allWords = pickedWords.value + " ";
}
const list = allWords.split(" ");//将所有的生词放入一个list中用于后续处理
for (let i = 0; i < list.length; ++i) { for (let i = 0; i < list.length; ++i) {
list[i] = list[i].replace(/(^\W*)|(\W*$)/g, ""); // 消除单词两边的非单词字符 list[i] = list[i].replace(/(^\s*)|(\s*$)/g, ""); //消除单词两边的空字符
if (list[i] != "" && "<mark>".indexOf(list[i]) === -1 && "</mark>".indexOf(list[i]) === -1) { list[i] = list[i].replace('|', "");
// 返回所有匹配单词的集合, 正则表达式RegExp()中, "\b"匹配一个单词的边界, g 表示全局匹配, i 表示对大小写不敏感。 list[i] = list[i].replace('?', "");
let matches = new Set(articleContent.match(new RegExp("\\b" + list[i] + "\\b", "gi"))); if (list[i] !== "" && "<mark>".indexOf(list[i]) === -1 && "</mark>".indexOf(list[i]) === -1) {
for (let word of matches) { //将文章中所有出现该单词word的地方改为"<mark>" + word + "<mark>"。 正则表达式RegExp()中,"\\b"代表单词边界匹配。
// 将文章中所有出现该单词word的地方改为"<mark>" + word + "<mark>"。
articleContent = articleContent.replace(new RegExp("\\b" + word + "\\b", "g"), "<mark>" + word + "</mark>"); //修改代码
let articleContent_fb = articleContent; //文章副本
while(articleContent_fb.toLowerCase().indexOf(list[i].toLowerCase()) !== -1 && list[i]!=""){
//找到副本中和list[i]匹配的第一个单词(第一种匹配情况),并赋值给list[i]。
const index = articleContent_fb.toLowerCase().indexOf(list[i].toLowerCase());
list[i] = articleContent_fb.substring(index, index + list[i].length);
articleContent_fb = articleContent_fb.substring(index + list[i].length); // 使用副本中list[i]之后的子串替换掉副本
articleContent = articleContent.replace(new RegExp("\\b"+list[i]+"\\b","g"),"<mark>" + list[i] + "</mark>");
} }
} }
} }

View File

@ -5,6 +5,8 @@
<meta name="viewport" <meta name="viewport"
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"/>
<meta name="format-detection" content="telephone=no"/> <meta name="format-detection" content="telephone=no"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script>
{{ yml['header'] | safe }} {{ yml['header'] | safe }}
{% if yml['css']['item'] %} {% if yml['css']['item'] %}
@ -26,12 +28,40 @@
} }
@keyframes shakes { @keyframes shakes {
10%, 90% { transform: translate3d(-1px, 0, 0); } 10%, 90% {
20%, 50% { transform: translate3d(+2px, 0, 0); } transform: translate3d(-1px, 0, 0);
30%, 70% { transform: translate3d(-4px, 0, 0); } }
40%, 60% { transform: translate3d(+4px, 0, 0); } 20%, 50% {
50% { transform: translate3d(-4px, 0, 0); } transform: translate3d(+2px, 0, 0);
}
30%, 70% {
transform: translate3d(-4px, 0, 0);
}
40%, 60% {
transform: translate3d(+4px, 0, 0);
}
50% {
transform: translate3d(-4px, 0, 0);
}
} }
.lead {
font-size: 22px;
font-family: Helvetica, sans-serif;
white-space: pre-wrap;
}
.arrow {
padding: 0;
font-size: 20px;
line-height: 21px;
display: inline-block;
}
.arrow:hover {
cursor: pointer;
}
</style> </style>
</head> </head>
<body> <body>
@ -39,53 +69,70 @@
<p><b>English Pal for <font id="username" color="red">{{ username }}</font></b> <p><b>English Pal for <font id="username" color="red">{{ username }}</font></b>
{% if username == admin_name %} {% if username == admin_name %}
<a class="btn btn-secondary" href="/admin" role="button" onclick="stopRead()">管理</a> <a class="btn btn-secondary" href="/admin" role="button" onclick="stopRead()">管理</a>
{% endif %} {% endif %}
<a id="quit" class="btn btn-secondary" href="/logout" role="button" onclick="stopRead()">退出</a> <a id="quit" class="btn btn-secondary" href="/logout" role="button" onclick="stopRead()">退出</a>
<a class="btn btn-secondary" href="/reset" role="button" onclick="stopRead()">重设密码</a> <a class="btn btn-secondary" href="/reset" role="button" onclick="stopRead()">重设密码</a>
</p> </p>
{# {% for message in flashed_messages %}#} {# 根据user_service.userpage,取消了参数flashed_messages因此注释了这段代码 #} {% for message in get_flashed_messages() %}
{# <div class="alert alert-warning" role="alert">Congratulations! {{ message }}</div>#} <div class="alert alert-warning alert-dismissible fade show" role="alert">
{# {% endfor %}#} {{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
<button class="btn btn-success" id="load_next_article" onclick="load_next_article();Reader.stopRead()"> 下一篇 Next Article </button> <button class="arrow" id="load_next_article" onclick="load_next_article();Reader.stopRead()"
<button class="btn btn-success" id="load_pre_article" onclick="load_pre_article();Reader.stopRead()" > 上一篇 Previous Article </button> title="下一篇 Next Article">⇨
</button>
<button class="arrow" id="load_pre_article" onclick="load_pre_article();Reader.stopRead()" style="display: none"
title="上一篇 Previous Article">⇦
</button>
<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="badge bg-success" id="user-level">{{ today_article["user_level"] }}</span> and we have chosen an article with a difficulty level of <span class="badge bg-success" id="text_level">{{ today_article["text_level"] }}</span> for you.</div> <div class="alert alert-success" role="alert">According to your word list, your level is <span
<p class="text-muted" id="date">Article added on: {{ today_article["date"] }}</p><br/> class="text-decoration-underline" id="user_level">{{ today_article["user_level"] }}</span> and we
<div class="p-3 mb-2 bg-light text-dark"><br/> have chosen an article with a difficulty level of <span class="text-decoration-underline"
<p class="display-5" id="article_title">{{ today_article["article_title"] }}</p><br/> id="text_level">{{ today_article["text_level"] }}</span>
<p class="lead"><font id="article" size=2>{{ today_article["article_body"] }}</font></p><br/> for you.
<p><small class="text-muted" id="source">{{ today_article['source'] }}</small></p><br/> </div>
<p><b id="question">{{ today_article['question'] }}</b></p><br/> <p class="text-muted" id="date">Article added on: {{ today_article["date"] }}</p><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="lead"><font id="article">{{ today_article["article_body"] }}</font></p><br/>
<div>
<p><small class="text-muted" id="source">{{ today_article['source'] }}</small></p><br/>
</div>
<p><b id="question">{{ today_article['question'] }}</b></p><br/>
<script type="text/javascript"> <script type="text/javascript">
function toggle_visibility(id) { {# https://css-tricks.com/snippets/javascript/showhide-element/#} function toggle_visibility(id) { {# https://css-tricks.com/snippets/javascript/showhide-element/#}
const e = document.getElementById(id); const e = document.getElementById(id);
if(e.style.display === 'block') if (e.style.display === 'block')
e.style.display = 'none'; e.style.display = 'none';
else else
e.style.display = 'block'; e.style.display = 'block';
} }
</script> </script>
<button onclick="toggle_visibility('answer');">ANSWER</button> <button onclick="toggle_visibility('answer');">ANSWER</button>
<div id="answer" style="display:none;">{{ today_article['answer'] }}</div><br/> <div id="answer" style="display:none;">{{ today_article['answer'] }}</div>
<br/>
</div> </div>
</div> </div>
<div class="alert alert-success" role="alert" id="not_found" style="display:none;"> <div class="alert alert-success" role="alert" id="not_found" style="display:none;">
<p class="text-muted"><span class="badge bg-success">Notes:</span><br>No article is currently available for you. You can try again a few times or mark new words in the passage to improve your level.</p> <p class="text-muted"><span class="badge bg-success">Notes:</span><br>No article is currently available for
you. You can try again a few times or mark new words in the passage to improve your level.</p>
</div> </div>
<div class="alert alert-success" role="alert" id="read_all" style="display:none;"> <div class="alert alert-success" role="alert" id="read_all" style="display:none;">
<p class="text-muted"><span class="badge bg-success">Notes:</span><br>You've read all the articles.</p> <p class="text-muted"><span class="badge bg-success">Notes:</span><br>You've read all the articles.</p>
</div> </div>
</div> </div>
<input type="checkbox" onclick="toggleHighlighting()" checked/>生词高亮 <input type="checkbox" id="highlightCheckbox" onclick="toggleHighlighting()"/>生词高亮
<input type="checkbox" onclick="onReadClick()" checked/>大声朗读 <input type="checkbox" id="readCheckbox" onclick="onReadClick()"/>大声朗读
<input type="checkbox" onclick="onChooseClick()" checked/>划词入库 <input type="checkbox" id="chooseCheckbox" onclick="onChooseClick()"/>划词入库
<div class="range"> <div class="range">
<div class="field"> <div class="field">
<div class="sliderValue"> <div class="sliderValue">
@ -97,8 +144,8 @@
<p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p> <p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p>
<form method="post" action="/{{ username }}/userpage"> <form method="post" action="/{{ username }}/userpage">
<textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/> <textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/>
<input type="submit" value="把生词加入我的生词库" onclick="Reader.stopRead()"/> <button class="btn btn-primary btn-lg" type="submit" onclick="Reader.stopRead()">把生词加入我的生词库</button>
<input type="reset" value="清除"/> <button class="btn btn-primary btn-lg" type="reset" onclick="clearSelectedWords()">清除</button>
</form> </form>
{% if session.get['thisWord'] %} {% if session.get['thisWord'] %}
<script type="text/javascript"> <script type="text/javascript">
@ -113,7 +160,7 @@
{% if d_len > 0 %} {% if d_len > 0 %}
<p> <p>
<b>我的生词簿</b> <b>我的生词簿</b>
<label for="move_dynamiclly"> <label for="move_dynamiclly">
<input type="checkbox" name="move_dynamiclly" id="move_dynamiclly" checked> <input type="checkbox" name="move_dynamiclly" id="move_dynamiclly" checked>
允许动态调整顺序 允许动态调整顺序
@ -126,9 +173,10 @@
{% set freq = x[1] %} {% set freq = x[1] %}
{% if session.get('thisWord') == x[0] and session.get('time') == 1 %} {% if session.get('thisWord') == x[0] and session.get('time') == 1 %}
{% endif %} {% endif %}
<p id='p_{{ word }}' class="new-word" > <p id='p_{{ word }}' class="new-word">
<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"
role="button">{{ word }}</a> href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'
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>
@ -147,60 +195,127 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<script type="text/javascript"> <script type="text/javascript">
function load_next_article(){ window.onload = function () { // 页面加载时执行
const settings = {
// initialize settings from localStorage
highlightChecked: localStorage.getItem('highlightChecked') !== 'false', // localStorage stores strings, default to true. same below
readChecked: localStorage.getItem('readChecked') !== 'false',
chooseChecked: localStorage.getItem('chooseChecked') !== 'false',
rangeValue: localStorage.getItem('rangeValue') || '1',
selectedWords: localStorage.getItem('selectedWords') || ''
};
const elements = {
highlightCheckbox: document.querySelector('#highlightCheckbox'),
readCheckbox: document.querySelector('#readCheckbox'),
chooseCheckbox: document.querySelector('#chooseCheckbox'),
rangeComponent: document.querySelector('#rangeComponent'),
rangeValueDisplay: document.querySelector('#rangeValue'),
selectedWordsInput: document.querySelector('#selected-words')
};
// 应用设置到页面元素
elements.highlightCheckbox.checked = settings.highlightChecked;
elements.readCheckbox.checked = settings.readChecked;
elements.chooseCheckbox.checked = settings.chooseChecked;
elements.rangeComponent.value = settings.rangeValue;
elements.rangeValueDisplay.textContent = `${settings.rangeValue}x`;
elements.selectedWordsInput.value = settings.selectedWords;
// 刷新页面或进入页面时判断,若不是首篇文章,则上一篇按钮可见
if (sessionStorage.getItem('pre_page_button') !== 'display' && sessionStorage.getItem('pre_page_button')) {
$('#load_pre_article').show();
}
// 事件监听器
elements.selectedWordsInput.addEventListener('input', () => {
localStorage.setItem('selectedWords', elements.selectedWordsInput.value);
});
elements.rangeComponent.addEventListener('input', () => {
const rangeValue = elements.rangeComponent.value;
elements.rangeValueDisplay.textContent = `${rangeValue}x`;
localStorage.setItem('rangeValue', rangeValue);
});
};
function clearSelectedWords() {
localStorage.removeItem('selectedWords');
document.querySelector('#selected-words').value = '';
}
function load_next_article() {
$("#load_next_article").prop("disabled", true)
$.ajax({ $.ajax({
url: '/get_next_article/{{username}}', url: '/get_next_article/{{username}}',
dataType: 'json', dataType: 'json',
success: function(data) { success: function (data) {
// 更新页面内容 // 更新页面内容
if(data['today_article']){ if (data['today_article']) {
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']);
} }
}, complete: function (xhr, status) {
$("#load_next_article").prop("disabled", false)
} }
}); });
} }
function load_pre_article(){
function load_pre_article() {
$.ajax({ $.ajax({
url: '/get_pre_article/{{username}}', url: '/get_pre_article/{{username}}',
dataType: 'json', dataType: 'json',
success: function(data) { success: function (data) {
// 更新页面内容 // 更新页面内容
if(data['today_article']){ if (data['today_article']) {
update(data['today_article']); update(data['today_article']);
check_pre(data['visited_articles']); check_pre(data['visited_articles']);
} }
} }
}); });
} }
function update(today_article){
$('#user-level').html(today_article['user_level']); function update(today_article) {
$('#user_level').html(today_article['user_level']);
$('#text_level').html(today_article["text_level"]); $('#text_level').html(today_article["text_level"]);
$('#date').html('Article added on: '+today_article["date"]); $('#date').html('Article added on: ' + today_article["date"]);
$('#article_title').html(today_article["article_title"]); $('#article_title').html(today_article["article_title"]);
$('#article').html(today_article["article_body"]); $('#article').html(today_article["article_body"]);
$('#source').html(today_article['source']); $('#source').html(today_article['source']);
$('#question').html(today_article["question"]); $('#question').html(today_article["question"]);
$('#answer').html(today_article["answer"]); $('#answer').html(today_article["answer"]);
document.querySelector('#text_level').classList.add('mark'); // highlight text difficult level for 2 seconds
setTimeout(() => {
document.querySelector('#text_level').classList.remove('mark');
}, 2000);
document.querySelector('#user_level').classList.add('mark'); // do the same thing for user difficulty level
setTimeout(() => {
document.querySelector('#user_level').classList.remove('mark');
}, 2000);
} }
<!-- 检查是否存在上一篇或下一篇,不存在则对应按钮隐藏-->
function check_pre(visited_articles){ <!-- 检查是否存在上一篇或下一篇,不存在则对应按钮隐藏-->
if((visited_articles=='')||(visited_articles['index']<=0)){ function check_pre(visited_articles) {
if ((visited_articles == '') || (visited_articles['index'] <= 0)) {
$('#load_pre_article').hide(); $('#load_pre_article').hide();
}else{ sessionStorage.setItem('pre_page_button', 'display')
} else {
$('#load_pre_article').show(); $('#load_pre_article').show();
sessionStorage.setItem('pre_page_button', 'show')
} }
} }
function check_next(result_of_generate_article){
if(result_of_generate_article == "found"){ function check_next(result_of_generate_article) {
$('#found').show();$('#not_found').hide(); if (result_of_generate_article == "found") {
$('#found').show();
$('#not_found').hide();
$('#read_all').hide(); $('#read_all').hide();
}else if(result_of_generate_article == "not found"){ } else if (result_of_generate_article == "not found") {
$('#found').hide(); $('#found').hide();
$('#not_found').show(); $('#not_found').show();
$('#read_all').hide(); $('#read_all').hide();
}else{ } else {
$('#found').hide(); $('#found').hide();
$('#not_found').hide(); $('#not_found').hide();
$('#read_all').show(); $('#read_all').show();

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

@ -1,10 +1,17 @@
from datetime import datetime from datetime import datetime
from flask import render_template, session, url_for, redirect, request, json, escape, Blueprint
from admin_service import ADMIN_NAME from admin_service import ADMIN_NAME
from flask import *
# from app import Yaml
# from app.Article import get_today_article, load_freq_history
# from app.WordFreq import WordFreq
# from app.wordfreqCMD import sort_in_descending_order
import Yaml import Yaml
from Article import get_today_article, load_freq_history from Article import get_today_article, load_freq_history
from WordFreq import WordFreq from WordFreq import WordFreq
from wordfreqCMD import sort_in_descending_order from wordfreqCMD import sort_in_descending_order
import pickle_idea import pickle_idea
import pickle_idea2 import pickle_idea2
@ -16,7 +23,7 @@ path_prefix = './' # comment this line in deployment
@userService.route("/get_next_article/<username>",methods=['GET','POST']) @userService.route("/get_next_article/<username>",methods=['GET','POST'])
def get_next_article(username): def get_next_article(username):
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_{username}.pickle' user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
session['old_articleID'] = session.get('articleID') session['old_articleID'] = session.get('articleID')
if request.method == 'GET': if request.method == 'GET':
visited_articles = session.get("visited_articles") visited_articles = session.get("visited_articles")
@ -37,7 +44,7 @@ def get_next_article(username):
@userService.route("/get_pre_article/<username>",methods=['GET']) @userService.route("/get_pre_article/<username>",methods=['GET'])
def get_pre_article(username): def get_pre_article(username):
user_freq_record = path_prefix + 'static/frequency/' + f'frequency_{username}.pickle' user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
if request.method == 'GET': if request.method == 'GET':
visited_articles = session.get("visited_articles") visited_articles = session.get("visited_articles")
if(visited_articles["index"]==0): if(visited_articles["index"]==0):
@ -78,7 +85,7 @@ def familiar(username, word):
:param word: :param word:
:return: :return:
''' '''
user_freq_record = path_prefix + 'static/frequency/' + f'frequency_{username}.pickle' user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
pickle_idea.familiar(user_freq_record, word) pickle_idea.familiar(user_freq_record, word)
session['thisWord'] = word # 1. put a word into session session['thisWord'] = word # 1. put a word into session
session['time'] = 1 session['time'] = 1
@ -93,7 +100,7 @@ def deleteword(username, word):
:param word: 单词 :param word: 单词
:return: 重定位到用户界面 :return: 重定位到用户界面
''' '''
user_freq_record = path_prefix + 'static/frequency/' + f'frequency_{username}.pickle' user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
pickle_idea2.deleteRecord(user_freq_record, word) pickle_idea2.deleteRecord(user_freq_record, word)
# 模板userpage_get.html中删除单词是异步执行而flash的信息后续是同步执行的所以注释这段代码同时如果这里使用flash但不提取信息则会影响 signup.html的显示。bug复现删除单词后点击退出点击注册注册页面就会出现提示信息 # 模板userpage_get.html中删除单词是异步执行而flash的信息后续是同步执行的所以注释这段代码同时如果这里使用flash但不提取信息则会影响 signup.html的显示。bug复现删除单词后点击退出点击注册注册页面就会出现提示信息
# flash(f'{word} is no longer in your word list.') # flash(f'{word} is no longer in your word list.')
@ -119,7 +126,7 @@ def userpage(username):
# 获取session里的用户名 # 获取session里的用户名
username = session.get('username') username = session.get('username')
user_freq_record = path_prefix + 'static/frequency/' + f'frequency_{username}.pickle' user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
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'])
@ -127,7 +134,7 @@ def userpage(username):
lst = f.get_freq() lst = f.get_freq()
return render_template('userpage_post.html',username=username,lst = lst, yml=Yaml.yml) return render_template('userpage_post.html',username=username,lst = lst, yml=Yaml.yml)
if request.method == 'GET': # when we load a html page elif request.method == 'GET': # when we load a html page
d = load_freq_history(user_freq_record) d = load_freq_history(user_freq_record)
lst = pickle_idea2.dict2lst(d) lst = pickle_idea2.dict2lst(d)
lst2 = [] lst2 = []
@ -160,7 +167,7 @@ def user_mark_word(username):
:return: 重定位到用户界面 :return: 重定位到用户界面
''' '''
username = session[username] username = session[username]
user_freq_record = path_prefix + 'static/frequency/' + f'frequency_{username}.pickle' user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
if request.method == 'POST': if request.method == 'POST':
# 提交标记的单词 # 提交标记的单词
d = load_freq_history(user_freq_record) d = load_freq_history(user_freq_record)
@ -171,11 +178,13 @@ def user_mark_word(username):
d = pickle_idea2.merge_frequency(lst, lst_history) d = pickle_idea2.merge_frequency(lst, lst_history)
pickle_idea2.save_frequency_to_pickle(d, user_freq_record) pickle_idea2.save_frequency_to_pickle(d, user_freq_record)
return redirect(url_for('user_bp.userpage', username=username)) return redirect(url_for('user_bp.userpage', username=username))
return 'Under construction' else:
return 'Under construction'
def get_time(): def get_time():
''' '''
获取当前时间 获取当前时间
:return: 当前时间 :return: 当前时间
''' '''
return datetime.now().strftime('%Y%m%d%H%M') # upper to minutes return datetime.now().strftime('%Y%m%d%H%M') # upper to minutes

View File

@ -5,8 +5,8 @@
import collections import collections
import string import string
import os import operator
import sys # 引入模块sys因为我要用里面的sys.argv列表中的信息来读取命令行参数。 import os, sys # 引入模块sys因为我要用里面的sys.argv列表中的信息来读取命令行参数。
import pickle_idea import pickle_idea
def freq(fruit): def freq(fruit):
@ -18,7 +18,7 @@ def freq(fruit):
''' '''
result = [] result = []
fruit = fruit.lower() # 字母转小写 fruit = fruit.lower() # 字母转小写
flst = fruit.split() # 字符串转成list flst = fruit.split() # 字符串转成list
c = collections.Counter(flst) c = collections.Counter(flst)
@ -32,18 +32,19 @@ def youdao_link(s): # 有道链接
def file2str(fname):#文件转字符 def file2str(fname):#文件转字符
with open(fname, encoding="utf-8") as f: f = open(fname) #打开
s = f.read() s = f.read() #读取
f.close() #关闭
return s return s
def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。 def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。
special_characters = r'\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|' # 把里面的字符都去掉 special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|' # 把里面的字符都去掉
for c in special_characters: for c in special_characters:
s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况 s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况
s = s.replace('--', ' ') s = s.replace('--', ' ')
s = s.strip() # 去除前后的空格 s = s.strip() # 去除前后的空格
if '\'' in s: if '\'' in s:
n = len(s) n = len(s)
t = '' # 用来收集我需要保留的字符 t = '' # 用来收集我需要保留的字符
@ -77,10 +78,11 @@ def make_html_page(lst, fname): # 只是在wordfreqCMD.py中的main函数中调
count = 1 count = 1
for x in lst: for x in lst:
# <a href="">word</a> # <a href="">word</a>
s += f'<p>{count} <a href="{youdao_link(x[0])}">{x[0]}</a> ({x[1]})</p>' s += '<p>%d <a href="%s">%s</a> (%d)</p>' % (count, youdao_link(x[0]), x[0], x[1])
count += 1 count += 1
with open(fname, 'w', encoding="utf-8") as f: f = open(fname, 'w')
f.write(s) f.write(s)
f.close()
## main程序入口 ## main程序入口
@ -99,10 +101,10 @@ if __name__ == '__main__':
s = remove_punctuation(s) # 这里是s是实参(argument),里面有值 s = remove_punctuation(s) # 这里是s是实参(argument),里面有值
L = freq(s) L = freq(s)
for x in sort_in_descending_order(L): for x in sort_in_descending_order(L):
print(f'{x[0]}\t{x[1]}\t{youdao_link(x[0])}' )#函数导出 print('%s\t%d\t%s' % (x[0], x[1], youdao_link(x[0])))#函数导出
# 把频率的结果放result.html中 # 把频率的结果放result.html中
make_html_page(sort_in_descending_order(L), 'result.html') make_html_page(sort_in_descending_order(L), 'result.html')
print('\nHistory:\n') print('\nHistory:\n')
if os.path.exists('frequency.p'): if os.path.exists('frequency.p'):
@ -116,3 +118,6 @@ if __name__ == '__main__':
lst_history = pickle_idea.dict2lst(d) lst_history = pickle_idea.dict2lst(d)
d = pickle_idea.merge_frequency(L, lst_history) d = pickle_idea.merge_frequency(L, lst_history)
pickle_idea.save_frequency_to_pickle(d, 'frequency.p') pickle_idea.save_frequency_to_pickle(d, 'frequency.p')

View File

@ -1,5 +1,8 @@
Flask==1.1.2 Flask==2.0.3
selenium==3.141.0 selenium==3.141.0
PyYAML~=6.0 PyYAML~=6.0
pony==0.7.16 pony==0.7.16
snowballstemmer==2.2.0 snowballstemmer==2.2.0
Werkzeug==2.2.2
pytest~=8.1.1