Compare commits

...

72 Commits

Author SHA1 Message Date
mrlan 43c719b6b2 Merge pull request 'Fix bug 501 - 特殊字符&加入生词库后删除按钮失效' (#81) from Bug501-Hui into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/81
2023-01-31 16:45:50 +08:00
Hui Lan a1955341c6 Fix bug 501 - 特殊字符&加入生词库后删除按钮失效 2023-01-31 16:39:11 +08:00
mrlan 92a8b4a994 Lanhui-update-README2 (#80)
Co-authored-by: Lan Hui <1348141770@qq.com>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/80
Co-authored-by: mrlan <mrlan@noreply.121.4.94.30>
Co-committed-by: mrlan <mrlan@noreply.121.4.94.30>
2023-01-30 15:44:01 +08:00
王志豪 e10dbf9d67 Bug507-WuWenZhuo (#70)
### 修复了生词簿为空时,双击文章单词无法高亮的问题
通过增加一个判断,判断生词簿为空时,不把生词簿的内容进行处理,仅处理选中单词。

### 修复了生词簿为空时,取消高亮后,导致文章格式混乱的问题
原代码中,从数据库提取文章放到网页上时,使用的是.innerText的方法,导致原文章里包含的<br>标签丢失,导致文章格式混乱的问题。

Co-authored-by: unknown <Alcatraz@qq.com>
Co-authored-by: Hui Lan <lanhui@zjnu.edu.cn>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/70
Co-authored-by: 王志豪 <1594799762@qq.com>
Co-committed-by: 王志豪 <1594799762@qq.com>
2023-01-29 12:48:52 +08:00
李雨峰 9cdc9c6f7f Bug521-LiYuFeng-Refactor (#72)
@mrlan
蓝老师:

    本次改进内容如下:
        1. 对生词居中问题进行修改,现在已经不会居中了。
        2. 对于单词数量基数大而导致的排序速度慢的问题,我们进行了优化,提升了排序的速度。
        3. 对用户交互进行了优化,当用户点击“熟悉”或“不熟悉”之后,会自动进行排序,并会跳转到那个单词的位置,用抖动的效果来提示用户。

Co-authored-by: isaac <1141730046@qq.com>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/72
Co-authored-by: 李雨峰 <1141730046@qq.com>
Co-committed-by: 李雨峰 <1141730046@qq.com>
2023-01-29 12:01:19 +08:00
陈秋伟 972a1a5524 Bug490-ChenQiuwei (#63)
修复Bug-490,使注册时确认密码能够发挥作用,在确认密码与所设置密码不一致时,能够提示“确认密码与输入密码不一致”。

Co-authored-by: 2658626578 <2658626578@qq.com>
Co-authored-by: Hui Lan <lanhui@zjnu.edu.cn>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/63
Co-authored-by: 陈秋伟 <2658626578@qq.com>
Co-committed-by: 陈秋伟 <2658626578@qq.com>
2023-01-29 11:49:27 +08:00
mrlan c52d53596f Merge pull request 'Bug489-DingRui' (#74) from SPM2022F-CONTRIBUTORS-DingRui into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/74
2023-01-29 11:31:41 +08:00
mrlan ca8c1bf8de Merge pull request 'Bug508-CenHaotian' (#62) from Bug508-CenHaotian into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/62
2023-01-29 11:19:49 +08:00
mrlan febd0fc932 Merge pull request '用简单的方法(创建 frequency 文件夹)修复Bug 499' (#64) from Bug499-Hui into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/64
2023-01-29 11:06:17 +08:00
mrlan 03353d49b1 Bug525-Hui (#79)
Co-authored-by: Hui Lan <lanhui@zjnu.edu.cn>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/79
Co-authored-by: mrlan <mrlan@noreply.121.4.94.30>
Co-committed-by: mrlan <mrlan@noreply.121.4.94.30>
2023-01-29 10:57:58 +08:00
mrlan 1373df2a3e Merge pull request '增加单词默认勾选,并修改提示 Fix Bug 495 - Liang Li Gang' (#77) from Bug495-LiangLiGang into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/77
2023-01-29 10:27:22 +08:00
np1717 086dfcb6eb fix bug 489 2022-12-19 17:54:10 +08:00
任榆 f0afd5f40f Merge pull request 'Bug 512 - 文章朗读问题' (#59) from Bug512-RenYu into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/59
2022-12-13 13:39:23 +08:00
任榆 5cc981b549 Merge branch 'master' into Bug512-RenYu 2022-12-13 13:28:48 +08:00
岑昊天 f37ea182f6 加入生词库过滤| 2022-12-06 17:02:59 +08:00
MR LAN 6327b11711 Made folder 'frequency' under folder 'static', and added a README file under folder 'frequency' 2022-12-06 16:06:13 +08:00
岑昊天 d58dacd71c 修复Bug508,解决带有特殊字符|的单词在文章中的高亮问题 2022-12-06 14:40:50 +08:00
张艺腾 e74f1ff477 Bug505-ZhangYiteng (#61)
bug修改只涉及到account_service.py中新增的5行。
其他增删都是重写reset.html(页面样式和login、signup页面相一致),并将reset、signup、login三个页面的共同样式抽离出独立的css文件。

Co-authored-by: Q_yt <2483750517@qq.com>
Co-authored-by: Hui Lan <lanhui@zjnu.edu.cn>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/61
Co-authored-by: 张艺腾 <2483750517@qq.com>
Co-committed-by: 张艺腾 <2483750517@qq.com>
2022-12-03 20:52:01 +08:00
丁锐 89c12337d5 在README中修改内容 2022-12-02 15:48:45 +08:00
梁立港 b65ffa6054 增加单词默认勾选,并修改提示 2022-12-01 21:04:12 +08:00
丁锐 7e3004a2e6 撤回操作 2022-11-29 16:55:48 +08:00
丁锐 3609421976 对确认密码部分保证有数据读入 2022-11-29 16:53:59 +08:00
任榆 5c85041135 Bug 512 - 文章朗读问题
在fillwowrd.js中添加了stopRead()函数,将其添加给对应按钮或超链接以终止朗读。
2022-11-25 15:42:37 +08:00
mrlan 671df67723 Bug487-WuYuhan-Refactor (#58)
将所有用于用户名验证的逻辑放入到 `UserName` 类中。

Hui

Co-authored-by: Lan Hui <1348141770@qq.com>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/58
Co-authored-by: mrlan <mrlan@noreply.121.4.94.30>
Co-committed-by: mrlan <mrlan@noreply.121.4.94.30>
2022-11-10 19:03:59 +08:00
mrlan f909201615 Merge pull request 'Bug487-WuYuhan-Refactor' (#57) from Bug487-WuYuhan-Refactor into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/57
2022-11-08 19:53:09 +08:00
Hui Lan 29ffada7eb Login.py: improve comments. 2022-11-03 22:28:25 +08:00
Hui Lan d3a796428d account_service.py: module re is no longer necessary. 2022-11-03 22:21:34 +08:00
Lan Hui 702205940c Login.py: must convert warn to string before comparing to OK 2022-11-03 22:06:24 +08:00
Lan Hui f0b5adc5e4 Login.py: fix function name 2022-11-03 22:02:32 +08:00
Hui Lan 3cfec31c3f Login.py: add missing colon 2022-11-03 22:00:47 +08:00
Hui Lan 286e884dd8 Refactor Wu Yuhan's code 2022-11-03 21:59:12 +08:00
mrlan bfd87c51f6 Merge pull request 'Bug487-WuYuhan' (#56) from Bug487-WuYuhan into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/56
2022-11-03 21:28:55 +08:00
mrlan ab01b8e19b Hui-Build (#55)
Make English Pal docker run automatically each time after Ubuntu reboot.

Hui

Co-authored-by: Hui Lan <lanhui@zjnu.edu.cn>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/55
Co-authored-by: mrlan <mrlan@noreply.121.4.94.30>
Co-committed-by: mrlan <mrlan@noreply.121.4.94.30>
2022-11-01 21:43:01 +08:00
吴宇涵 59d95d8e9f account_service.py: 导入re库使用正则匹配过滤了注册时用户名的非法字符 2022-10-21 11:07:20 +08:00
吴宇涵 5844eab6d5 account_service.py: 添加注册时用户名的非法字符过滤 2022-10-21 10:44:39 +08:00
mrlan 02ffcd3b59 Merge pull request 'Bug412-JiangLetian-Refactor' (#54) from Bug412-JiangLetian-Refactor into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/54
2022-08-05 16:18:06 +08:00
Lan Hui ecc354bc0d Refactor: use better function 2022-08-02 12:33:41 +08:00
Lan Hui 1d8671c5c7 Refactor: use better function name 2022-08-02 12:30:27 +08:00
Lan Hui 8cb34e56ba Refactor: remove duplicate code block 2022-08-02 12:26:18 +08:00
Lan Hui b5dacb9ad2 Improve comments 2022-08-02 11:52:40 +08:00
Lan Hui 47e745e774 Use better variable name (use articleContent instead of txt, and use camelCase) 2022-08-02 11:45:21 +08:00
Lan Hui 1dfe370983 Use better variable names 2022-08-02 11:39:35 +08:00
mrlan 9927515b11 Merge pull request 'Bug412-JiangLetian' (#39) from Bug412-JiangLetian into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/39
2022-08-02 11:03:39 +08:00
Lan Hui c15746bbb2 Resolve conflicts 2022-08-02 11:00:33 +08:00
mrlan b745da4c90 Merge pull request 'IMPROVE-WangWeiLong' (#35) from IMPROVE-WangWeiLong into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/35
2022-07-31 09:11:53 +08:00
mrlan 7663dfb8f4 Merge pull request 'Hui-EscapeUserInput' (#53) from Hui-EscapeUserInput into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/53
2022-07-29 16:20:01 +08:00
Lan Hui 0098fa8746 Prevent attribute injection 2022-07-29 15:26:19 +08:00
Lan Hui 828cef406c Escape user input first 2022-07-29 15:22:42 +08:00
徐幸 2c1bc98833 Bug422-XuXing (#46)
增加了返回上一篇的按钮及相关功能的实现,当点击下一篇文章跳转至下一篇时,页面中会增加一个返回上一篇按钮,点击返回上一篇按钮后可以回到上一篇。

Co-authored-by: Lan Hui <1348141770@qq.com>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/46
Co-authored-by: 徐幸 <2567198082@qq.com>
Co-committed-by: 徐幸 <2567198082@qq.com>
2022-07-21 23:13:33 +08:00
mrlan 9a89510f4e Merge pull request 'Improvement-Stewart' (#49) from Improvement-Stewart into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/49
2022-07-20 17:31:01 +08:00
Lan Hui e9eb604a22 Improve spacing and indentation. 2022-07-20 17:10:03 +08:00
Lan Hui 9beb1ad1d2 Make up the enclosing >. 2022-07-20 17:09:04 +08:00
Lan Hui 8998d6e4af Improve spacing. 2022-07-20 17:08:07 +08:00
Lan Hui 8747f35fd8 Make up the enclosing >. 2022-07-20 17:07:09 +08:00
Lan Hui fdb432031f Better indentation. 2022-07-20 17:06:11 +08:00
Lan Hui 1401870591 Use Chinese UI language. 2022-07-20 17:04:34 +08:00
Lan Hui 1ca90bb2a9 Remove superfluous 'jjj'. 2022-07-20 17:00:13 +08:00
Lan Hui ea19658212 Merge branch 'Improvement-Stewart' 2022-07-20 16:57:31 +08:00
mrlan 028e2f9d56 Merge pull request '[Refactor]: Remove loop.index0, as it is hard to understand.' (#52) from Bug400-QiuZhonghui-Refactor into master
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/52
2022-07-18 19:58:01 +08:00
Lan Hui 50a1093781 Remove loop.index0, as it is hard to understand. 2022-07-18 19:51:23 +08:00
陈靖毅 c21659ba7e Bug477 重构 and 增加功能 (#48)
Co-authored-by: cjybyjk <cjybyjk@zjnu.edu.cn>
Co-authored-by: Lan Hui <1348141770@qq.com>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/48
Co-authored-by: 陈靖毅 <cjybyjk@zjnu.edu.cn>
Co-committed-by: 陈靖毅 <cjybyjk@zjnu.edu.cn>
2022-07-18 16:00:08 +08:00
邱忠辉 9d420acd8b [REFACTOR] user_service.py: Added id to user name, word and frequency for click to execute AJAX request in JS (#30)
1. Add jquery.js and word_operation.js to the static folder.

2. Changed the return type of the familiar, unfamiliar and deleteword methods of user_service.py.
    ```
    original: return redirect(url_for('userpage', username=username))
    now: return "success"
    ```
3. In order to get elements in JS to make ajax requests we add ids in userpage_get.html for familiar, unfamiliar, delete buttons and so on.

4. When the user's word book was retrieved, the list returned was already sorted in descending order of frequency, so redundant code was removed in userpage_get.html.

Co-authored-by: PeterQiu <www.1392993990@qq.com>
Co-authored-by: Lan Hui <1348141770@qq.com>
Reviewed-on: http://121.4.94.30:3000/mrlan/EnglishPal/pulls/30
Co-authored-by: 邱忠辉 <1392993990@qq.com>
Co-committed-by: 邱忠辉 <1392993990@qq.com>
2022-07-18 13:29:04 +08:00
stewy 6823c10043 improved 2022-06-14 13:25:42 +08:00
stewy ee44372848 improved 2022-06-14 12:37:28 +08:00
蒋乐天 041cbd97fc 更新 'app/static/js/highlight.js' 2022-06-13 21:32:49 +08:00
lin b53e7031e5 Bug412-JiangLetian 2022-06-13 11:40:20 +08:00
缪宸硕 e48008550a Merge branch 'Bug394-MiaoChenShuo' into master 2022-06-12 21:50:36 +08:00
李凯 fde3be4c23 更新 'app/Article.py' 2022-06-11 23:21:17 +08:00
李凯 4817557099 更新 'app/Article.py' 2022-06-11 23:20:41 +08:00
李凯 5d5f4cf8f2 更新 'app/Article.py' 2022-06-11 23:13:08 +08:00
李凯 e40ebac452 更新 'app/static/css/bootstrap.css' 2022-06-11 23:08:29 +08:00
miaochenshuo 260f62967b 修复 Bug394 2022-06-05 23:36:55 +08:00
23 changed files with 728 additions and 197 deletions

View File

@ -11,15 +11,14 @@ Hui Lan <hui.lan@cantab.net>
EnglishPal allows the user to build his list of new English words
picked from articles selected for him according his vocabulary level.
picked from articles selected for him to read according his vocabulary level. EnglishPal will determine a user's vocabulary level based on his picked words. After that, it will recommend articles for him to read, in order to booster his English vocabulary furthermore.
## Run it on a local machine
## Run on your own laptop
`python3 main.py`
Make sure you have the SQLite database file in `app/static` (see below).
Make sure you have put the SQLite database file in the path `app/static` (see below).
## Run it as a Docker container
@ -29,32 +28,32 @@ Assuming that docker has been installed and that you are a sudo user (i.e., sudo
`sudo ./build.sh`
Open your favourite Internet browser and enter this URL address: `http://ip-address:90`.
Open your favourite Internet browser and enter this URL address: `http://ip-address:90`. Note: you must update the variable `DEPLOYMENT_DIR` in `build.sh`.
### Explanation on the commands in build.sh
My steps for deploying English on the server.
My steps for deploying English on a Ubuntu server.
- ssh to ubuntu@118.*.*.118
- cd to /home/lanhui/englishpal2/EnglishPal
- cd to `/home/lanhui/englishpal2/EnglishPal`
- Stop all docker service: `sudo service docker restart`. If you know the docker container ID, then the above command is an overkill. Use the following command instead: `sudo docker stop ContainerID`. You could get all container IDs with the following command: `sudo docker ps`
- Rebuild container. Run the following command to rebuild a docker image after the code gets updated: `sudo docker build -t englishpal .`
- Rebuild container. Run the following command to rebuild a docker image each time after the source code gets updated: `sudo docker build -t englishpal .`
- Run the application: `sudo docker run -d -p 90:80 -v /home/lanhui/englishpal2/EnglishPal/app/static/frequency:/app/static/frequency -t englishpal`. If you use `sudo docker run -d -p 90:80 -t englishpal`, data will be lost after terminating the program.
- Run the application: `sudo docker run -d -p 90:80 -v /home/lanhui/englishpal2/EnglishPal/app/static/frequency:/app/static/frequency -t englishpal`. If you use `sudo docker run -d -p 90:80 -t englishpal`, data will be lost after terminating the program. If you want to automatically restart the docker image after each system reboot, add the option `--restart=always` after `docker run`.
- Save space: `sudo docker system prune -a -f`
- Save disk space: `sudo docker system prune -a -f`
`build.sh` contains all the above commands. Run "sudo ./build.sh" to rebuild and start the web application.
### Other useful docker commands
#### Other useful docker commands
- `sudo docker ps -a`
- `sudo docker logs image_name`, where image_name could be obtained from `sudo docker ps`.
`build.sh` contains all the above commands. Run "sudo ./build.sh" to rebuild and run the web application.
- `sudo docker logs image_name`, where `image_name` could be obtained from `sudo docker ps`.
@ -68,6 +67,10 @@ All articles are stored in the `article` table in a SQLite file called
To add articles, open and edit `app/static/wordfreqapp.db` using DB Browser for SQLite (https://sqlitebrowser.org).
### Extending an account's expiry date
By default, an account's expiry is 30 days after first sign-up. To extend account's expiry date, open and edit `user` table in `app/static/wordfreqapp.db`. Simply update field `expiry_date`.
### Exporting the database
Export wordfreqapp.db to wordfreqapp.sql using the following commands:
@ -92,33 +95,31 @@ sqlite3 wordfreqapp.db`. Delete wordfreqapp.db first if it exists.
### Uploading wordfreqapp.db to the server
`pscp wordfreqapp.db lanhui@118.*.*.118:/home/lanhui/englishpal/app/static`
`pscp wordfreqapp.db lanhui@118.*.*.118:/home/lanhui/englishpal2/EnglishPal/app/static`
## Feedback
We welcome feedback on EnglishPal.
We welcome feedback on EnglishPal. Feedback examples:
### Respondent 1
### Feedback 1
- "Need a phone app. I use phone a lot. You cannot ask students to use computers."
"Need a phone app. I use phone a lot. You cannot ask students to use computers."
Can take a picture for text. Automatic translation.
### Respondent 2
### Feedback 2
“成为会员”改成“注册”
- “成为会员”改成“注册”
“登出”改成“退出”
- “登出”改成“退出”
“收集生词吧”改成“生词收集栏”
- “收集生词吧”改成“生词收集栏”
“不要自动显示下一篇”
- 不要自动显示下一篇
需要有“上一篇”、“下一篇”
- 需要有“上一篇”、“下一篇”按钮。
@ -137,7 +138,7 @@ EnglishPal's bugs and improvement suggestions are recorded in [Bugzilla](http://
- Usability testing
## Improvements made by contributors
## Improvements made by contributors (incomplete list)
### 朱文绮
@ -159,7 +160,6 @@ too many words that they already know, on the other hand, it can
reduce unnecessary memory occupied by the database, in addition, it
can also improve the simplicity of the page.
More information at: http://118.25.96.118/kanboard/?controller=TaskViewController&action=readonly&task_id=736&token=81a561da57ff7a172da17a480f0d421ff3bc69efbd29437daef90b1b8959
### 占健豪
@ -181,4 +181,14 @@ Demo video link: https://b23.tv/QuB77m
Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=215
*Last modified on 2021-10-17*
### 丁锐
修复了以下漏洞
漏洞:新用户在创建账号时,不需要输入确定密码也可以注册成功,并且新账户可以正常使用。
Bug report: http://118.25.96.118/bugzilla/show_bug.cgi?id=489
*Last modified on 2023-01-30*

View File

@ -68,7 +68,7 @@ def get_today_article(user_word_list, articleID):
s += '<div class="p-3 mb-2 bg-light text-dark">'
article_title = get_article_title(d['text'])
article_body = get_article_body(d['text'])
s += '<p class="display-3">%s</p>' % (article_title)
s += '<p class="display-5">%s</p>' % (article_title)
s += '<p class="lead"><font id="article" size=2>%s</font></p>' % (article_body)
s += '<p><small class="text-muted">%s</small></p>' % (d['source'])
s += '<p><b>%s</b></p>' % (get_question_part(d['question']))

View File

@ -1,10 +1,15 @@
import hashlib
from datetime import datetime
import string
from datetime import datetime, timedelta
from UseSqlite import InsertQuery, RecordQuery
path_prefix = '/var/www/wordfreq/wordfreq/'
path_prefix = './' # comment this line in deployment
def verify_pass(newpass,oldpass):
if(newpass==oldpass):
return True
def verify_user(username, password):
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
@ -18,7 +23,7 @@ def verify_user(username, password):
def add_user(username, password):
start_date = datetime.now().strftime('%Y%m%d')
expiry_date = '20221230'
expiry_date = (datetime.now() + timedelta(days=30)).strftime('%Y%m%d') # will expire after 30 days
# 将用户名和密码一起加密,以免暴露不同用户的相同密码
password = md5(username + password)
rq = InsertQuery(path_prefix + 'static/wordfreqapp.db')
@ -47,6 +52,8 @@ def change_password(username, old_password, new_password):
if not verify_user(username, old_password): # 旧密码错误
return False
# 将用户名和密码一起加密,以免暴露不同用户的相同密码
if verify_pass(new_password,old_password): #新旧密码一致
return False
password = md5(username + new_password)
rq = InsertQuery(path_prefix + 'static/wordfreqapp.db')
rq.instructions_with_parameters("UPDATE user SET password=:password WHERE name=:username", dict(
@ -75,3 +82,32 @@ def md5(s):
'''
h = hashlib.md5(s.encode(encoding='utf-8'))
return h.hexdigest()
class UserName:
def __init__(self, username):
self.username = username
def validate(self):
if len(self.username) > 20:
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
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
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
if c in string.punctuation and c is not '.' and c is not '_':
return f'{c} is not allowed in the user name.'
if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del']:
return 'You used a restricted word as your user name. Please come up with a better one.'
return 'OK'
class WarningMessage:
def __init__(self, s):
self.s = s
def __str__(self):
return UserName(self.s).validate()

View File

@ -1,10 +1,10 @@
from flask import *
from Login import check_username_availability, verify_user, add_user, get_expiry_date, change_password
from Login import check_username_availability, verify_user, add_user, get_expiry_date, change_password, WarningMessage
# 初始化蓝图
accountService = Blueprint("accountService", __name__)
### Sign-up, login, logout ###
@accountService.route("/signup", methods=['GET', 'POST'])
def signup():
@ -19,6 +19,12 @@ def signup():
# POST方法需判断是否注册成功再根据结果返回不同的内容
username = escape(request.form['username'])
password = escape(request.form['password'])
password2 = escape(request.form['password2'])
#! 添加如下代码为了过滤注册时的非法字符
warn = WarningMessage(username)
if str(warn) != 'OK':
return str(warn)
available = check_username_availability(username)
if not available: # 用户名不可用
@ -26,6 +32,8 @@ def signup():
return render_template('signup.html')
elif len(password.strip()) < 4: # 密码过短
return '密码过于简单。'
elif password != password2:
return '确认密码与输入密码不一致!'
else: # 添加账户信息
add_user(username, password)
verified = verify_user(username, password)
@ -42,6 +50,7 @@ def signup():
return '用户名密码验证失败。'
@accountService.route("/login", methods=['GET', 'POST'])
def login():
'''
@ -106,6 +115,13 @@ def reset():
# POST请求用于提交修改后信息
old_password = escape(request.form['old-password'])
new_password = escape(request.form['new-password'])
re_new_password = escape(request.form['re-new-password']) # 确认新密码
if re_new_password != new_password: #验证新密码两次输入是否相同
return '新密码不匹配,请重新输入'
if len(new_password) < 4: #验证新密码长度,原则参照注册模块
return '密码过于简单。(密码长度至少4位)'
flag = change_password(username, old_password, new_password) # flag表示是否修改成功
if flag:
session['logged_in'] = False

View File

@ -6,6 +6,7 @@
# Written permission must be obtained from the author for commercial uses.
###########################################################################
from flask import escape
from Login import *
from Article import *
import Yaml
@ -81,7 +82,7 @@ def mainpage():
:return: 主界面
'''
if request.method == 'POST': # when we submit a form
content = request.form['content']
content = escape(request.form['content'])
f = WordFreq(content)
lst = f.get_freq()
# save history

View File

@ -6,8 +6,8 @@ css:
# 全局引入的js文件地址
js:
head: # 在页面加载之前加载
# - static/js/APlayer.js
# - static/js/Meting.js
- static/js/jquery.js
- static/js/word_operation.js
bottom: # 在页面加载完之后加载
- static/js/fillword.js
- static/js/highlight.js

View File

@ -417,7 +417,7 @@ progress {
}
.lead {
font-size: 1.25rem;
font-size: 2rem;
font-weight: 300
}

View File

@ -0,0 +1,107 @@
/*样式应用于login、signup、reset三个页面*/
.container {
background-color: #FFFFFF;
width: 400px;
height: 500px;
margin: 7em auto;
border-radius: 1.5em;
box-shadow: 0px 11px 35px 2px rgba(0, 0, 0, 0.14);
}
/*增加一个类reset-heading*/
.signin-heading, .reset-heading {
padding-top: 5px;
color: #8C55AA;
font-family: 'Ubuntu', sans-serif;
font-weight: bold;
font-size: 23px;
text-align: center;
}
/*增加2个类.old-password和.new-password*/
.username, .email, .password, .re-password, .old-password, .new-password,.re-new-password {
width: 76%;
color: rgb(38, 50, 56);
font-weight: 700;
font-size: 14px;
letter-spacing: 1px;
background: rgba(136, 126, 126, 0.04);
padding: 10px 20px;
border: none;
border-radius: 20px;
outline: none;
box-sizing: border-box;
border: 2px solid rgba(124, 16, 97, 0.02);
margin-bottom: 50px;
margin-left: 46px;
text-align: center;
margin-bottom: 27px;
font-family: 'Ubuntu', sans-serif;
}
.btn {
width: 50%;
border: none;
border-radius: 20px;
box-sizing: border-box;
border: 2px solid #8C55AA;
margin-bottom: 50px;
margin-left: 90px;
padding: 10px 20px;
}
.btn:hover {
background: #8C55AA;
transition: .5s;
cursor: pointer;
color: #fff;
}
.signup {
display: flex;
justify-content: center;
align-items: center;
}
ul {
position: absolute;
display: flex;
left: 65%;
}
li {
padding: 10px;
margin: 10px;
}
a {
text-decoration: none;
list-style: none;
font-weight: bold;
font-family: 'ink free';
}
.main_menu a {
color: #fff;
font-size: 300px;
}
li :hover {
color: #8C55AA;
transition: .5s;
}
h1 {
font-family: 'ink free';
}
.main_menu h1 {
color: #fff;
}

View File

@ -0,0 +1,5 @@
This folder holds users' vocabulary files.
Each file ends with .pickle.
For example, mrlan.pickle is the vocabulary file for user mrlan.

View File

@ -1,29 +1,68 @@
isRead = true;
isChoose = true;
var reader = window.speechSynthesis; // 全局定义朗读者,以便朗读和暂停
let isRead = true;
let isChoose = true;
let reader = window.speechSynthesis; // 全局定义朗读者,以便朗读和暂停
let current_position = 0; // 朗读文本的当前位置
let original_position = 0; // 朗读文本的初始位置
let to_speak = ""; // 朗读的初始内容
function getWord() {
var word = window.getSelection?window.getSelection():document.selection.createRange().text;
return word;
return window.getSelection ? window.getSelection() : document.selection.createRange().text;
}
function fillinWord(){
var word = getWord();
function fillInWord() {
let word = getWord();
if (isRead) read(word);
if (!isChoose) return;
var element = document.getElementById("selected-words");
const element = document.getElementById("selected-words");
element.value = element.value + " " + word;
}
document.getElementById("text-content").addEventListener("click", fillinWord, false);
document.getElementById("text-content").addEventListener("click", fillInWord, false);
function makeUtterance(str, rate) {
let msg = new SpeechSynthesisUtterance(str);
msg.rate = rate;
msg.lang = "en-US"; // TODO: add language options menu
msg.onboundary = ev => {
if (ev.name == "word") {
current_position = ev.charIndex;
}
}
return msg;
}
const sliderValue = document.getElementById("rangeValue"); // 显示值
const inputSlider = document.getElementById("rangeComponent"); // 滑块元素
inputSlider.oninput = () => {
let value = inputSlider.value; // 获取滑块的值
sliderValue.textContent = value + '×';
if (!reader.speaking) return;
reader.cancel();
let msg = makeUtterance(to_speak.substring(original_position + current_position), value);
original_position = original_position + current_position;
current_position = 0;
reader.speak(msg);
};
function read(s) {
var msg = new SpeechSynthesisUtterance(s);
to_speak = s.toString();
original_position = 0;
current_position = 0;
let msg = makeUtterance(to_speak, inputSlider.value);
reader.speak(msg);
}
function onReadClick() {
isRead = !isRead;
if (!isRead) {
reader.cancel();
}
}
function onChooseClick() {
isChoose = !isChoose;
}
function stopRead() {
reader.cancel();
}

View File

@ -1,94 +1,91 @@
var isHighlight = true;
let isHighlight = true;
function cancelBtnHandler() {
cancel_highLight();
document.getElementById("text-content").removeEventListener("click", fillinWord, false);
document.getElementById("text-content").removeEventListener("touchstart", fillinWord, false);
document.getElementById("text-content").addEventListener("click", fillinWord2, false);
document.getElementById("text-content").addEventListener("touchstart", fillinWord2, false);
cancelHighlighting();
document.getElementById("text-content").removeEventListener("click", fillInWord, false);
document.getElementById("text-content").removeEventListener("touchstart", fillInWord, false);
document.getElementById("text-content").addEventListener("click", fillInWord2, false);
document.getElementById("text-content").addEventListener("touchstart", fillInWord2, false);
}
function showBtnHandler() {
document.getElementById("text-content").removeEventListener("click", fillinWord2, false);
document.getElementById("text-content").removeEventListener("touchstart", fillinWord2, false);
document.getElementById("text-content").addEventListener("click", fillinWord, false);
document.getElementById("text-content").addEventListener("touchstart", fillinWord, false);
document.getElementById("text-content").removeEventListener("click", fillInWord2, false);
document.getElementById("text-content").removeEventListener("touchstart", fillInWord2, false);
document.getElementById("text-content").addEventListener("click", fillInWord, false);
document.getElementById("text-content").addEventListener("touchstart", fillInWord, false);
highLight();
}
function getWord() {
var word = window.getSelection ? window.getSelection() : document.selection.createRange().text;
return word;
return window.getSelection ? window.getSelection() : document.selection.createRange().text;
}
function highLight() {
if (!isHighlight) return;
var txt = document.getElementById("article").innerText;
var sel_word1 = document.getElementById("selected-words");
var sel_word2 = document.getElementById("selected-words2");
if (sel_word1 != null) {
var list = sel_word1.value.split(" ");
for (var i = 0; i < list.length; ++i) {
list[i] = list[i].replace(/(^\s*)|(\s*$)/g, "");
if (list[i] != "" && "<mark>".indexOf(list[i]) == -1 && "</mark>".indexOf(list[i]) == -1) {
txt = txt.replace(new RegExp(list[i], "g"), "<mark>" + list[i] + "</mark>");
let articleContent = document.getElementById("article").innerText; //将原来的.innerText改为.innerHtml使用innerText会把原文章中所包含的<br>标签去除,导致处理后的文章内容失去了原来的格式
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 allWords = ""; //初始化allWords的值避免进入判断后编译器认为allWords未初始化的问题
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) {
list[i] = list[i].replace(/(^\s*)|(\s*$)/g, ""); //消除单词两边的空字符
list[i] = list[i].replace('|', "");
list[i] = list[i].replace('?', "");
if (list[i] !== "" && "<mark>".indexOf(list[i]) === -1 && "</mark>".indexOf(list[i]) === -1) {
//将文章中所有出现该单词word的地方改为" <mark>" + word + "<mark> "。 正则表达式RegExp()中,"\\s"代表单词前后必须要有空格,以防止只对单词中的部分字符高亮的情况出现。
articleContent = articleContent.replace(new RegExp("\\s"+list[i]+"\\s", "g"), " <mark>" + list[i] + "</mark> ");
}
}
}
if (sel_word2 != null) {
var list2 = sel_word2.value.split(" ");
for (var i = 0; i < list2.length; ++i) {
list2[i] = list2[i].replace(/(^\s*)|(\s*$)/g, "");
if (list2[i] != "" && "<mark>".indexOf(list2[i]) == -1 && "</mark>".indexOf(list2[i]) == -1) {
txt = txt.replace(new RegExp(list2[i], "g"), "<mark>" + list2[i] + "</mark>");
}
}
}
document.getElementById("article").innerHTML = txt;
document.getElementById("article").innerHTML = articleContent;
}
function cancel_highLight() {
var txt = document.getElementById("article").innerText;
var sel_word1 = document.getElementById("selected-words");
var sel_word2 = document.getElementById("selected-words2");
if (sel_word1 != null) {
var list = sel_word1.value.split(" ");
for (var i = 0; i < list.length; ++i) {
function cancelHighlighting() {
let articleContent = document.getElementById("article").innerText;//将原来的.innerText改为.innerHtml原因同上
let pickedWords = document.getElementById("selected-words");
const dictionaryWords = document.getElementById("selected-words2");
const list = pickedWords.value.split(" ");
if (pickedWords != null) {
for (let i = 0; i < list.length; ++i) {
list[i] = list[i].replace(/(^\s*)|(\s*$)/g, "");
if (list[i] != "") {
txt = txt.replace("<mark>" + list[i] + "</mark>", "list[i]");
if (list[i] !== "") { //原来判断的代码中替换的内容为“list[i]”这个字符串这明显是错误的我们需要替换的是list[i]里的内容
articleContent = articleContent.replace(new RegExp("<mark>"+list[i]+"</mark>", "g"), list[i]);
}
}
}
if (sel_word2 != null) {
var list2 = sel_word1.value.split(" ");
for (var i = 0; i < list2.length; ++i) {
var list2 = sel_word2.value.split(" ");
if (dictionaryWords != null) {
let list2 = pickedWords.value.split(" ");
for (let i = 0; i < list2.length; ++i) {
list2 = dictionaryWords.value.split(" ");
list2[i] = list2[i].replace(/(^\s*)|(\s*$)/g, "");
if (list2[i] != "") {
txt = txt.replace("<mark>" + list[i] + "</mark>", "list[i]");
if (list2[i] !== "") { //原来代码中替换的内容为“list[i]”这个字符串这明显是错误的我们需要替换的是list[i]里的内容
articleContent = articleContent.replace(new RegExp("<mark>"+list2[i]+"</mark>", "g"), list2[i]);
}
}
}
document.getElementById("article").innerHTML = txt;
document.getElementById("article").innerHTML = articleContent;
}
function fillinWord() {
function fillInWord() {
highLight();
}
function fillinWord2() {
cancel_highLight();
function fillInWord2() {
cancelHighlighting();
}
function ChangeHighlight() {
function toggleHighlighting() {
if (isHighlight) {
isHighlight = false;
cancel_highLight();
cancelHighlighting();
} else {
isHighlight = true;
highLight();
}
}

2
app/static/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,173 @@
function familiar(theWord) {
let username = $("#username").text();
let word = $("#word_" + theWord).text();
let freq = $("#freq_" + theWord).text();
$.ajax({
type:"GET",
url:"/" + username + "/" + word + "/familiar",
success:function(response){
let new_freq = freq - 1;
const allow_move = document.getElementById("move_dynamiclly").checked;
if (allow_move) {
if (new_freq <= 0) {
removeWord(theWord);
} else {
renderWord({ word: theWord, freq: new_freq });
}
} else {
if(new_freq <1) {
$("#p_" + theWord).remove();
} else {
$("#freq_" + theWord).text(new_freq);
}
}
}
});
}
function unfamiliar(theWord) {
let username = $("#username").text();
let word = $("#word_" + theWord).text();
let freq = $("#freq_" + theWord).text();
$.ajax({
type:"GET",
url:"/" + username + "/" + word + "/unfamiliar",
success:function(response){
let new_freq = parseInt(freq) + 1;
const allow_move = document.getElementById("move_dynamiclly").checked;
if (allow_move) {
renderWord({ word: theWord, freq: new_freq });
} else {
$("#freq_" + theWord).text(new_freq);
}
}
});
}
function delete_word(theWord) {
let username = $("#username").text();
let word = theWord.replace('&amp;', '&');
$.ajax({
type:"GET",
url:"/" + username + "/" + word + "/del",
success:function(response){
const allow_move = document.getElementById("move_dynamiclly").checked;
if (allow_move) {
removeWord(theWord);
} else {
$("#p_" + theWord).remove();
}
}
});
}
/*
* interface Word {
* word: string,
* freq: number
* }
* */
/**
* 传入一个词频HTML元素将其解析为Word类型的对象
*/
function parseWord(element) {
const word = element
.querySelector("a.btn.btn-light[role=button]") // 获取当前词频元素的词汇元素
.innerText // 获取词汇值;
const freq = Number.parseInt(element.querySelector(`#freq_${word}`).innerText); // 获取词汇的数量
return {
word,
freq
};
}
/**
* 使用模板将传入的单词转换为相应的HTML字符串
*/
function wordTemplate(word) {
// 这个模板应当与 templates/userpage_get.html 中的 <p id='p_${word.word}' class="new-word" > ... </p> 保持一致
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'
role="button">${word.word}</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-warning" onclick="unfamiliar('${word.word}')" role="button">不熟悉</a>
<a class="btn btn-danger" onclick="delete_word('${word.word}')" role="button">删除</a>
</p>`;
}
/**
* 删除某一词频元素
* 此处word为词频元素对应的单词
*/
function removeWord(word) {
// 根据词频信息删除元素
word = word.replace('&amp;', '&');
const element_to_remove = document.getElementById(`p_${word}`);
if (element_to_remove != null) {
element_to_remove.remove();
}
}
function renderWord(word) {
const container = document.querySelector(".word-container");
// 删除原有元素
removeWord(word.word);
// 插入新元素
let inserted = false;
const new_element = elementFromString(wordTemplate(word));
for (const current of container.children) {
const cur_word = parseWord(current);
// 找到第一个词频比它小的元素,插入到这个元素前面
if (compareWord(cur_word, word) == -1) {
container.insertBefore(new_element, current);
inserted = true;
break;
}
}
// 当word就是词频最小的词时把他补回去
if (!inserted) {
container.appendChild(new_element);
}
// 让发生变化的元素抖动
new_element.classList.add("shaking");
// 移动到该元素
new_element.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"});
// 抖动完毕后删除抖动类
setTimeout(() => {
new_element.classList.remove("shaking");
}, 1600);
}
/**
* 从string中创建一个HTML元素并返回
*/
function elementFromString(string) {
const d = document.createElement('div');
d.innerHTML = string;
return d.children.item(0);
}
/**
* 对比两个单词
* 当first小于second时返回-1
* 当first等于second时返回0
* 当first大于second时返回1
*/
function compareWord(first, second) {
if (first.freq < second.freq) {
return -1;
}
if (first.freq > second.freq) {
return 1;
}
if (first.word < second.word) {
return -1;
}
if (first.word > second.word) {
return 1;
}
return 0;
}

View File

@ -5,7 +5,7 @@
<title>账号过期</title>
</head>
<body>
<p>您的账号{{ username }}过期</p>
<p>您的账号过期(过期日 {{expiry_date}}</p>
<p>为了提高服务质量English Pal 收取会员费用, 每天1元。</p>
<p>请决定你要试用的时间长度,扫描下面支付宝二维码支付。 支付时请注明<i>English Pal Membership Fee</i>。 我们会于12小时内激活账号。</p>
<p><img src="static/donate-the-author-hidden.jpg" width="120px" alt="支付宝二维码" /></p>

View File

@ -5,17 +5,24 @@ You're logged in already!
{% else %}
<meta charset="utf-8" name="viewport" 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">
<div class="container">
<section class="signin-heading">
<h1>Sign In</h1>
</section>
<form action="/login" method="POST">
<p>
<input type="username" name="username" placeholder="邮箱地址、电话号码">
</p>
<p>
<input type="password" name="password" placeholder="密码">
</p>
<p>
<input type="submit" value="登录">
</p>
<input type="text" placeholder="用户名" class="username" name="username" required>
<input type="password" placeholder="密码" class="password" name="password" required>
<button type="submit" class="btn">登录</button>
</form>
</div>
<a href="/signup" class="signup">注册</a>
{% endif %}
{% endblock %}

View File

@ -1,14 +1,22 @@
<html>
<body>
<form action="/reset" method='POST'>
旧密码:
<input type="password" name="old-password" />
<br/>
新密码:
<input type="password" name="new-password" />
<br/>
<input type="submit" name="submit" value="提交" />
<input type="button" name="submit" value="放弃修改" onclick="window.location.href='/{{ username }}'"/>
{% block body %}
<meta charset="utf-8" name="viewport"
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">
<div class="container">
<section class="reset-heading">
<h1>Reset Password</h1>
</section>
<form action="/reset" method="POST">
<input type="password" placeholder="原密码" class="old-password" name="old-password" required>
<input type="password" placeholder="新密码" class="new-password" name="new-password" required>
<input type="password" placeholder="确认新密码" class="re-new-password" name="re-new-password" required>
<input type="submit" name="submit" class="btn" value="提交"/>
<input type="button" name="submit" class="btn" value="放弃修改"
onclick="window.location.href='/{{ username }}'"/>
</form>
</body>
</html>
</div>
{% endblock %}

View File

@ -5,15 +5,25 @@ You're logged in already! <a href="/logout">Logout</a>.
{% else %}
<meta name="viewport" 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">
<p>{{ get_flashed_messages()[0] | safe }}</p>
<p>Sign up here.</p>
<div class="container">
<section class="signin-heading">
<h1>Sign Up</h1>
</section>
<form action="/signup" method="POST">
<p><input type="username" name="username" placeholder="邮箱地址、电话号码" required="required"></p>
<p><input type="password" name="password" placeholder="密码"></p>
<p><input type="submit" value="注册"></p>
<p><input type="username" name="username" placeholder="输入用户名" required="required" class="username"></p>
<p><input type="password" name="password" placeholder="输入密码" required="required" class="password"></p>
<p><input type="password" name="password2" placeholder="确认密码" required="required" class="password" ></p>
<button type="submit" class="btn">注册</button>
</form>
</div>
{% endif %}
{% endblock %}

View File

@ -19,23 +19,50 @@
{% endif %}
<title>EnglishPal Study Room for {{ username }}</title>
<style>
.shaking {
animation: shakes 1600ms ease-in-out;
}
@keyframes shakes {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 50% { 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); }
}
</style>
</head>
<body>
<div class="container-fluid">
<p><b>English Pal for <font color="red">{{ username }}</font></b>
<p><b>English Pal for <font id="username" color="red">{{ username }}</font></b>
<a class="btn btn-secondary" href="/logout" role="button">退出</a>
<a class="btn btn-secondary" href="/reset" role="button">重设密码</a>
</p>
{{ flashed_messages|safe }}
<p><a class="btn btn-success" href="/{{ username }}/reset" role="button"> 下一篇 Next Article </a></p>
<a class="btn btn-success" href="/{{ username }}/reset" role="button"> 下一篇 Next Article </a>
{% if session.get('articleID') != session.get('old_articleID') %}
{% if session.get('old_articleID') != None %}
<a class="btn btn-success" href="/{{ username }}/back" role="button"> 上一篇 Previous Article </a>
{% endif%}
{% endif %}
<p><b>阅读文章并回答问题</b></p>
<div id="text-content">{{ today_article|safe }}</div>
<input type="checkbox" onclick="ChangeHighlight()" checked/>生词高亮
<input type="checkbox" onclick="toggleHighlighting()" checked/>生词高亮
<input type="checkbox" onclick="onReadClick()" checked/>大声朗读
<input type="checkbox" onclick="onChooseClick()" checked/>划词入库
<div class="range">
<div class="field">
<div class="sliderValue">
<span id="rangeValue">1×</span>
</div>
<input type="range" id="rangeComponent" min="0.5" max="2" value="1" step="0.25" "/>
</div>
</div>
<p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p>
<form method="post" action="/{{ username }}">
<textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/>
@ -54,41 +81,30 @@
{% endif %}
{% if d_len > 0 %}
<p><b>我的生词簿</b></p>
<p>
<b>我的生词簿</b>
<label for="move_dynamiclly">
<input type="checkbox" name="move_dynamiclly" id="move_dynamiclly" checked>
允许动态调整顺序
</label>
</p>
<a name="aaa"></a>
<div class="word-container">
{% for x in lst3 %}
{% set word = x[0] %}
{% set freq = x[1] %}
{% if session.get('thisWord') == x[0] and session.get('time') == 1 %}
<a name="aaa"></a>
{% endif %}
{% if freq > 1 %}
<p class="new-word">
<a class="btn btn-light" href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'
<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'
role="button">{{ word }}</a>
(
<a title="{{ word }}">{{ freq }}</a>
)
<a class="btn btn-success" href={{ username }}/{{ word }}/familiar role="button">熟悉</a>
<a class="btn btn-warning" href={{ username }}/{{ word }}/unfamiliar role="button">不熟悉</a>
<a class="btn btn-danger" href={{ username }}/{{ word }}/del role="button">删除</a>
( <a id="freq_{{ word }}" title="{{ word }}">{{ freq }}</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-danger" onclick="delete_word('{{ word }}')" role="button">删除</a>
</p>
{% else %}
<p class="new-word">
<a class="btn btn-light" href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'
role="button">{{ word }}</a>
(
<a title="{{ word }}">{{ freq }}</a>
)
<a class="btn btn-success" href={{ username }}/{{ word }}/familiar role="button">熟悉</a>
<a class="btn btn-warning" href={{ username }}/{{ word }}/unfamiliar role="button">不熟悉</a>
<a class="btn btn-danger" href={{ username }}/{{ word }}/del role="button">删除</a>
</p>
{% endif %}
{% else %}
<a href='http://youdao.com/w/eng/{{ word }}/#keyfrom=dict2.index'>{{ word }}</a>{{ freq }}
{% endfor %}
</div>
<input id="selected-words2" type="hidden" value="{{ words }}">
{% endif %}
</div>

View File

@ -20,7 +20,7 @@
<title>EnglishPal Study Room for {{username}}</title>
</head>
<body>
<p>勾选认识的单词</p>
<p>取消勾选认识的单词</p>
<form method="post" action="/{{username}}/mark">
<input type="submit" name="add-btn" value="加入我的生词簿"/>
{% for x in lst %}
@ -30,7 +30,7 @@
:
<a href='http://youdao.com/w/eng/{{word}}/#keyfrom=dict2.index' title={{word}}>{{word}}</a>
({{x[1]}})
<input type="checkbox" name="marked" value={{word}}>
<input type="checkbox" name="marked" value="{{word}}" checked>
</p>
{% endfor %}

View File

@ -0,0 +1,91 @@
from time import sleep
from selenium import webdriver
# 获取浏览器驱动,并且打开响应的网址
driver = webdriver.Chrome(executable_path="C:\Program Files (x86)\Google\ChromeDriver\chromedriver.exe")
HOME_PAGE = "http://127.0.0.1:5000/"
def test_word_operation():
try:
login()
unfamiliar()
familiar()
delete()
finally:
driver.quit()
def login():
driver.get(HOME_PAGE)
assert 'English Pal -' in driver.page_source
# login
elem = driver.find_element_by_link_text('登录')
elem.click()
sleep(2)
uname = 'peter'
password = 'peter'
elem = driver.find_element_by_name('username')
elem.send_keys(uname)
elem = driver.find_element_by_name('password')
elem.send_keys(password)
# find the login button
elem = driver.find_element_by_xpath('/html/body/form/p[3]/input')
elem.click()
assert 'EnglishPal Study Room for ' + uname in driver.title
def familiar():
sleep(5)
elem = driver.find_element_by_xpath('//*[@id="p_0"]/a[3]')
count = int(elem.find_element_by_xpath('//*[@id="freq_0"]').text)
loop = 3
for i in range(loop):
elem.click()
sleep(1)
new_count = int(driver.find_element_by_xpath('//*[@id="freq_0"]').text)
assert count - loop == new_count
def unfamiliar():
sleep(5)
elem = driver.find_element_by_xpath('//*[@id="p_0"]/a[4]')
count = int(elem.find_element_by_xpath('//*[@id="freq_0"]').text)
loop = 2
for i in range(loop):
elem.click()
sleep(1)
new_count = int(driver.find_element_by_xpath('//*[@id="freq_0"]').text)
assert count + loop == new_count
def delete():
sleep(3)
word = driver.find_element_by_xpath('//*[@id="word_0"]').text
elem = driver.find_element_by_xpath('//*[@id="p_0"]/a[5]')
elem.click()
sleep(5)
driver.refresh()
driver.refresh()
driver.refresh()
find_word = word in driver.page_source
assert find_word is False

View File

@ -29,12 +29,25 @@ def user_reset(username):
:param username: 用户名
:return: 返回页面内容
'''
session['old_articleID'] = session.get('articleID')
if request.method == 'GET':
session['articleID'] = None
return redirect(url_for('user_bp.userpage', username=username))
else:
return 'Under construction'
@userService.route("/<username>/back", methods=['GET'])
def user_back(username):
'''
用户界面
:param username: 用户名
:return: 返回页面内容
'''
if request.method == 'GET':
session['articleID'] = session.get('old_articleID')
return redirect(url_for('user_bp.userpage', username=username))
@userService.route("/<username>/<word>/unfamiliar", methods=['GET', 'POST'])
def unfamiliar(username, word):
@ -48,7 +61,7 @@ def unfamiliar(username, word):
pickle_idea.unfamiliar(user_freq_record, word)
session['thisWord'] = word # 1. put a word into session
session['time'] = 1
return redirect(url_for('user_bp.userpage', username=username))
return "success"
@userService.route("/<username>/<word>/familiar", methods=['GET', 'POST'])
@ -63,7 +76,7 @@ def familiar(username, word):
pickle_idea.familiar(user_freq_record, word)
session['thisWord'] = word # 1. put a word into session
session['time'] = 1
return redirect(url_for('user_bp.userpage', username=username))
return "success"
@userService.route("/<username>/<word>/del", methods=['GET', 'POST'])
@ -77,7 +90,7 @@ def deleteword(username, word):
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
pickle_idea2.deleteRecord(user_freq_record, word)
flash(f'<strong>{word}</strong> is no longer in your word list.')
return redirect(url_for('user_bp.userpage', username=username))
return "success"
@userService.route("/<username>", methods=['GET', 'POST'])
@ -94,7 +107,7 @@ def userpage(username):
# 用户过期
user_expiry_date = session.get('expiry_date')
if datetime.now().strftime('%Y%m%d') > user_expiry_date:
return render_template('expiry.html')
return render_template('expiry.html', expiry_date=user_expiry_date)
# 获取session里的用户名
username = session.get('username')
@ -102,7 +115,7 @@ def userpage(username):
user_freq_record = path_prefix + 'static/frequency/' + 'frequency_%s.pickle' % (username)
if request.method == 'POST': # when we submit a form
content = request.form['content']
content = escape(request.form['content'])
f = WordFreq(content)
lst = f.get_freq()
return render_template('userpage_post.html',username=username,lst = lst, yml=Yaml.yml)

View File

@ -39,7 +39,7 @@ def file2str(fname):#文件转字符
def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。
special_characters = '_©~=+[]*&$%^@.,?!:;#()"“”—‘’' # 把里面的字符都去掉
special_characters = '\_©~<=>+-/[]*&$%^@.,?!:;#()"“”—‘’{}|' # 把里面的字符都去掉
for c in special_characters:
s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况
s = s.replace('--', ' ')

View File

@ -1,6 +1,6 @@
#!/bin/sh
DEPLOYMENT_DIR=/home/lanhui/EnglishPal
DEPLOYMENT_DIR=/home/lanhui/englishpal2/EnglishPal
cd $DEPLOYMENT_DIR
# Stop service
@ -11,7 +11,7 @@ sudo docker rm EnglishPal
sudo docker build -t englishpal .
# Run the application
sudo docker run -d --name EnglishPal -p 90:80 -v ${DEPLOYMENT_DIR}/app/static/frequency:/app/static/frequency -v ${DEPLOYMENT_DIR}/app/static/:/app/static/ -t englishpal # for permanently saving data
sudo docker run --restart=always -d --name EnglishPal -p 90:80 -v ${DEPLOYMENT_DIR}/app/static/frequency:/app/static/frequency -v ${DEPLOYMENT_DIR}/app/static/:/app/static/ -t englishpal # for permanently saving data
# Save space. Run it after sudo docker run
sudo docker system prune -a -f