Compare commits

..

255 Commits

Author SHA1 Message Date
Lan Hui b43174ffba Resolve merge conflicts 2024-09-01 07:35:50 +08:00
mrlan 74f47b786e Merge pull request 'Fix bug 547' (#146) from Bug547-FanWenQi-Adapted into Alpha-snapshot20240618
Reviewed-on: #146
2024-09-01 07:28:15 +08:00
Lan Hui d00432cee1 Resolve merge conflicts 2024-09-01 07:27:46 +08:00
mrlan 19ea14b38c Merge pull request '提供更便利的获取用户单词表的方法,以json数据格式返回' (#93) from SPM2023S-QianJunQi into Alpha-snapshot20240618
Reviewed-on: #93
2024-08-31 07:39:00 +08:00
Lan Hui 621ac24991 Give the blueprint a better name: apiService 2024-08-31 07:38:30 +08:00
Lan Hui ceb5f14ee9 Simplify wordCMD.py and rename it to api_service.py 2024-08-31 07:35:50 +08:00
Lan Hui 8f31b030ea Resolve merge conflicts 2024-08-31 06:59:46 +08:00
mrlan 909119b587 Merge pull request 'DONE: Bug536-Jiangwangzhe' (#138) from Bug536-Jiangwangzhe into Alpha-snapshot20240618
Reviewed-on: #138
2024-08-30 08:32:48 +08:00
Lan Hui e6c945bac7 fillword.js & highlight.js: fix JavaScript errors on the front page 2024-08-30 08:32:09 +08:00
Lan Hui 0c6616d52c Resolve merge conflicts 2024-08-30 08:11:03 +08:00
mrlan 44d9c39d4b Merge pull request 'DONE: Bug540-XiongJiaming' (#125) from Bug540-XiongJiaming into Alpha-snapshot20240618
Reviewed-on: #125
2024-08-29 07:53:48 +08:00
Lan Hui b2e11aea6f 将标记文章按钮移到页面上方;标记文章时不要alert 2024-08-29 07:52:58 +08:00
Lan Hui 2bec0642dd Resolve merge conflicts 2024-08-29 07:34:32 +08:00
Lan Hui 62dd580974 在加入生词簿前,去掉全角方括号【】 2024-08-28 08:08:31 +08:00
mrlan 47a359c798 Merge pull request 'Bug579-LuKangyang' (#172) from Bug579-LuKangyang into Alpha-snapshot20240618
Reviewed-on: #172
2024-08-28 07:56:09 +08:00
Lan Hui fb190b0563 Resolve merge conflict 2024-08-28 07:53:26 +08:00
mrlan 262604e761 Merge pull request 'BUG543-JiWenkai' (#153) from BUG543-JiWenkai into Alpha-snapshot20240618
Reviewed-on: #153
2024-08-28 07:50:49 +08:00
Lan Hui 61a0b39507 Resolve merge conflict 2024-08-28 07:48:50 +08:00
mrlan 391e859d30 Merge pull request 'Bug574-ChenLingjie2' (#160) from Bug574-ChenLingjie2 into Alpha-snapshot20240618
Reviewed-on: #160
2024-08-28 07:43:37 +08:00
Lan Hui c453317ad8 Make sure the user name is not on the black list before proceeding 2024-08-28 07:42:05 +08:00
Lan Hui f9003ece69 Remove unused import 2024-08-28 07:23:52 +08:00
Lan Hui 146781df50 Resolve merge conflict 2024-08-28 07:23:30 +08:00
mrlan 329732038f Merge pull request 'Bug573-PanBinjie' (#169) from Bug573-PanBinjie into Alpha-snapshot20240618
Reviewed-on: #169
2024-08-27 11:30:23 +08:00
Lan Hui 0bfb35b978 Make translation tooltip work 2024-08-27 11:29:09 +08:00
Lan Hui 13a091be72 Merge branch 'Bug573-PanBinjie' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug573-PanBinjie 2024-08-27 10:41:33 +08:00
mrlan 30ff57909e Merge pull request 'word_operation.js: do not alert each time' (#179) from Bug570_CaiShuHuang into Alpha-snapshot20240618
Reviewed-on: #179
2024-08-27 08:19:54 +08:00
Lan Hui fb1f927e83 word_operation.js: do not alert each time 2024-08-27 08:19:22 +08:00
mrlan 3b6d88bd68 Merge pull request 'Bug570_CaiShuHuang' (#170) from Bug570_CaiShuHuang into Alpha-snapshot20240618
Reviewed-on: #170
2024-08-27 08:18:44 +08:00
Lan Hui eff0d01fa0 word_operation.js: Resolve conflict 2024-08-27 08:15:18 +08:00
mrlan 8f38e5fd87 Merge pull request 'Bug571-TongQi' (#166) from Bug571-TongQi into Alpha-snapshot20240618
Reviewed-on: #166
2024-08-27 08:03:04 +08:00
mrlan b39cf2e4fa Merge pull request 'Remove wordfreqapp.db from app/static' (#178) from Bug578-ChenChen2 into Alpha-snapshot20240618
Reviewed-on: #178
2024-08-27 07:59:02 +08:00
Lan Hui 6f4b91fd73 Remove wordfreqapp.db from app/static 2024-08-27 07:58:26 +08:00
mrlan 09beaff831 Merge pull request 'Bug578-ChenChen' (#167) from Bug578-ChenChen into Alpha-snapshot20240618
Reviewed-on: #167
2024-08-27 07:53:20 +08:00
Lan Hui ebfe7416e6 userpaget_get.html: Resolve conflict 2024-08-27 07:43:57 +08:00
mrlan 98027158b5 Merge pull request 'Bug577-JiangXueQin' (#163) from Bug577-JiangXueQin into Alpha-snapshot20240618
Reviewed-on: #163
2024-08-27 07:39:25 +08:00
Lan Hui 92a1af4222 userpaget_get.html: Resolve conflict 2024-08-27 07:39:04 +08:00
mrlan 79dff9a3b5 Merge pull request 'Bug576-XiaBaizhi' (#162) from Bug576-XiaBaizhi into Alpha-snapshot20240618
Reviewed-on: #162
2024-08-27 07:21:05 +08:00
Lan Hui bd4fb6846b userpaget_get.html: Do not disable the 'Next Article' button 2024-08-27 07:20:20 +08:00
Lan Hui 2e203699c0 userpaget_get.html: Resolve merge conflicts 2024-08-27 07:15:22 +08:00
mrlan d502b3f474 Merge pull request 'Bug564-JiangChao' (#152) from Bug564-JiangChao into Alpha-snapshot20240618
Reviewed-on: #152
2024-08-26 10:29:26 +08:00
Lan Hui 66899376c3 userpaget_get.html: Resolve merge conflicts 2024-08-26 10:27:43 +08:00
mrlan 6cc7f043da Merge pull request 'Bug572-ZhongYi2' (#159) from Bug572-ZhongYi2 into Alpha-snapshot20240618
Reviewed-on: #159
2024-08-26 10:05:45 +08:00
Lan Hui 1d80fa8886 userpaget_get.html: Resolve merge conflicts 2024-08-26 10:03:47 +08:00
mrlan dc3ef2bc9f Merge pull request 'Bug533-ZhangXuDong' (#161) from Bug533-ZhangXuDong into Alpha-snapshot20240618
Reviewed-on: #161
2024-08-26 09:49:38 +08:00
Lan Hui 9eac40493b userpaget_get.html and mainpage_gethtml: improve display information. 2024-08-26 09:49:00 +08:00
Lan Hui fe6a28a81d userpaget_get.html: Resolve merge conflicts 2024-08-26 09:41:53 +08:00
mrlan bf8bb1a6bc Merge pull request 'Bug568-SongHaiyan' (#158) from Bug568-SongHaiyan into Alpha-snapshot20240618
Reviewed-on: #158
2024-08-26 09:28:18 +08:00
mrlan 7a97e25b8c Merge pull request 'Bug567-YuZheChen' (#155) from Bug567-YuZheChen into Alpha-snapshot20240618
Reviewed-on: #155
2024-08-26 09:18:24 +08:00
卢康阳 eb3268a139 Fix bug 579 2024-07-06 16:34:54 +08:00
卢康阳 220e108ee1 解决合并冲突 2024-07-06 16:33:43 +08:00
潘彬杰 af59afc7e0 解决合并冲突 2024-07-05 17:54:11 +08:00
蔡书煌 3049a4314a 只是修改名字 2024-07-05 13:35:16 +08:00
Caroline d8263d17e0 Fix bug 570 2024-07-04 19:50:34 +08:00
宋海燕 794dcf399c fix Bug571 2024-07-04 19:22:20 +08:00
陈宇航 9bbd3a978d Fix bug 533 2024-07-04 15:37:37 +08:00
夏栢芝 6b3efad1dc Merge branch 'Alpha-snapshot20240618' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug576-XiaBaizhi 2024-07-04 15:05:36 +08:00
陈晨 faf5ec14a4 Fix bug 578 2024-07-04 15:04:34 +08:00
“jxq” 70fc469f5e Fix bug 577 2024-07-04 15:03:31 +08:00
陈晨 9dab83219a Fix bug 578 2024-07-04 15:02:57 +08:00
“jxq” 7bf1a958b5 Fix bug 577 2024-07-04 15:01:22 +08:00
“jxq” 55eac68160 Merge branch 'Alpha-snapshot20240618' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug577-JiangXueQin 2024-07-04 14:56:26 +08:00
“jxq” dc3fd67b02 解决合并冲突 2024-07-04 14:56:20 +08:00
Kikky666 02f3c4fdfa Fix bug 576-XiaBaizhi 2024-07-04 11:35:59 +08:00
陈灵婕 109a9447a7 Fix bug 574 2024-07-04 11:16:32 +08:00
陈灵婕 84e0fc51f7 解决合并冲突 2024-07-04 11:13:47 +08:00
钟埸 18472acd3d Fix bug 572 2024-07-04 11:04:38 +08:00
钟埸 75c96a60df 解决合并冲突 2024-07-04 11:00:17 +08:00
张旭东 5657d8d5ee Fix Bug533 2024-07-04 10:44:41 +08:00
张旭东 1f718e201f init 2024-07-04 10:18:47 +08:00
宋海燕 7cf9c7cf56 fix Bug568 2024-07-04 09:54:09 +08:00
Yzccc 6633df7b70 Fix bug 567 2024-07-04 09:33:30 +08:00
Yzccc ccf22af9df Fix bug 567 2024-07-04 09:29:55 +08:00
姜潮 3383e411ec Fix bug 564 添加注释版 2024-07-03 15:19:42 +08:00
Fighoh db66c8ed86 fix BUG543 2024-07-03 14:36:01 +08:00
Caroline 9215339b1e Merge branch 'Alpha-snapshot20240618' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug570_CaiShuHuang 2024-07-03 10:11:16 +08:00
Fighoh cb576b40ed init 2024-07-03 10:03:55 +08:00
姜潮 f4488672ec Merge branch 'Alpha-snapshot20240618' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug564-JiangChao 2024-07-03 09:46:06 +08:00
Caroline eb2051ca3f Fix bug 570 2024-07-03 09:45:44 +08:00
Lan Hui 00ae770195 Merge branch 'Alpha-snapshot20240618' of http://118.25.96.118:3000/mrlan/EnglishPal into Alpha-snapshot20240618 2024-07-03 07:40:38 +08:00
Lan Hui 3db629b57d Update version flag on the front page 2024-07-03 07:39:33 +08:00
姜潮 54d09469f5 修复Bug564 2024-07-02 16:19:24 +08:00
姜潮 4b06915dc4 解决合并冲突 2024-07-02 16:17:43 +08:00
Caroline 8664da12de 删了几条注释 2024-07-02 16:07:26 +08:00
Caroline 2d765b5c63 随机抽取10个生词,并显示 2024-07-02 16:03:03 +08:00
梁自月 be7ae9e296 fix bug561 2024-06-27 17:06:54 +08:00
Lan Hui 2095550f33 Fix bug 547 2024-06-22 09:04:25 +08:00
Fuxinyan 1c08e80236 Fix Bug 536 2024-06-04 13:58:47 +08:00
熊佳明 d2d383f21a Fix bug 540 2024-06-03 14:03:27 +08:00
熊佳明 d83809dcc7 Fix bug 540 2024-06-03 13:49:53 +08:00
熊佳明 2785ded52b 解决冲突 2024-06-03 11:58:03 +08:00
lixiaofeng e3db7c30b0 上传文件至 app/static/js 2024-05-28 19:55:38 +08:00
lixiaofeng 9f03cc49fa 上传文件至 app/static/js 2024-05-28 19:55:26 +08:00
lixiaofeng eed852cd66 上传文件至 app/templates 2024-05-28 19:55:07 +08:00
lixiaofeng c6f8a3448d 上传文件至 app/test 2024-05-28 19:54:46 +08:00
黄慧玲 d8af2a7e54 Merge pull request 'Bug545-HuangHuiLing' (#122) from Bug545-HuangHuiLing into Alpha-snapshot20230621
Reviewed-on: #122
2024-05-28 13:36:01 +08:00
1994836463@qq.com a0c9b82ee7 参考了一下tangxinyuan小组的写法,将选取单词的方法由模拟鼠标选取单词改为根据id获取文章,随机从文章中挑选一个单词放入生词框中,这样是一定可以选取到文章中的单词。
然后根据span标签定位到选择的生词,双击再次选择,从而在生词框中删除该单词
2024-05-27 14:35:56 +08:00
1994836463@qq.com 4a42c5c22c 我又将tangxinyuan小组的测试代码改回去了,我改完我的测试代码之后,只有使用未经我修改的tangxinyuan小组的测试代码才能通过。 2024-05-27 14:26:54 +08:00
1994836463@qq.com 101c359596 我将tangxinyuan小组的测试文件改了一下,只是改了一下点击按钮的方法,将点击退出按钮,使用ActionChains去点击,因为我测试的时候,用driver去点击大部分会报出'退出'的按钮被浏览器拦截的错误, 2024-05-26 11:32:39 +08:00
1994836463@qq.com 64a82bee22 测试文件也进行了修改,为了避免选择的单词出现空格的情况,增加了一个判断,若选中空格,则更换文章再次选择,这样总会选择到非空格的单词。 2024-05-26 11:27:15 +08:00
1994836463@qq.com 9537024339 再次选择高亮单词,由于标记高亮单词的标签由<mark>改为了<span>,所以在取消单词高亮状态时将标签均改为了<span>,替换时的代码也冗余了,所以也进行了一部分删除。 2024-05-26 11:22:20 +08:00
1994836463@qq.com 85a3a9ce6a 这里的代码写多余了,删除了一部分 2024-05-26 11:19:28 +08:00
1994836463@qq.com f3b9fd8790 change 2024-05-25 15:24:49 +08:00
1994836463@qq.com 624426b6cf change 2024-05-25 15:20:31 +08:00
1994836463@qq.com d1589f6062 time.sleep(1) 2024-05-25 15:14:31 +08:00
1994836463@qq.com fcd2c83904 change 2024-05-25 12:03:37 +08:00
1994836463@qq.com 29a673cd92 change 2024-05-25 11:59:38 +08:00
李思楠 8cbc7c9a0c 修复快速点击下一页按钮点击频率过快时页面跳转到未知名页面 2024-05-24 22:00:08 +08:00
1994836463@qq.com 1bec0bd107 Merge remote-tracking branch 'origin/Bug545-HuangHuiLing' into Bug545-HuangHuiLing 2024-05-21 20:35:38 +08:00
1994836463@qq.com 630cb6befa delete 2024-05-21 20:34:28 +08:00
黄慧玲 5dbdf60a2c Merge branch 'Alpha-snapshot20230621' into Bug545-HuangHuiLing 2024-05-21 20:02:08 +08:00
1994836463@qq.com 101506a511 test and fix 2024-05-21 20:00:32 +08:00
丁晟晔 d8e4fbbb2d Merge pull request 'Bug551-DingZeYu' (#114) from Bug551-DingZeYu into Alpha-snapshot20230621
Reviewed-on: #114
2024-05-20 16:12:25 +08:00
1994836463@qq.com 9b3551bbc8 fix 2024-05-20 15:30:38 +08:00
1994836463@qq.com 90ac789bb4 Merge remote-tracking branch 'origin/Bug545-HuangHuiLing' into Bug545-HuangHuiLing
# Conflicts:
#	app/static/js/fillword.js
#	app/static/js/highlight.js
2024-05-20 15:26:30 +08:00
1994836463@qq.com 8575a0dc33 fix 2024-05-20 15:25:34 +08:00
黄慧玲 3373ba1429 Merge pull request 'Bug545-HuangHuiLing' (#120) from Bug545-HuangHuiLing into Alpha-snapshot20230621
Reviewed-on: #120
2024-05-20 15:16:09 +08:00
黄慧玲 d296580b3b Merge branch 'Alpha-snapshot20230621' into Bug545-HuangHuiLing 2024-05-20 15:13:50 +08:00
Lan Hui a4db1edffb Close the web browswer after finishing each test case 2024-05-20 13:28:23 +08:00
丁晟晔 49ead93bcb 更新 app/test/test_bug551_DingZeYu.py 2024-05-20 08:24:36 +08:00
丁晟晔 9afd38a09a 更新 app/test/test_bug551_DingZeYu.py 2024-05-20 08:21:02 +08:00
丁晟晔 d97743649a 更新 app/test/test_bug551_DingZeYu.py 2024-05-19 22:51:32 +08:00
丁晟晔 9f6a007426 更新 app/test/test_bug551_DingZeYu.py 2024-05-19 21:21:25 +08:00
丁晟晔 7fe3feae9a 更新 app/test/test_bug551_DingZeYu.py 2024-05-18 09:23:36 +08:00
丁晟晔 b2a53a0e40 更新 app/test/test_bug551_DingZeYu.py 2024-05-18 09:18:22 +08:00
丁晟晔 0301c39cf0 更新 app/test/test_bug551_DingZeYu.py 2024-05-18 09:14:05 +08:00
丁晟晔 5857395251 更新 app/test/test_bug551_DingZeYu.py 2024-05-18 09:08:10 +08:00
丁晟晔 77ed63441b 更新 app/static/css/bootstrap.css 2024-05-14 23:26:53 +08:00
丁晟晔 b19eabd225 更新 app/static/config.yml 2024-05-14 23:26:11 +08:00
丁晟晔 b9d0ad4a15 上传文件至 app/static/css 2024-05-14 23:24:26 +08:00
丁晟晔 aca827a912 更新 app/static/css/bootstrap.css 2024-05-14 23:24:06 +08:00
丁晟晔 23f0dfd8ca 更新 app/test/test_bug551_DingZeYu.py
我这边好像没有helper文件所以我直接判断有没有这个账号,如果没有就注册一个来进行测试。这样子可以吗
2024-05-12 13:39:14 +08:00
丁晟晔 6ee94d1610 上传文件至 app/test 2024-05-06 11:48:19 +08:00
丁晟晔 416f40222e 删除 app/test/test_bug551_DingZeYu.py 2024-05-06 11:46:39 +08:00
丁晟晔 66f5c28ead 上传文件至 app/test 2024-05-06 11:46:15 +08:00
丁晟晔 c9c0ef60d8 Merge branch 'Alpha-snapshot20230621' into Bug551-DingZeYu 2024-05-06 11:34:44 +08:00
唐娇 a4e64ee4a0 Merge pull request 'Bug528-TangJiao' (#103) from Bug528-TangJiao into Alpha-snapshot20230621
Reviewed-on: #103
2024-05-05 21:27:38 +08:00
唐娇 1d23062c47 Merge branch 'Alpha-snapshot20230621' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug528-TangJiao 2024-04-29 13:41:09 +08:00
林杉 df34770af0 Merge pull request 'Bug553_LinShan' (#104) from Bug553_LinShan into Alpha-snapshot20230621
Reviewed-on: #104
2024-04-25 15:12:54 +08:00
林杉 b626df7c4b Merge branch 'Alpha-snapshot20230621' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug553_LinShan 2024-04-24 18:15:04 +08:00
唐娇 184656230f Merge branch 'Alpha-snapshot20230621' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug528-TangJiao 2024-04-23 17:35:07 +08:00
唐欣媛 2d9aa8fa61 Merge pull request 'Bug544-TangXinyuan' (#105) from Bug544-TangXinyuan into Alpha-snapshot20230621
Reviewed-on: #105
2024-04-22 23:06:54 +08:00
林杉 bd5f8f63f5 Merge branch 'Bug553_LinShan' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug553_LinShan 2024-04-22 12:47:50 +08:00
林杉 2500fa5fc8 Fix bug553 2024-04-22 12:46:31 +08:00
AliasJeff ddcc5206d5 Merge branch 'Alpha-snapshot20230621' into Bug544-TangXinyuan
merge latest branch
2024-04-22 10:37:33 +08:00
林杉 6b3ad77c44 Merge branch 'Alpha-snapshot20230621' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug553_LinShan 2024-04-22 09:11:29 +08:00
唐娇 739094e844 Fix Test File 2024-04-22 04:59:51 +08:00
唐娇 8d76133cca Merge branch 'Alpha-snapshot20230621' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug528-TangJiao 2024-04-22 04:57:36 +08:00
Lan Hui 6dbb1e2c06 No webdriver manager 2024-04-21 15:40:35 +08:00
Lan Hui 8ef62395ed Revise README.md 2024-04-21 15:33:45 +08:00
Lan Hui 374da86174 Why not use PyPI's webdriver-manager? 2024-04-21 15:31:39 +08:00
丁晟晔 393264a6fe 更新 app/static/js/highlight.js 2024-04-19 10:51:18 +08:00
丁晟晔 d5c76674da 更新 app/static/css/bootstrap.css 2024-04-19 10:50:43 +08:00
丁晟晔 b8222c951b revert 4120e7e54b
revert Fix bug 551
2024-04-19 10:49:41 +08:00
dingchengye 4120e7e54b Fix bug 551 2024-04-19 10:06:48 +08:00
AliasJeff 8e769587cf simplify module import path 2024-04-19 08:50:41 +08:00
Lan Hui 9aa718b236 Simplify the test code, use the Edge WebDriver 2024-04-18 20:06:02 +08:00
Lan Hui 230e8e92dc Merge branch 'Bug553_LinShan' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug553_LinShan 2024-04-18 19:39:53 +08:00
林杉 4ea6d9aeed Fix Bug553 2024-04-16 08:48:55 +08:00
林杉 768c81828d Fix Bug553 2024-04-16 08:46:16 +08:00
AliasJeff 9901d887e0 revert conftest.py 2024-04-15 11:12:35 +08:00
AliasJeff 498639a753 signup in unit test 2024-04-15 11:08:06 +08:00
唐娇 1ded133056 Fix bug 528 2024-04-15 06:23:48 +08:00
唐娇 db66b59513 Fix Test Files 2024-04-15 06:17:01 +08:00
唐娇 692d8cf453 Merge branch 'Bug528-TangJiao' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug528-TangJiao 2024-04-15 06:14:30 +08:00
唐娇 426d131f64 Fix Text Files 2024-04-15 06:12:41 +08:00
AliasJeff 4fe96cfc9c refactor: Implicitly use fixtures in conftest.py 2024-04-11 17:09:12 +08:00
Lan Hui e8fbccdcf7 README.md: do not forget to run launch the web app first before running the test 2024-04-11 07:08:49 +08:00
AliasJeff 93264da3d9 Merge branch 'Alpha-snapshot20230621' into Bug544-TangXinyuan
# Conflicts:
#	app/test/conftest.py
2024-04-10 22:09:27 +08:00
Lan Hui 64b9c51fab Define a fixture that restarts flask app for each test run 2024-04-10 14:44:23 +08:00
AliasJeff 0fd1592036 rename unit test file 2024-04-10 08:55:57 +08:00
AliasJeff 46233ead1e Merge branch 'Alpha-snapshot20230621' into Bug544-TangXinyuan
# Conflicts:
#	app/test/conftest.py
2024-04-09 22:17:54 +08:00
AliasJeff b5bde9d33d test: update test file, update username/password config, add pytest to requirements.txt 2024-04-09 22:09:50 +08:00
AliasJeff 83491ce28c refactor: Add comments & optimize code 2024-04-09 21:21:19 +08:00
Lan Hui 77a3adb546 Define fixture 'restore_sqlite_database' that will be automatically used to restore the database before starting each test 2024-04-09 20:06:30 +08:00
Lan Hui 4f91659713 How to run pytest 2024-04-09 16:13:05 +08:00
Lan Hui 083cbfd040 Ignore app/test/assets 2024-04-09 16:01:44 +08:00
Lan Hui 0dc253bc19 Ignore pytest_report.html 2024-04-09 15:58:47 +08:00
Lan Hui a4608db424 Correct code comments 2024-04-09 13:21:44 +08:00
Lan Hui 0a63c5354a Make test_add_word.py work again 2024-04-09 12:11:30 +08:00
林杉 b7fe68c54d test_bug553 2024-04-08 16:37:15 +08:00
唐娇 85a3faaa9f Fix bug 528 2024-04-06 00:18:44 +08:00
唐欣媛 2966a8162f Fix bug 544 2024-03-27 12:39:42 +08:00
林杉 b8f2919959 Fix bug 553 2024-03-25 10:15:11 +08:00
林杉 f164465903 Merge branch 'Alpha-snapshot20230621' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug553_LinShan 2024-03-25 10:14:32 +08:00
唐娇 292972c0ce Fix bug 528 2024-03-19 14:59:36 +08:00
林杉 04c4064c68 Fix bug 553 2024-03-18 13:21:46 +08:00
Hui Lan 7d5b1c0ed4 Fix ImportError: cannot import name 'url_quote' 2024-02-22 16:27:31 +08:00
Lan Hui d9e28e3a2b Tweak button size so it looks better on tablets. 2023-08-13 21:08:13 +08:00
Lan Hui 41d1d9619d Stress test code contributed by students 2023-08-13 16:14:43 +08:00
Lan Hui 30b54f8023 Tweak button style 2023-08-12 17:59:49 +08:00
Lan Hui 1e3ac7a379 Use larger buttons 2023-08-12 17:42:25 +08:00
Lan Hui 8dd6a2a343 Use an arrow for Next Article or Previsou Article 2023-08-12 17:36:49 +08:00
Lan Hui d2f30daab1 Use PonyORM instead of class RecordQuery from UseSqlite.py. Incorporated changes from Pull Request 91 contributed by He Zhengzheng. 2023-08-12 15:29:12 +08:00
Lan Hui ed1d0fd714 Show only one place after the decimal point. 2023-08-11 21:02:22 +08:00
Lan Hui f3aa407c56 Use small letters for In and Up. 2023-08-11 19:32:34 +08:00
Lan Hui e9ac50422b Make the flash message dismissible. 2023-08-11 19:28:53 +08:00
Lan Hui f4df263d6e Flash message is informative. Why not add it? 2023-08-11 19:14:51 +08:00
Lan Hui dff560cc73 Indent code using web-mode. 2023-08-11 18:39:36 +08:00
Lan Hui c110de0393 Better spacing. 2023-08-11 18:38:35 +08:00
Lan Hui aaabd3e3bb Enlarge button size so it is more table-friendly. 2023-08-11 18:34:48 +08:00
Lan Hui 9da1a1cae6 Merge branch 'Alpha-snapshot20230621' of http://121.4.94.30:3000/mrlan/EnglishPal into Alpha-snapshot20230621 2023-08-11 18:29:54 +08:00
Hui Lan 9b1d60748d Increase button size so that it is easier to use on tablets . 2023-08-11 18:29:35 +08:00
Lan Hui 83bbd8f600 Improve the speed of loading the next article further after incorporating Chen Qiuwei et al.'s suggestions. 2023-08-11 15:48:53 +08:00
Lan Hui 1b211f107d Speed up loading next article
The key change is replacing "d1 = load_freq_history(path_prefix + 'static/frequency/ferquency.p)" with "d1 = load_freq_history(user_word_list)"
in function get_today_article() from Article.py.  Now, with a user_word_list of size about 500, the next article can be loaded within 100ms.
The new d1 is much smaller than the old one, therefore the following computation "d3 = get_difficulty_level_for_user(d1, d2)" is much faster.
The students did not feel that loading next article is slow; this is because their frequency.p is quite small.

Also log information in app/log.txt
2023-08-11 11:59:48 +08:00
Lan Hui 10c291bed2 Highlight user difficulty level too. 2023-08-10 19:12:30 +08:00
Lan Hui 6d15b65e3c Make highlighting text difficulty level work. 2023-08-10 19:09:15 +08:00
Lan Hui e4f870c995 Create folder app/db with a README file. 2023-08-10 15:25:42 +08:00
Lan Hui 06f896a33a Update .gitignore 2023-08-10 15:22:30 +08:00
Hui Lan 25c2e0aca8 README.md: update the path to wordfreqapp.db. 2023-08-10 14:24:00 +08:00
Hui Lan dca76969eb 解决程序源文件更新了,但是 docker container 中的程序源文件没有更新的问题
问题出在 build.sh 中的 --mount type=volume,src=englishpal-db,target=/app 。
运行 docker container 时,会将整个 englishpal-db 对应的文件夹 /var/lib/docker/volumes/englishpal-db/_data
下面的内容拷贝到  docker container 里面的 /app 文件夹下面。

然而,/var/lib/docker/volumes/englishpal-db/_data 下面的源程序文件并不是最新的(比如其 main.py 是 7月17日的)。
将 target=/app 改为 target=/app/db。即可解决问题。
2023-08-10 14:19:37 +08:00
Hui Lan 00ae957b27 Try to figure out how to rebuild the image after updating the source code. 2023-08-10 10:47:57 +08:00
Hui Lan a397c756cf Merge branch 'Alpha-snapshot20230621' of http://121.4.94.30:3000/mrlan/EnglishPal into Alpha-snapshot20230621 2023-08-10 10:05:29 +08:00
Lan Hui 14ab63c85c 文章段落正确分段(在 lead class 中添加 white-space: pre-wrap;) 2023-08-10 10:03:02 +08:00
Hui Lan b8c3d9bda7 No cache while building docker image 2023-07-17 17:46:55 +08:00
Lan Hui 43419ab4b6 Enlarge text difficult level for one second (make it work using vanilla JavaScript) 2023-07-17 16:28:08 +08:00
Hui Lan 78d9a66e88 After loading the next article, show its difficulty level in a larger size for one second. 2023-07-17 16:13:56 +08:00
Hui Lan 79bdec2a7d Dockerfile: update docker image version. 2023-07-17 10:38:17 +08:00
Hui Lan fb80e952b9 Simplify the docker run options 2023-07-17 10:25:07 +08:00
Hui Lan 6ea0b970a2 (1) Downgrade Flask version from 2.3.2 to 2.0.3 as installing the higher version reports errors. This is probably due to my outdated python version (3.6). (2) Persist SQLite database data in a docker volume. Created a docker volume called englishpal-db using command 'docker volume create englishpal-db' and associate this volume with the docker image file directory /app. So, now what happens in /app will be mirrored to englishpal-db. Where is englishpal-db located? Use command 'docker volume inspect englishpal-db' to find out. 2023-07-17 07:45:38 +08:00
Lan Hui 20051e1a93 article.py: correct data format 2023-07-14 09:17:11 +08:00
Lan Hui 5711f0e826 Update flask version in requirements.txt, use escape from markupsafe package. 2023-07-14 09:11:02 +08:00
Lan Hui cc92e5e29a admin_service.py: non-programmers probably do not know int. So, use integer instead. 2023-07-14 08:32:20 +08:00
Lan Hui cd562a745c admin_service.py: do not need to have seperate lines for a statement. 2023-07-14 08:30:58 +08:00
Lan Hui c284893097 admin_service.py: correct typo parmas. 2023-07-14 08:27:33 +08:00
Lan Hui 87fd594636 admin_service.py: refactor view function article(). 2023-07-14 08:26:37 +08:00
Lan Hui 18c37d583a admin_service.py: show article content for the newly added article after clicking the button '保存'. 2023-07-14 08:15:15 +08:00
Lan Hui 472c0c115f Fix Bug 541 2023-07-09 20:26:32 +08:00
Lan Hui 9a156ebf7e Fix Bug 539 2023-07-08 18:23:45 +08:00
Hui Lan 807d74741b 修复 Bug 493。不要转义(escape)表单提交的内容。否则类 WordFreq 不能正确工作,比如转义会把单引号变成 &#39;,这不利于 WordFreq 类处理。 2023-07-07 16:13:48 +08:00
Hui Lan 287d496ae9 Merge branch 'Alpha-snapshot20230621' of http://121.4.94.30:3000/mrlan/EnglishPal into Alpha-snapshot20230621 2023-07-01 15:17:34 +08:00
张艺腾 582f399f73 Bug537 and Bug538 2023-06-21 16:48:45 +08:00
Hui Lan c37ee98b77 Merge branch 'fix-vuln' of http://121.4.94.30:3000/mrlan/EnglishPal into Alpha-snapshot20230619b 2023-06-19 21:50:48 +08:00
徐宣 f40a388277 Fix: Move wordfreqapp.db to new location 2023-06-19 14:48:35 +08:00
徐宣 2277473afe Fix: Add import for 'abort' function 2023-06-18 19:49:33 +08:00
徐宣 f01c334827 Fix: no-random secret key generation and XSS vulnerability 2023-06-18 19:44:19 +08:00
Laugh 4d2535a6e8 Merge branch 'Alpha-snapshot20230605' into Bug527-ZhouZhifang 2023-06-15 15:59:29 +08:00
Laugh bb2d0363e4 Feat: update fontsize and margin of some elements 2023-06-15 15:54:46 +08:00
poincareS 0ed7657747 Merge remote-tracking branch 'origin/SPM2023S-QianJunQi' into SPM2023S-QianJunQi
# Conflicts:
#	app/wordCMD.py
2023-06-04 10:42:40 +08:00
poincareS 2fb3003808 fix: 2023.6.1
1. 删去了wordCMD.py中:
from flask import ....,Blueprint,....
改为:
from flask import *
2. 修改了代码的格式,包括:等号两边的空格、加号两边的空格
3. 更新了访问用户单词的token验证
3.1 使用Authorization的字段值(Bearer xxx)验证token
3.2 取消了路由访问用户单词的功能,只能在终端命令行中输入: "curl -H "Authorization: Bearer 密钥" http://127.0.0.1:5000/show/用户名/"获取单词
2023-06-04 10:41:24 +08:00
黄子睿 9816596cf8 删除 'app/static/js/tanyanmei-fillword.js' 2023-06-02 21:34:27 +08:00
黄子睿 682247bff1 refactor partial function and code writing specifications 2023-06-02 21:33:21 +08:00
Hui Lan b22c654f0f Merge branch 'Bug529-GuHan' of http://121.4.94.30:3000/mrlan/EnglishPal into Alpha-snapshot20230601 2023-06-01 07:40:27 +08:00
倪玲丽 d402bb45cb 刷新屏幕,点击上下篇,加入生词库,停止阅读(更改) 2023-05-31 18:37:05 +08:00
倪玲丽 cdf6180901 刷新屏幕,点击上下篇,加入生词库,停止阅读 2023-05-30 18:48:37 +08:00
倪玲丽 38837c9c2f 合并最新的Alpha-snapshot20230529 2023-05-30 18:45:23 +08:00
黄子睿 a0ddf4bdad 上传文件至 'app/static/js'
修复了 Bug492 选中问号出现多个问号的问题。
解决了选中紧跟标点符号的单词,单词能正常显示。
优化了选中较长的文章时页面容易出现卡顿的问题。
2023-05-27 17:33:37 +08:00
poincareS dc37f5f229 提供更便利的获取用户单词表的方法,以json数据格式范围
1、注册了一个新的蓝图路径以供功能实现
2、wordCMD中完成功能的代码代码实现
2023-05-26 17:34:43 +08:00
poincareS e9ec65e7a5 提供更便利的获取用户单词表的方法,以json数据格式范围
1、注册了一个新的蓝图路径以供功能实现
2、wordCMD中完成功能的代码代码实现
2023-05-26 17:29:59 +08:00
倪玲丽 df64065dcc 点击上下篇,停止阅读 2023-05-06 18:19:24 +08:00
倪玲丽 ce28b91bd5 屏幕刷新,停止阅读 2023-05-06 18:18:12 +08:00
倪玲丽 d6bd24ee1c Merge branch 'Alpha' of http://121.4.94.30:3000/mrlan/EnglishPal into Bug393-TanYanMei
merge alpha
2023-04-24 12:01:08 +08:00
Hui Lan e9e2bd3d23 Remove static\js 2023-04-24 11:43:43 +08:00
Hui Lan 320a99d479 Move Tan Yanmei's fillword.js to app/static/js/tanyanmei-fillword.js. Delete the strange folder static js. 2023-04-24 11:38:16 +08:00
Hui Lan 3eca9234a9 Merge branch 'Bug393-TanYanMei' of http://121.4.94.30:3000/mrlan/EnglishPal into Bug393-TanYanMei 2023-04-24 11:32:15 +08:00
覃艳美 8924166975 上传文件至 'static\js' 2022-06-12 21:23:34 +08:00
56 changed files with 8078 additions and 510 deletions

12
.gitignore vendored
View File

@ -2,12 +2,20 @@
venv/
app/__init__.py
app/__pycache__/
.DS_Store
app/.DS_Store
app/sqlite_commands.py
app/static/usr/*.jpg
app/static/img/
app/static/frequency/frequency_*.pickle
app/static/frequency/frequency.p
app/static/wordfreqapp.db
app/wordfreqapp.db
app/db/wordfreqapp.db
app/static/donate-the-author.jpg
app/static/donate-the-author-hidden.jpg
app/model/__pycache__/
app/model/__pycache__/
app/test/__pycache__/
app/test/.pytest_cache/
app/test/pytest_report.html
app/test/assets
app/log.txt

View File

@ -1,4 +1,5 @@
FROM tiangolo/uwsgi-nginx-flask:python3.6
COPY requirements.txt /app
RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
COPY ./app /app
FROM tiangolo/uwsgi-nginx-flask:python3.8-alpine
COPY requirements.txt /tmp
COPY ./app/ /app/
RUN pip3 install -U pip -i https://mirrors.aliyun.com/pypi/simple/
RUN pip3 install -r /tmp/requirements.txt -i https://mirrors.aliyun.com/pypi/simple/

4
Jenkinsfile vendored
View File

@ -10,8 +10,8 @@ pipeline {
stages {
stage('MakeDatabasefile') {
steps {
sh 'touch ./app/static/wordfreqapp.db && rm -f ./app/static/wordfreqapp.db'
sh 'cat ./app/static/wordfreqapp.sql | sqlite3 ./app/static/wordfreqapp.db'
sh 'touch ./app/wordfreqapp.db && rm -f ./app/wordfreqapp.db'
sh 'cat ./app/static/wordfreqapp.sql | sqlite3 ./app/wordfreqapp.db'
}
}
stage('BuildIt') {

View File

@ -61,15 +61,15 @@ My steps for deploying English on a Ubuntu server.
All articles are stored in the `article` table in a SQLite file called
`app/static/wordfreqapp.db`.
`app/db/wordfreqapp.db`.
### Adding new articles
To add articles, open and edit `app/static/wordfreqapp.db` using DB Browser for SQLite (https://sqlitebrowser.org).
To add articles, open and edit `app/db/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`.
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/db/wordfreqapp.db`. Simply update field `expiry_date`.
### Exporting the database
@ -95,7 +95,7 @@ sqlite3 wordfreqapp.db`. Delete wordfreqapp.db first if it exists.
### Uploading wordfreqapp.db to the server
`pscp wordfreqapp.db lanhui@118.*.*.118:/home/lanhui/englishpal2/EnglishPal/app/static`
`pscp wordfreqapp.db lanhui@118.*.*.118:/home/lanhui/englishpal2/EnglishPal/app/db/`
@ -129,6 +129,28 @@ We welcome feedback on EnglishPal. Feedback examples:
EnglishPal's bugs and improvement suggestions are recorded in [Bugzilla](http://118.25.96.118/bugzilla/buglist.cgi?bug_status=__all__&list_id=1302&order=Importance&product=EnglishPal&query_format=specific). Send (lanhui at zjnu.edu.cn) an email message for opening a Bugzilla account or reporting a bug.
## End-to-end testing
We use the Selenium test framework to test our app.
In order to run the test, first we need to download a webdriver executable.
Microsoft Edge's webdriver can be downloaded from [microsoft-edge-tools-webdriver](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/). Make sure the version we download matches the version of the web browser installed on our laptop.
After extracting the downloaded zip file (e.g., edgedriver_win64.zip), rename msedgedriver.exe to MicrosoftWebDriver.exe.
Add MicrosoftWebDriver.exe's path to system's PATH variable.
Install the following dependencies too:
- pip install -U selenium==3.141.0
- pip install -U urllib3==1.26.2
Run English Pal first, then run the test using pytest as follows: pytest --html=pytest_report.html test_add_word.py
The above command will generate a HTML report file pytest_report.html after finishing executing test_add_word.py. Note: you need to install pytest-html package first: pip install pytest-html.
You may also want to use [webdriver-manager](https://pypi.org/project/webdriver-manager/) from PyPI, so that you can avoid tediously installing a web driver executable manually. However, my experience shows that webdriver-manager is too slow. For example, it took me 16 minutes to run 9 tests, while with the pre-installed web driver executable, it took less than 2 minutes.
## TODO

View File

@ -1,6 +1,5 @@
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 random, glob
@ -8,18 +7,37 @@ 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 model.article import get_all_articles, get_article_by_id, get_number_of_articles
import logging
import re
path_prefix = './'
db_path_prefix = './db/' # comment this line in deployment
oxford_words_path='./db/oxford_words.txt'
def count_oxford_words(text, oxford_words):
words = re.findall(r'\b\w+\b', text.lower())
total_words = len(words)
oxford_word_count = sum(1 for word in words if word in oxford_words)
return oxford_word_count, total_words
path_prefix = '/var/www/wordfreq/wordfreq/'
path_prefix = './' # comment this line in deployment
def calculate_ratio(oxford_word_count, total_words):
if total_words == 0:
return 0
return oxford_word_count / total_words
def load_oxford_words(file_path):
oxford_words = {}
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
parts = line.strip().split()
word = parts[0]
pos = parts[1]
level = parts[2]
oxford_words[word] = {'pos': pos, 'level': level}
return oxford_words
def total_number_of_essays():
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
rq.instructions("SELECT * FROM article")
rq.do()
result = rq.get_results()
return len(result)
return get_number_of_articles()
def get_article_title(s):
@ -33,32 +51,36 @@ def get_article_body(s):
def get_today_article(user_word_list, visited_articles):
rq = RecordQuery(path_prefix + 'static/wordfreqapp.db')
if visited_articles is None:
visited_articles = {
"index" : 0, # 为 article_ids 的索引
"article_ids": [] # 之前显示文章的id列表越后越新
}
if visited_articles["index"] > len(visited_articles["article_ids"])-1: # 生成新的文章,因此查找所有的文章
rq.instructions("SELECT * FROM article")
result = get_all_articles()
else: # 生成阅读过的文章,因此查询指定 article_id 的文章
if visited_articles["article_ids"][visited_articles["index"]] == 'null': # 可能因为直接刷新页面导致直接去查询了'null',因此当刷新的页面的时候,需要直接进行“上一篇”操作
visited_articles["index"] -= 1
visited_articles["article_ids"].pop()
rq.instructions('SELECT * FROM article WHERE article_id=%d' % (visited_articles["article_ids"][visited_articles["index"]]))
rq.do()
result = rq.get_results()
article_id = visited_articles["article_ids"][visited_articles["index"]]
result = get_article_by_id(article_id)
random.shuffle(result)
# Choose article according to reader's level
d1 = load_freq_history(path_prefix + 'static/frequency/frequency.p')
logging.debug('* get_today_article(): start d1 = ... ')
d1 = load_freq_history(user_word_list)
d2 = load_freq_history(path_prefix + 'static/words_and_tests.p')
logging.debug(' ... get_today_article(): get_difficulty_level_for_user() start')
d3 = get_difficulty_level_for_user(d1, d2)
logging.debug(' ... get_today_article(): done')
d = None
result_of_generate_article = "not found"
d_user = load_freq_history(user_word_list)
logging.debug('* get_today_article(): user_difficulty_level() start')
user_level = user_difficulty_level(d_user, d3) # more consideration as user's behaviour is dynamic. Time factor should be considered.
logging.debug('* get_today_article(): done')
text_level = 0
if visited_articles["index"] > len(visited_articles["article_ids"])-1: # 生成新的文章
amount_of_visited_articles = len(visited_articles["article_ids"])
@ -86,15 +108,19 @@ def get_today_article(user_word_list, visited_articles):
today_article = None
if d:
oxford_words = load_oxford_words(oxford_words_path)
oxford_word_count, total_words = count_oxford_words(d['text'],oxford_words)
ratio = calculate_ratio(oxford_word_count,total_words)
today_article = {
"user_level": '%4.2f' % user_level,
"text_level": '%4.2f' % text_level,
"user_level": '%4.1f' % user_level,
"text_level": '%4.1f' % text_level,
"date": d['date'],
"article_title": get_article_title(d['text']),
"article_body": get_article_body(d['text']),
"source": d["source"],
"question": get_question_part(d['question']),
"answer": get_answer_part(d['question'])
"answer": get_answer_part(d['question']),
"ratio" : ratio
}
return visited_articles, today_article, result_of_generate_article

View File

@ -1,7 +1,8 @@
import hashlib
import string
from datetime import datetime, timedelta
from UseSqlite import InsertQuery, RecordQuery
import unicodedata
def md5(s):
'''
@ -12,14 +13,16 @@ def md5(s):
h = hashlib.md5(s.encode(encoding='utf-8'))
return h.hexdigest()
# 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 = './' # comment this line in deployment
def verify_pass(newpass,oldpass):
if(newpass==oldpass):
def verify_pass(newpass, oldpass):
if (newpass == oldpass):
return True
@ -31,7 +34,7 @@ def verify_user(username, password):
def add_user(username, password):
start_date = datetime.now().strftime('%Y%m%d')
expiry_date = (datetime.now() + timedelta(days=30)).strftime('%Y%m%d') # will expire after 30 days
expiry_date = (datetime.now() + timedelta(days=30)).strftime('%Y%m%d') # will expire after 30 days
# 将用户名和密码一起加密,以免暴露不同用户的相同密码
password = md5(username + password)
insert_user(username=username, password=password, start_date=start_date, expiry_date=expiry_date)
@ -53,7 +56,7 @@ def change_password(username, old_password, new_password):
if not verify_user(username, old_password): # 旧密码错误
return False
# 将用户名和密码一起加密,以免暴露不同用户的相同密码
if verify_pass(new_password,old_password): #新旧密码一致
if verify_pass(new_password, old_password): #新旧密码一致
return False
update_password_by_username(username, new_password)
return True
@ -66,30 +69,64 @@ def get_expiry_date(username):
else:
return user.expiry_date
class UserName:
def __init__(self, username):
self.username = username
def contains_chinese(self):
for char in self.username:
# Check if the character is in the CJK (Chinese, Japanese, Korean) Unicode block
if unicodedata.name(char).startswith('CJK UNIFIED IDEOGRAPH'):
return True
return False
def validate(self):
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
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
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
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 != '_':
return f'{c} is not allowed in the user name.'
if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del', 'admin']:
if self.username in ['signup', 'login', 'logout', 'reset', 'mark', 'back', 'unfamiliar', 'familiar', 'del',
'admin']:
return 'You used a restricted word as your user name. Please come up with a better one.'
if self.contains_chinese():
return 'Chinese characters are not allowed in the user name.'
return 'OK'
class Password:
def __init__(self, password):
self.password = password
def contains_chinese(self):
for char in self.password:
# Check if the character is in the CJK (Chinese, Japanese, Korean) Unicode block
if unicodedata.name(char).startswith('CJK UNIFIED IDEOGRAPH'):
return True
return False
def validate(self):
if len(self.password) < 4:
return 'Password must be at least 4 characters long.'
if ' ' in self.password:
return 'Password cannot contain spaces.'
if self.contains_chinese():
return 'Chinese characters are not allowed in the password.'
return 'OK'
class WarningMessage:
def __init__(self, s):
def __init__(self, s, type='username'):
self.s = s
self.type = type
def __str__(self):
return UserName(self.s).validate()
if self.type == 'username':
return UserName(self.s).validate()
if self.type == 'password':
return Password(self.s).validate()

View File

@ -1,87 +0,0 @@
###########################################################################
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
# Written permission must be obtained from the author for commercial uses.
###########################################################################
# Reference: Dusty Phillips. Python 3 Objected-oriented Programming Second Edition. Pages 326-328.
# Copyright (C) 2019 Hui Lan
import sqlite3
class Sqlite3Template:
def __init__(self, db_fname):
self.db_fname = db_fname
def connect(self, db_fname):
self.conn = sqlite3.connect(self.db_fname)
def instructions(self, query_statement):
raise NotImplementedError()
def operate(self):
self.conn.row_factory = sqlite3.Row
self.results = self.conn.execute(self.query) # self.query is to be given in the child classes
self.conn.commit()
def format_results(self):
raise NotImplementedError()
def do(self):
self.connect(self.db_fname)
self.instructions(self.query)
self.operate()
def instructions_with_parameters(self, query_statement, parameters):
self.query = query_statement
self.parameters = parameters
def do_with_parameters(self):
self.connect(self.db_fname)
self.instructions_with_parameters(self.query, self.parameters)
self.operate_with_parameters()
def operate_with_parameters(self):
self.conn.row_factory = sqlite3.Row
self.results = self.conn.execute(self.query, self.parameters) # self.query is to be given in the child classes
self.conn.commit()
class InsertQuery(Sqlite3Template):
def instructions(self, query):
self.query = query
class RecordQuery(Sqlite3Template):
def instructions(self, query):
self.query = query
def format_results(self):
output = []
for row_dict in self.results.fetchall():
lst = []
for k in dict(row_dict):
lst.append( row_dict[k] )
output.append(', '.join(lst))
return '\n\n'.join(output)
def get_results(self):
result = []
for row_dict in self.results.fetchall():
result.append( dict(row_dict) )
return result
if __name__ == '__main__':
#iq = InsertQuery('RiskDB.db')
#iq.instructions("INSERT INTO inspection Values ('FoodSupplies', 'RI2019051301', '2019-05-13', '{}')")
#iq.do()
#iq.instructions("INSERT INTO inspection Values ('CarSupplies', 'RI2019051302', '2019-05-13', '{[{\"risk_name\":\"elevator\"}]}')")
#iq.do()
rq = RecordQuery('wordfreqapp.db')
rq.instructions("SELECT * FROM article WHERE level=3")
rq.do()
#print(rq.format_results())

View File

@ -1,7 +1,7 @@
from flask import *
from markupsafe import escape
from Login import check_username_availability, verify_user, add_user, get_expiry_date, change_password, WarningMessage
# 初始化蓝图
accountService = Blueprint("accountService", __name__)
@ -43,7 +43,6 @@ def signup():
return jsonify({'status': '1'})
@accountService.route("/login", methods=['GET', 'POST'])
def login():
'''
@ -59,17 +58,48 @@ def login():
username = escape(request.form['username'])
password = escape(request.form['password'])
verified = verify_user(username, password)
if verified:
# 登录成功写入session
session['logged_in'] = True
session[username] = username
session['username'] = username
user_expiry_date = get_expiry_date(username)
session['expiry_date'] = user_expiry_date
session['visited_articles'] = None
return jsonify({'status': '1'})
else:
return jsonify({'status': '0'})
#读black.txt文件判断用户是否在黑名单中
with open('black.txt') as f:
for line in f:
line = line.strip()
if username == line:
return jsonify({'status': '5'})
with open('black.txt', 'a+') as f:
f.seek(0)
lines = f.readlines()
line=[]
for i in lines:
line.append(i.strip('\n'))
#读black.txt文件判断用户是否在黑名单中
if verified and username not in line: #TODO: 一个用户名是另外一个用户名的子串怎么办?
# 登录成功写入session
session['logged_in'] = True
session[username] = username
session['username'] = username
user_expiry_date = get_expiry_date(username)
session['expiry_date'] = user_expiry_date
session['visited_articles'] = None
f.close()
return jsonify({'status': '1'})
elif verified==0 and password!='黑名单':
#输入错误密码次数小于5次
return jsonify({'status': '0'})
else:
#输入错误密码次数达到5次
with open('black.txt', 'a+') as f:
f.seek(0)
lines = f.readlines()
line = []
for i in lines:
line.append(i.strip('\n'))
if username in line:
return jsonify({'status': '5'})
else:
f.write(username)
f.write('\n')
return jsonify({'status': '5'})
@accountService.route("/logout", methods=['GET', 'POST'])
@ -83,6 +113,7 @@ def logout():
return redirect(url_for('mainpage'))
@accountService.route("/reset", methods=['GET', 'POST'])
def reset():
'''
@ -108,3 +139,4 @@ def reset():
return jsonify({'status':'1'}) # 修改成功
else:
return jsonify({'status':'2'}) # 修改失败

View File

@ -1,5 +1,6 @@
# System Library
from flask import *
from markupsafe import escape
# Personal library
from Yaml import yml
@ -37,6 +38,22 @@ def admin():
@adminService.route("/admin/article", methods=["GET", "POST"])
def article():
def _make_title_and_content(article_lst):
for article in article_lst:
text = escape(article.text) # Fix XSS vulnerability, contributed by Xu Xuan
article.title = text.split("\n")[0]
article.content = '<br/>'.join(text.split("\n")[1:])
def _update_context():
article_len = get_number_of_articles()
context["article_number"] = article_len
context["text_list"] = get_page_articles(_cur_page, _page_size)
_articles = get_page_articles(_cur_page, _page_size)
_make_title_and_content(_articles)
context["text_list"] = _articles
global _cur_page, _page_size
is_admin = check_is_admin()
@ -44,20 +61,15 @@ def article():
return is_admin
_article_number = get_number_of_articles()
try:
_page_size = min(
max(1, int(request.args.get("size", 5))), _article_number
) # 最小的size是1
_cur_page = min(
max(1, int(request.args.get("page", 1))), _article_number // _page_size + (_article_number % _page_size > 0)
) # 最小的page是1
_page_size = min(max(1, int(request.args.get("size", 5))), _article_number) # 最小的size是1
_cur_page = min(max(1, int(request.args.get("page", 1))), _article_number // _page_size + (_article_number % _page_size > 0)) # 最小的page是1
except ValueError:
return "page parmas must be int!"
return "page parameters must be integer!"
_articles = get_page_articles(_cur_page, _page_size)
for article in _articles: # 获取每篇文章的title
article.title = article.text.split("\n")[0]
article.content = '<br/>'.join(article.text.split("\n")[1:])
_make_title_and_content(_articles)
context = {
"article_number": _article_number,
@ -67,25 +79,18 @@ def article():
"username": session.get("username"),
}
def _update_context():
article_len = get_number_of_articles()
context["article_number"] = article_len
context["text_list"] = get_page_articles(_cur_page, _page_size)
_articles = get_page_articles(_cur_page, _page_size)
for article in _articles: # 获取每篇文章的title
article.title = article.text.split("\n")[0]
context["text_list"] = _articles
if request.method == "GET":
try:
delete_id = int(request.args.get("delete_id", 0))
except:
return "Delete article ID must be int!"
if delete_id: # delete article
delete_article_by_id(delete_id)
_update_context()
elif request.method == "POST":
if request.method == "POST":
data = request.form
if "delete_id" in data:
try:
delete_id = int(data["delete_id"]) # 转成int型
delete_article_by_id(delete_id) # 根据id删除article
flash(f'Article ID {delete_id} deleted successfully.') # 刷新页首提示语
_update_context()
except ValueError:
flash('Invalid article ID for deletion.') # 刷新页首提示语
content = data.get("content", "")
source = data.get("source", "")
question = data.get("question", "")
@ -94,9 +99,10 @@ def article():
if level not in ['1', '2', '3', '4']:
return "Level must be between 1 and 4."
add_article(content, source, level, question)
_update_context()
title = content.split('\n')[0]
flash(f'Article added. Title: {title}')
_update_context() # 这行应在flash之后 否则会发生新建的文章即点即删
return render_template("admin_manage_article.html", **context)

31
app/api_service.py Normal file
View File

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

1
app/black.txt Normal file
View File

@ -0,0 +1 @@
hsy

1
app/db/README.txt Normal file
View File

@ -0,0 +1 @@
Put wordfreqapp.db here

5942
app/db/oxford_words.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
import pickle
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, map_percentages_to_levels
import snowballstemmer
@ -18,6 +18,7 @@ def load_record(pickle_fname):
return d
ENGLISH_WORD_DIFFICULTY_DICT = {}
def convert_test_type_to_difficulty_level(d):
"""
对原本的单词库中的单词进行难度评级
@ -39,8 +40,10 @@ def convert_test_type_to_difficulty_level(d):
elif 'BBC' in d[k]:
result[k] = 8
return result # {'apple': 4, ...}
global ENGLISH_WORD_DIFFICULTY_DICT
ENGLISH_WORD_DIFFICULTY_DICT = result
return result # {'apple': 4, ...}
def get_difficulty_level_for_user(d1, d2):
"""
@ -49,7 +52,11 @@ def get_difficulty_level_for_user(d1, d2):
在d2的后面添加单词没有新建一个新的字典
"""
# TODO: convert_test_type_to_difficulty_level() should not be called every time. Each word's difficulty level should be pre-computed.
d2 = convert_test_type_to_difficulty_level(d2) # 根据d2的标记评级{'apple': 4, 'abandon': 4, ...}
if ENGLISH_WORD_DIFFICULTY_DICT == {}:
d2 = convert_test_type_to_difficulty_level(d2) # 根据d2的标记评级{'apple': 4, 'abandon': 4, ...}
else:
d2 = ENGLISH_WORD_DIFFICULTY_DICT
stemmer = snowballstemmer.stemmer('english')
for k in d1: # 用户的词
@ -87,30 +94,58 @@ def revert_dict(d):
return d2
def user_difficulty_level(d_user, d):
def user_difficulty_level(d_user, d, calc_func=0):
'''
two ways to calculate difficulty_level
set calc_func!=0 to use sqrt, otherwise use weighted average
'''
if calc_func != 0:
# calculation function 1: sqrt
d_user2 = revert_dict(d_user) # key is date, and value is a list of words added in that date
geometric = 0
count = 0
for date in sorted(d_user2.keys(),
reverse=True): # most recently added words are more important while determining user's level
lst = d_user2[date] # a list of words
lst2 = [] # a list of tuples, (word, difficulty level)
for word in lst:
if word in d:
lst2.append((word, d[word]))
lst3 = sort_in_ascending_order(lst2) # easiest tuple first
# print(lst3)
for t in lst3:
word = t[0]
hard = t[1]
# print('WORD %s HARD %4.2f' % (word, hard))
geometric = geometric + math.log(hard)
count += 1
return math.exp(geometric / max(count, 1))
# calculation function 2: weighted average
d_user2 = revert_dict(d_user) # key is date, and value is a list of words added in that date
count = 0
geometric = 1
for date in sorted(d_user2.keys(),
reverse=True): # most recently added words are more important while determining user's level
count = {} # number of all kinds of words
percentages = {} # percentages of all kinds of difficulties
total = 0 # total words
for date in d_user2.keys():
lst = d_user2[date] # a list of words
lst2 = [] # a list of tuples, (word, difficulty level)
for word in lst:
if word in d:
lst2.append((word, d[word]))
if d[word] not in count:
count[d[word]] = 0
count[d[word]] += 1
total += 1
lst3 = sort_in_ascending_order(lst2) # easiest tuple first
# print(lst3)
for t in lst3:
word = t[0]
hard = t[1]
# print('WORD %s HARD %4.2f' % (word, hard))
geometric = geometric * (hard)
count += 1
if count >= 10:
return geometric ** (1 / count)
if total == 0:
return 1
for k in count.keys():
percentages[k] = count[k] / total
weight = map_percentages_to_levels(percentages)
sum = 0
for k in weight.keys():
sum += weight[k] * k
return sum
return geometric ** (1 / max(count, 1))
def text_difficulty_level(s, d):

View File

@ -1,24 +1,28 @@
#! /usr/bin/python3
# -*- coding: utf-8 -*-
###########################################################################
# Copyright 2019 (C) Hui Lan <hui.lan@cantab.net>
# Written permission must be obtained from the author for commercial uses.
###########################################################################
from flask import escape
from flask import abort, jsonify
from markupsafe import escape
from Login import *
from Article import *
import Yaml
from user_service import userService
from account_service import accountService
from admin_service import adminService, ADMIN_NAME
from api_service import apiService
import os
from translate import *
app = Flask(__name__)
app.secret_key = 'lunch.time!'
app.secret_key = os.urandom(32)
# 将蓝图注册到Lab app
app.register_blueprint(userService)
app.register_blueprint(accountService)
app.register_blueprint(adminService)
app.register_blueprint(apiService)
path_prefix = '/var/www/wordfreq/wordfreq/'
path_prefix = './' # comment this line in deployment
@ -54,7 +58,6 @@ def appears_in_test(word, d):
else:
return ','.join(d[word])
@app.route("/mark", methods=['GET', 'POST'])
def mark_word():
'''
@ -80,6 +83,19 @@ def mainpage():
根据GET或POST方法来返回不同的主界面
:return: 主界面
'''
article_text = get_all_articles()
texts = [item['text'] for item in article_text]
oxford_words = load_oxford_words(oxford_words_path)
# 提取所有单词
all_words = []
for text in texts:
words = re.findall(r'\b\w+\b', text.lower())
all_words.extend(words)
oxford_word_count = sum(1 for word in all_words if word in oxford_words)
ratio = calculate_ratio(oxford_word_count, len(all_words))
if request.method == 'POST': # when we submit a form
content = escape(request.form['content'])
f = WordFreq(content)
@ -103,7 +119,17 @@ def mainpage():
d_len=d_len,
lst=lst,
yml=Yaml.yml,
number_of_essays=number_of_essays)
number_of_essays=number_of_essays,
ratio = ratio)
@app.route("/translate", methods=['POST'])
def translate_word():
data = request.get_json()
word = data.get('word', '')
from_lang = data.get('from_lang', 'en') # 假设默认源语言是英语
to_lang = data.get('to_lang', 'zh') # 假设默认目标语言是中文
result = translate(word, from_lang, to_lang)
return jsonify({'translation': result})
if __name__ == '__main__':

View File

@ -1,7 +1,7 @@
from pony.orm import *
db = Database()
db.bind("sqlite", "../static/wordfreqapp.db", create_db=True) # bind sqlite file
db.bind("sqlite", "../db/wordfreqapp.db", create_db=True) # bind sqlite file
class User(db.Entity):

View File

@ -7,7 +7,7 @@ def add_article(content, source="manual_input", level="5", question="No question
Article(
text=content,
source=source,
date=datetime.now().strftime("%-d %b %Y"), # format style of `5 Oct 2022`
date=datetime.now().strftime("%d %b %Y"), # format style of `5 Oct 2022`
level=level,
question=question,
)
@ -32,3 +32,17 @@ def get_page_articles(num, size):
x
for x in Article.select().order_by(desc(Article.article_id)).page(num, size)
]
def get_all_articles():
articles = []
with db_session:
for article in Article.select():
articles.append(article.to_dict())
return articles
def get_article_by_id(article_id):
with db_session:
article = Article.get(article_id=article_id)
return [article.to_dict()]

View File

@ -2,13 +2,14 @@
css:
item:
- ../static/css/bootstrap.css
- ../static/css/highlighted.css
# 全局引入的js文件地址
js:
head: # 在页面加载之前加载
- ../static/js/jquery.js
- ../static/js/read.js
- ../static/js/word_operation.js
- ../static/js/checkboxes.js
bottom: # 在页面加载完之后加载
- ../static/js/fillword.js
- ../static/js/highlight.js

37
app/static/css/button.css Normal file
View File

@ -0,0 +1,37 @@
/* 按钮(上一篇,下一篇)样式 */
.pagination {
padding: 20px;
max-width: 600px;
}
.arrow {
margin-right: 15px;
display: flex;
align-items: center;
background: #007BFF; /* 按钮背景颜色 */
border: none;
color: #FFF; /* 按钮文字颜色 */
padding: 5px 12px;
font-size: 20px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.arrow i {
margin: 0 5px;
}
.arrow:hover {
background-color: #0056b3; /* 按钮悬停时的背景颜色 */
cursor: pointer;
}
.arrow:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.5);
}
/* 为首页时按钮(Pre Article)的背景颜色 */
.gray-background {
background-color: #6c757d !important;
}

View File

@ -0,0 +1,5 @@
.highlighted {
color: red;
font-weight: normal;
}

View File

@ -0,0 +1,52 @@
#slider {
margin: 20px auto;
width: 200px;
height: 40px;
position: relative;
border-radius: 5px;
background-color: #dae2d0;
overflow: hidden;
text-align: center;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
#slider_bg {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #7AC23C;
z-index: 1;
}
#label {
width: 46px;
position: absolute;
left: 0;
top: 0;
height: 38px;
line-height: 38px;
border: 1px solid #cccccc;
background: #fff;
z-index: 3;
cursor: move;
color: #ff9e77;
font-size: 16px;
font-weight: 900;
}
#labelTip {
position: absolute;
left: 0;
width: 100%;
height: 100%;
font-size: 13px;
font-family: 'Microsoft Yahei', serif;
color: #787878;
line-height: 38px;
text-align: center;
z-index: 2;
}

View File

@ -0,0 +1,5 @@
function toggleCheckboxSelection(checkStatus) {
// used in userpage_post.html
const checkBoxes = document.getElementsByName('marked');
checkBoxes.forEach((checkbox) => { checkbox.checked = checkStatus;} );
}

View File

@ -1,5 +1,5 @@
let isRead = true;
let isChoose = true;
let isRead = localStorage.getItem('readChecked') !== 'false'; // default to true
let isChoose = localStorage.getItem('chooseChecked') !== 'false';
function getWord() {
return window.getSelection ? window.getSelection() : document.selection.createRange().text;
@ -8,24 +8,45 @@ function getWord() {
function fillInWord() {
let word = getWord();
if (isRead) Reader.read(word, inputSlider.value);
if (!isChoose) return;
if (!isChoose) {
if(isHighlight){
const element = document.getElementById("selected-words3");
element.value = element.value + " " + word;
}
return;
}
const element = document.getElementById("selected-words");
localStorage.setItem('nowWords', element.value);
element.value = element.value + " " + word;
localStorage.setItem('selectedWords', element.value);
}
document.getElementById("text-content").addEventListener("click", fillInWord, false);
if (document.getElementById("text-content")) {
document.getElementById("text-content").addEventListener("click", fillInWord, false);
}
const sliderValue = document.getElementById("rangeValue");
const inputSlider = document.getElementById("rangeComponent");
inputSlider.oninput = () => {
let value = inputSlider.value;
sliderValue.textContent = value + '×';
};
if (inputSlider) {
inputSlider.oninput = () => {
let value = inputSlider.value;
sliderValue.textContent = value + '×';
};
}
function onReadClick() {
isRead = !isRead;
localStorage.setItem('readChecked', isRead);
}
function onChooseClick() {
isChoose = !isChoose;
localStorage.setItem('chooseChecked', isChoose);
}
// 如果网页刷新,停止播放声音
if (performance.getEntriesByType("navigation")[0].type == "reload") {
Reader.stopRead();
}

View File

@ -1,4 +1,4 @@
let isHighlight = true;
let isHighlight = localStorage.getItem('highlightChecked') !== 'false'; // default to true
function cancelBtnHandler() {
cancelHighlighting();
@ -9,75 +9,113 @@ function cancelBtnHandler() {
}
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);
highLight();
if (document.getElementById("text-content")) {
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 replaceWords(str, word) {
let count = 0;
const regex = new RegExp(`(^|\\s)${word}(?=\\s|$)`, 'g');
let result = str.replace(regex, (match, p1) => {
count++;
// p1 保留前导空格(如果有),仅第一个匹配保留,后续匹配替换为空字符串
return count === 1 ? match : p1;
});
return result;
}
function countWords(str, word) {
// 使用正则表达式匹配目标单词的整个单词边界情况,包括前后空格、行首和行尾
const regex = new RegExp(`(^|\\s)${word}(?=\\s|$)`, 'g');
let match;
let count = 0;
// 迭代匹配所有符合条件的单词
while ((match = regex.exec(str)) !== null) {
count++;
}
return count;
}
//用于替换单词
function replaceAllWords(str, word, replacement) {
const regex = new RegExp(`(^|\\s)${word}(?=\\s|$)`, 'gi');
let result = str.replace(regex, (match, p1) => {
return p1 + replacement;
});
return result;
}
function getWord() {
return window.getSelection ? window.getSelection() : document.selection.createRange().text;
return window.getSelection ? window.getSelection().toString() : document.selection.createRange().text;
}
function highLight() {
if (!isHighlight) return;
let articleContent = document.getElementById("article").innerText; //将原来的.innerText改为.innerHtml使用innerText会把原文章中所包含的<br>标签去除,导致处理后的文章内容失去了原来的格式
let word = (getWord() + "").trim().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "");
let articleContent = document.getElementById("article").innerHTML; // innerHTML保留HTML标签来保持部分格式且适配不同的浏览器
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()中,"\\b"代表单词边界匹配。
let allWords = dictionaryWords === null ? pickedWords.value + " " : pickedWords.value + " " + dictionaryWords.value;
let highlightWords = document.getElementById("selected-words3");
allWords = highlightWords == null ? allWords : allWords + " " + highlightWords.value;
const list = allWords.split(" "); // 将所有的生词放入一个list中
if(word !== null && word !== "" && word !== " "){
if(localStorage.getItem("nowWords").indexOf(word) !== -1 || localStorage.getItem("nowWords").indexOf(word.toLowerCase()) !== -1){
articleContent = articleContent.replace(new RegExp('<span class="highlighted">' + word + '</span>', "g"), word);
//修改代码
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>");
}
let count=countWords(pickedWords.value,word)
let currentWords=localStorage.getItem("nowWords")+" "+word
localStorage.setItem("nowWords",currentWords)
//
if(count>0){
if(count==1){
localStorage.setItem("nowWords",replaceWords(currentWords,word))
}else{
localStorage.setItem("nowWords",replaceAllWords(currentWords,word,""))
}
}
pickedWords.value = localStorage.getItem("nowWords")
document.getElementById("article").innerHTML = articleContent;
return;
}
}
let totalSet = new Set();
for (let i = 0; i < list.length; ++i) {
list[i] = list[i].replace(/(^\W*)|(\W*$)/g, ""); // 消除单词两边的非单词字符
list[i] = list[i].replace('|', "");
list[i] = list[i].replace('?', "");
if (list[i] != "" && !totalSet.has(list[i])) {
// 返回所有匹配单词的集合, 正则表达式RegExp()中, "\b"匹配一个单词的边界, g 表示全局匹配, i 表示对大小写不敏感。
let matches = new Set(articleContent.match(new RegExp("\\b" + list[i] + "\\b", "gi")));
totalSet = new Set([...totalSet, ...matches]);
}
}
// 删除所有的"<span class='highlighted'>"标签,防止标签发生嵌套
articleContent = articleContent.replace(new RegExp('<span class="highlighted">',"gi"), "")
articleContent = articleContent.replace(new RegExp("</span>","gi"), "");
// 将文章中所有出现该单词word的地方改为"<span class='highlighted'>" + word + "</span>"。
for (let word of totalSet) {
articleContent = articleContent.replace(new RegExp("\\b" + word + "\\b", "g"), "<span class='highlighted'>" + word + "</span>");
}
document.getElementById("article").innerHTML = articleContent;
addClickEventToHighlightedWords();
}
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] !== "") { //原来判断的代码中替换的内容为“list[i]”这个字符串这明显是错误的我们需要替换的是list[i]里的内容
articleContent = articleContent.replace(new RegExp("<mark>"+list[i]+"</mark>", "g"), list[i]);
}
}
}
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] !== "") { //原来代码中替换的内容为“list[i]”这个字符串这明显是错误的我们需要替换的是list[i]里的内容
articleContent = articleContent.replace(new RegExp("<mark>"+list2[i]+"</mark>", "g"), list2[i]);
}
}
}
let articleContent = document.getElementById("article").innerHTML;
articleContent = articleContent.replace(new RegExp('<span class="highlighted">',"gi"), "")
articleContent = articleContent.replace(new RegExp("</span>","gi"), "");
document.getElementById("article").innerHTML = articleContent;
}
@ -97,6 +135,62 @@ function toggleHighlighting() {
isHighlight = true;
highLight();
}
localStorage.setItem('highlightChecked', isHighlight);
}
function showWordMeaning(event) {
const word = event.target.innerText.trim().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "").toLowerCase();
const apiUrl = '/translate';
const rect = event.target.getBoundingClientRect();
const tooltipX = rect.left + window.scrollX;
const tooltipY = rect.top + window.scrollY + rect.height;
// 发送POST请求
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ word: word }), // 发送的JSON数据
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // 解析JSON响应
})
.then(data => {
// 假设data.translation是翻译结果
const tooltip = document.getElementById('tooltip');
if (!tooltip) {
console.error('Tooltip element not found');
return;
}
tooltip.textContent = data.translation || '没有找到该单词的中文意思';
tooltip.style.left = `${tooltipX}px`;
tooltip.style.top = `${tooltipY}px`;
tooltip.style.display = 'block';
tooltip.style.position = 'absolute';
tooltip.style.background = 'yellow';
// 可以在这里添加点击事件监听器来隐藏tooltip但注意避免内存泄漏
document.addEventListener('click', function handler(e) {
if (!tooltip.contains(e.target)) {
tooltip.style.display = 'none';
document.removeEventListener('click', handler);
}
});
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
}
function addClickEventToHighlightedWords() {
const highlightedWords = document.querySelectorAll('.highlighted');
highlightedWords.forEach(word => {
word.addEventListener('click', showWordMeaning);
});
}
showBtnHandler();

5
app/static/js/jquery-1.12.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,200 @@
/**
* jquery plugin -- jquery.slideunlock.js
* Description: a slideunlock plugin based on jQuery
* Version: 1.1
* Author: Dong Yuhao
* created: March 27, 2016
*/
;(function ($,window,document,undefined) {
function SliderUnlock(elm, options, success){
var me = this;
var $elm = me.checkElm(elm) ? $(elm) : $;
success = me.checkFn(success) ? success : function(){};
var opts = {
successLabelTip: "Successfully Verified",
duration: 200,
swipestart: false,
min: 0,
max: $elm.width(),
index: 0,
isOk: false,
lableIndex: 0
};
opts = $.extend(opts, options||{});
//$elm
me.elm = $elm;
//opts
me.opts = opts;
//是否开始滑动
me.swipestart = opts.swipestart;
//最小值
me.min = opts.min;
//最大值
me.max = opts.max;
//当前滑动条所处的位置
me.index = opts.index;
//是否滑动成功
me.isOk = opts.isOk;
//滑块宽度
me.labelWidth = me.elm.find('#label').width();
//滑块背景
me.sliderBg = me.elm.find('#slider_bg');
//鼠标在滑动按钮的位置
me.lableIndex = opts.lableIndex;
//success
me.success = success;
}
SliderUnlock.prototype.init = function () {
var me = this;
me.updateView();
me.elm.find("#label").on("mousedown", function (event) {
var e = event || window.event;
me.lableIndex = e.clientX - this.offsetLeft;
me.handerIn();
}).on("mousemove", function (event) {
me.handerMove(event);
}).on("mouseup", function (event) {
me.handerOut();
}).on("mouseout", function (event) {
me.handerOut();
}).on("touchstart", function (event) {
var e = event || window.event;
me.lableIndex = e.originalEvent.touches[0].pageX - this.offsetLeft;
me.handerIn();
}).on("touchmove", function (event) {
me.handerMove(event, "mobile");
}).on("touchend", function (event) {
me.handerOut();
});
};
SliderUnlock.prototype.getIsOk = function() {
return this.isOk;
};
/**
* 鼠标/手指接触滑动按钮
*/
SliderUnlock.prototype.handerIn = function () {
var me = this;
me.swipestart = true;
me.min = 0;
me.max = me.elm.width();
};
/**
* 鼠标/手指移出
*/
SliderUnlock.prototype.handerOut = function () {
var me = this;
//停止
me.swipestart = false;
//me.move();
if (me.index < me.max) {
me.reset();
}
};
/**
* 鼠标/手指移动
* @param event
* @param type
*/
SliderUnlock.prototype.handerMove = function (event, type) {
var me = this;
if (me.swipestart) {
event.preventDefault();
event = event || window.event;
if (type == "mobile") {
me.index = event.originalEvent.touches[0].pageX - me.lableIndex;
} else {
me.index = event.clientX - me.lableIndex;
}
me.move();
}
};
/**
* 鼠标/手指移动过程
*/
SliderUnlock.prototype.move = function () {
var me = this;
if ((me.index + me.labelWidth) >= me.max) {
me.index = me.max - me.labelWidth -2;
//停止
me.swipestart = false;
//解锁
me.isOk = true;
}
if (me.index < 0) {
me.index = me.min;
//未解锁
me.isOk = false;
}
if (me.index+me.labelWidth+2 == me.max && me.max > 0 && me.isOk) {
//解锁默认操作
$('#label').unbind().next('#labelTip').
text(me.opts.successLabelTip).css({'color': '#fff'});
me.success();
}
me.updateView();
};
/**
* 更新视图
*/
SliderUnlock.prototype.updateView = function () {
var me = this;
me.sliderBg.css('width', me.index);
me.elm.find("#label").css("left", me.index + "px")
};
/**
* 重置slide的起点
*/
SliderUnlock.prototype.reset = function () {
var me = this;
me.index = 0;
me.sliderBg .animate({'width':0},me.opts.duration);
me.elm.find("#label").animate({left: me.index}, me.opts.duration)
.next("#lableTip").animate({opacity: 1}, me.opts.duration);
me.updateView();
};
/**
* 检测元素是否存在
* @param elm
* @returns {boolean}
*/
SliderUnlock.prototype.checkElm = function (elm) {
if($(elm).length > 0){
return true;
}else{
throw "this element does not exist.";
}
};
/**
* 检测传入参数是否是function
* @param fn
* @returns {boolean}
*/
SliderUnlock.prototype.checkFn = function (fn) {
if(typeof fn === "function"){
return true;
}else{
throw "the param is not a function.";
}
};
window['SliderUnlock'] = SliderUnlock;
})(jQuery, window, document);

View File

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

View File

@ -5,15 +5,14 @@ function familiar(theWord) {
$.ajax({
type:"GET",
url:"/" + username + "/" + word + "/familiar",
success:function(response){
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 });
renderWord({word: theWord, freq: new_freq});
}
} else {
if(new_freq <1) {
@ -33,11 +32,11 @@ function unfamiliar(theWord) {
$.ajax({
type:"GET",
url:"/" + username + "/" + word + "/unfamiliar",
success:function(response){
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 });
renderWord({word: theWord, freq: new_freq});
} else {
$("#freq_" + theWord).text(new_freq);
}
@ -51,7 +50,7 @@ function delete_word(theWord) {
$.ajax({
type:"GET",
url:"/" + username + "/" + word + "/del",
success:function(response){
success:function(response) {
const allow_move = document.getElementById("move_dynamiclly").checked;
if (allow_move) {
removeWord(theWord);
@ -69,6 +68,7 @@ function read_word(theWord) {
Reader.read(to_speak, inputSlider.value);
}
/*
* interface Word {
* word: string,
@ -103,9 +103,12 @@ function wordTemplate(word) {
<a class="btn btn-warning" onclick="unfamiliar('${word.word}')" role="button">不熟悉</a>
<a class="btn btn-danger" onclick="delete_word('${word.word}')" role="button">删除</a>
<a class="btn btn-info" onclick="read_word('${word.word}')" role="button">朗读</a>
<a class="btn btn-primary" onclick="addNote('{{ word }}'); saveNote('{{ word }}')" role="button">笔记</a> <!-- Modify to call addNote and then saveNote -->
<input type="text" id="note_{{ word }}" class="note-input" placeholder="输入笔记内容" style="display:none;" oninput="saveNote('{{ word }}')"> <!-- Added oninput event -->
</p>`;
}
/**
* 删除某一词频元素
* 此处word为词频元素对应的单词
@ -114,7 +117,7 @@ function removeWord(word) {
// 根据词频信息删除元素
word = word.replace('&amp;', '&');
const element_to_remove = document.getElementById(`p_${word}`);
if (element_to_remove != null) {
if (element_to_remove !== null) {
element_to_remove.remove();
}
}
@ -129,7 +132,7 @@ function renderWord(word) {
for (const current of container.children) {
const cur_word = parseWord(current);
// 找到第一个词频比它小的元素,插入到这个元素前面
if (compareWord(cur_word, word) == -1) {
if (compareWord(cur_word, word) === -1) {
container.insertBefore(new_element, current);
inserted = true;
break;
@ -165,17 +168,67 @@ function elementFromString(string) {
* 当first大于second时返回1
*/
function compareWord(first, second) {
if (first.freq < second.freq) {
return -1;
if (first.freq !== second.freq) {
return first.freq < second.freq ? -1 : 1;
}
if (first.freq > second.freq) {
return 1;
}
if (first.word < second.word) {
return -1;
}
if (first.word > second.word) {
return 1;
if (first.word !== second.word) {
return first.word < second.word ? -1 : 1;
}
return 0;
}
/* 生词csv导出 */
function exportToCSV() {
let csvContent = "data:text/csv;charset=utf-8,Word,Frequency\n";
let rows = document.querySelectorAll(".new-word");
rows.forEach(row => {
let word = row.querySelector("a.btn-light").innerText;
let freq = row.querySelector("a[title]").innerText;
csvContent += word + "," + freq + "\n";
});
let encodedUri = encodeURI(csvContent);
let link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "word_list.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
*
* 随机选取 10 个单词学习
*/
function random_select_word(word) {
// 获取所有带有 "word-container" 类的 <p> 标签
const container = document.querySelector('.word-container');
console.log("container",container)
// 获取所有带有"new-word"类的<p>标签
let wordContainers = container.querySelectorAll('.new-word');
// 检查是否存在带有"new-word"类的<p>标签
if (wordContainers.length > 0) {
// 将NodeList转换为数组
let wordContainersArray = [...wordContainers];
// 随机打乱数组,乱序
for (let i = wordContainersArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[wordContainersArray[i], wordContainersArray[j]] = [wordContainersArray[j], wordContainersArray[i]];
}
wordContainersArray.forEach((p, index) => {
if (index < 10) {
p.style.display = 'block';
} else {
p.style.display = 'none';
}
});
}
}

View File

@ -7,6 +7,11 @@
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" />
<link href="../static/css/bootstrap.css" rel="stylesheet">
<script>
function confirmDeletion(articleId, articleTitle) {
return confirm(`确认删除文章 "${articleTitle}" (ID: ${articleId}) 吗?`);
}
</script>
</head>
<body class="container" style="width: 800px; margin: auto; margin-top:24px;">
@ -66,9 +71,10 @@
<div class="list-group">
{% for text in text_list %}
<div class="list-group-item list-group-item-action" aria-current="true">
<div>
<a type="button" href="/admin/article?delete_id={{text.article_id}}" class="btn btn-outline-danger btn-sm">删除</a>
</div>
<form action="/admin/article" method="post" style="display: inline;">
<input type="hidden" name="delete_id" value="{{ text.article_id }}">
<button type="submit" class="btn btn-outline-danger btn-sm" onclick="return confirmDeletion('{{ text.article_id }}', '{{ text.title }}')">删除</button>
</form>
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{ text.title }}</h5>
</div>

View File

@ -1,45 +1,107 @@
{% block body %}
{% if session['logged_in'] %}
你已登录 <a href="/{{ session['username'] }}">{{ session['username'] }}</a>。 登出点击<a href="/logout">这里</a>
你已登录 <a href="/{{ session['username'] }}/userpage">{{ session['username'] }}</a>。 登出点击<a href="/logout">这里</a>
{% 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">
<script src="static/js/jquery.js"></script>
<script>
function login(){
let blackList = [];
<!--function getBlack() {-->
<!-- const fs = require('fs');-->
<!-- global.blackFile = fs.readFileSync('black', 'utf8');-->
<!-- const blackListTemp = blackFile.split('\n');-->
<!-- global.blackList = blackListTemp.map(line => line.trim()).filter(line => line !== '');-->
<!--}-->
function putUserIntoBlack(usernameTemp) {
blackList.push(usernameTemp);
}
function ifUsernameInBlack(usernameTemp) {
return blackList.includes(usernameTemp);
}
count=0
function login()
{
let username = $("#username").val();
let password = $("#password").val();
if (username === "" || password === ""){
alert('输入不能为空!');
return false;
}
$.post(
"/login", {'username': username, 'password': password},
function (response) {
if (response.status === '0') {
alert('无法通过验证。');
window.location.href = "/login";
} else if (response.status === '1') {
window.location.href = "/"+username+"/userpage";
if (password.includes(' ')) {
alert('输入不能包含空格!');
return false;
}
$.post
(
"/login", {'username': username, 'password': password},
function (response)
{
if(response.status === '5')
{
alert('已被加入黑名单,请联系管理员!');
}
}
)
else{
if(!ifUsernameInBlack(username))
{
if (response.status === '0')
{
if(count<5)
{
alert('无法通过验证。');
<!--window.location.href = "/login";-->
count++;
}
else
{
<!--输入错误密码次数超过5次-->
alert('密码输入错误超过五次,已被加入黑名单!');
putUserIntoBlack(username);
console.log(ifUsernameInBlack(username));
response.status=5;
$("#password").val('黑名单');
}
}
else if (response.status === '1')
{
window.location.href = "/"+username+"/userpage";
}
}
else if(ifUsernameInBlack(username))
{
alert('已被加入黑名单!');
}
}
}
)
return false;
}
</script>
<div class="container">
<section class="signin-heading">
<h1>Sign In</h1>
<h1>Sign in</h1>
</section>
<input type="text" placeholder="用户名" class="username" id="username">
<input type="password" placeholder="密码" class="password" id="password">
<button type="button" class="btn" onclick="login()">登录</button>
<a class="signup" href="/signup">注册</a>
</div>
{% endif %}

View File

@ -31,12 +31,12 @@
<p><a href="/login">登录</a> <a href="/signup">注册</a> <a href="/static/usr/instructions.html">使用说明</a></p >
<p><b> {{ random_ads }}。 <a href="/signup">试试</a>吧!</b></p>
{% endif %}
<div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span></div>
<div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span>,覆盖 <span class="badge bg-success"> {{ (ratio * 100) | int }}% </span> 的 Oxford5000 单词</div>
<p>粘贴1篇文章 (English only)</p>
<form method="post" action="/">
<textarea name="content" rows="10" cols="120"></textarea><br/>
<textarea name="content" id="article" rows="10" cols="120"></textarea><br/>
<input type="submit" value="get文章中的词频"/>
<input type="reset" value="清除"/>
<input type="reset" value="清除" onclick="clearArticle()"/>
</form>
{% if d_len > 0 %}
<p><b>最常见的词</b></p>
@ -44,6 +44,7 @@
<a href="http://youdao.com/w/eng/{{x[0]}}/#keyfrom=dict2.index">{{x[0]}}</a> {{x[1]}}
{% endfor %}
{% endif %}
<p class="text-muted">Version: 20240618</p>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</div>
{{ yml['footer'] | safe }}
@ -52,5 +53,22 @@
<script src="{{ js }}" ></script>
{% endfor %}
{% endif %}
<script type="text/javascript">
// IIFE, avoid polluting the global scope
(function() {
const articleInput = document.querySelector('#article');
articleInput.value = localStorage.getItem('article') || '';
articleInput.addEventListener('input', function() {
localStorage.setItem('article', articleInput.value);
});
window.clearArticle = function() {
localStorage.removeItem('article');
articleInput.value = '';
};
})();
</script>
</body>
</html>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<title>单词词频</title>
{{ yml['header'] | safe }}
{% if yml['css']['item'] %}

View File

@ -12,6 +12,10 @@
alert('输入不能为空!');
return false;
}
if (old_password.includes(' ') || new_password.includes(' ')) {
alert('输入不能包含空格!');
return false;
}
if (new_password !== re_new_password) {
alert('新密码不匹配,请重新输入');
return false;

View File

@ -1,68 +1,107 @@
{% block body %}
{% if session['logged_in'] %}
You're logged in already! <a href="/logout">Logout</a>.
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">
<script src="static/js/jquery.js"></script>
<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">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE-edge,chrome=1">
<link href="static/css/slide-unlock.css" rel="stylesheet">
<script src="static/js/jquery.js"></script>
<script src="static/js/jquery.slideunlock.js"></script>
<script>
function signup() {
let username = $("#username").val();
let password = $("#password").val();
let password2 = $("#password2").val();
if (username === "" || password === "" || password2 === ""){
alert('输入不能为空!');
return false;
}
if (password !== password2) {
alert('确认密码与输入密码不一致!');
return false;
}
if (password.length < 4) {
alert('密码过于简单。(密码长度至少4位)');
return false;
}
$.post("/signup", {'username': username, 'password': password},
function (response) {
if (response.status === '0') {
alert('用户名'+username+'已经被注册。');
window.location.href = "/signup";
} else if (response.status === '1') {
alert('用户名密码验证失败。');
window.location.href = "/signup";
} else if (response.status === '2') {
let f = confirm("恭喜,你已成功注册,你的用户名是"+username+'.\n点击“确认”开始使用或点击“取消”返回首页');
if (f) {
window.location.href = '/'+username+'/userpage';
} else {
window.location.href = '/';
}
} else if (response.status === '3') {
alert(response.warn);
}
}
)
var slider
let username,password,password2
$(document).ready(function() {
slider = new SliderUnlock("#slider", {
successLabelTip: "验证成功"
}, function() {
});
slider.init(); // 初始化滑块解锁功能
});
function signup(){
// 发起 AJAX 请求来处理注册
username = $("#username").val().trim();
password = $("#password").val().trim();
password2 = $("#password2").val().trim();
// 基本表单验证
if (username === "" || password === "" || password2 === "") {
alert('输入不能为空!');
return false;
}
if (password.includes(' ') || password2.includes(' ')) {
alert('输入不能包含空格!');
return false;
}
if (password !== password2) {
alert('确认密码与输入密码不一致!');
return false;
}
if (password.length < 4) {
alert('密码过于简单。(密码长度至少4位)');
return false;
}
is_ok = slider.getIsOk();
if(!is_ok){
alert('没有滑动验证');
return false;
}
$.post("/signup", {
'username': username,
'password': password
}, function(response) {
if (response.status === '0') {
alert('用户名' + username + '已经被注册。');
window.location.href = "/signup";
} else if (response.status === '1') {
alert('用户名密码验证失败。');
window.location.href = "/signup";
} else if (response.status === '2') {
var f = confirm("恭喜,你已成功注册,你的用户名是" + username + '.\n点击“确认”开始使用或点击“取消”返回首页');
if (f) {
window.location.href = '/' + username + '/userpage';
} else {
window.location.href = '/';
}
} else if (response.status === '3') {
alert(response.warn);
}
});
return false;
}
</script>
<p>{{ get_flashed_messages()[0] | safe }}</p>
<div class="container">
<section class="signin-heading">
<h1>Sign up</h1>
</section>
<form>
<p><input type="text" id="username" placeholder="输入用户名" class="username"></p>
<p><input type="password" id="password" placeholder="输入密码" class="password"></p>
<p><input type="password" id="password2" placeholder="确认密码" class="password"></p>
<div id="slider">
<div id="slider_bg"></div>
<span id="label">>></span> <span id="labelTip">-----滑动验证你是不是人类</span>
</div>
<button type="button" class="btn" onclick="signup()">注册</button>
</form>
</div>
<script>
// Bind click event to the signup button
$(".btn").click(function() {
// Trigger slider unlock
var slider = new SliderUnlock("#slider");
slider.isOk();
});
</script>
<p>{{ get_flashed_messages()[0] | safe }}</p>
<div class="container">
<section class="signin-heading">
<h1>Sign Up</h1>
</section>
<p><input type="username" id="username" placeholder="输入用户名" class="username"></p>
<p><input type="password" id="password" placeholder="输入密码" class="password"></p>
<p><input type="password" id="password2" placeholder="确认密码" class="password" ></p>
<button type="button" class="btn" onclick="signup()">注册</button>
</div>
{% endif %}
{% endblock %}

View File

@ -1,10 +1,14 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
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"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="../static/css/button.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script>
{{ yml['header'] | safe }}
{% if yml['css']['item'] %}
@ -21,17 +25,35 @@
<title>EnglishPal Study Room for {{ username }}</title>
<style>
.shaking {
animation: shakes 1600ms ease-in-out;
}
.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); }
}
.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;
}
@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>
@ -45,22 +67,42 @@
<a class="btn btn-secondary" href="/reset" role="button" onclick="stopRead()">重设密码</a>
</p>
{# {% for message in flashed_messages %}#} {# 根据user_service.userpage,取消了参数flashed_messages因此注释了这段代码 #}
{# <div class="alert alert-warning" role="alert">Congratulations! {{ message }}</div>#}
{# {% endfor %}#}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{ 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()"> 下一篇 Next Article </button>
<button class="btn btn-success" id="load_pre_article" onclick="load_pre_article()" > 上一篇 Previous Article </button>
<div class="pagination">
<button class="arrow" id="load_pre_article" onclick="load_pre_article();Reader.stopRead()" title="Previous Article">
<i class="fas fa-chevron-left"></i> 上一篇
</button>
<button class="arrow" id="load_next_article" onclick="load_next_article();Reader.stopRead()" title="Next Article">
下一篇 <i class="fas fa-chevron-right"></i>
</button>
</div>
<p><b>阅读文章并回答问题</b></p>
<div id="text-content">
<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>
<p class="text-muted" id="date">Article added on: {{ today_article["date"] }}</p><br/>
<div class="p-3 mb-2 bg-light text-dark"><br/>
<p class="display-5" id="article_title">{{ today_article["article_title"] }}</p><br/>
<p class="lead"><font id="article" size=2>{{ today_article["article_body"] }}</font></p><br/>
<p><small class="text-muted" id="source">{{ today_article['source'] }}</small></p><br/>
<div class="alert alert-success" role="alert">According to your word list, your level is <span class="text-decoration-underline" id="user_level">{{ today_article["user_level"] }}</span> and we have chosen an article with a difficulty level of <span class="text-decoration-underline" id="text_level">{{ today_article["text_level"] }}</span> for you. The Oxford word coverage is <span class="text-decoration-underline" id="ratio">{{ (today_article["ratio"] * 100) | int }}%.</span></div>
<p class="text-muted" id="date">Article added on: {{ today_article["date"] }}</p><br/>
<button onclick="saveArticle()" >标记文章</button>
<select id="saved_articles_dropdown">
<!-- 这里将显示已经保存的文章 -->
<option></option>
</select>
<div class="p-3 mb-2 bg-light text-dark" style="margin: 0 0.5%;"><br/>
<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">
function toggle_visibility(id) { {# https://css-tricks.com/snippets/javascript/showhide-element/#}
@ -74,6 +116,7 @@
<button onclick="toggle_visibility('answer');">ANSWER</button>
<div id="answer" style="display:none;">{{ today_article['answer'] }}</div><br/>
</div>
<div id="tooltip"></div>
</div>
<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>
@ -83,22 +126,22 @@
</div>
</div>
<input type="checkbox" onclick="toggleHighlighting()" checked/>生词高亮
<input type="checkbox" onclick="onReadClick()" checked/>大声朗读
<input type="checkbox" onclick="onChooseClick()" checked/>划词入库
<input type="checkbox" id="highlightCheckbox" onclick="toggleHighlighting()" />生词高亮
<input type="checkbox" id="readCheckbox" onclick="onReadClick()" />大声朗读
<input type="checkbox" id="chooseCheckbox" onclick="onChooseClick()" />划词入库
<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"/>
<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 }}/userpage">
<textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/>
<input type="submit" value="把生词加入我的生词库"/>
<input type="reset" value="清除"/>
<button class="btn btn-primary btn-lg" type="submit" onclick="Reader.stopRead()">把生词加入我的生词库</button>
<button class="btn btn-primary btn-lg" type="reset" onclick="clearSelectedWords()">清除</button>
</form>
{% if session.get['thisWord'] %}
<script type="text/javascript">
@ -113,12 +156,18 @@
{% if d_len > 0 %}
<p>
<b>我的生词簿</b>
<b>我的生词簿</b>
<label for="move_dynamiclly">
<input type="checkbox" name="move_dynamiclly" id="move_dynamiclly" checked>
允许动态调整顺序
</label>
<br>
<a class="btn btn-primary btn-lg" onclick="random_select_word('{{ word }}')" role="button">随机选取10个</a>
<a class="btn btn-primary btn-lg" onclick="location.reload();" role="button">显示所有生词</a>
</p>
<!--添加导出按钮-->
<button class="btn btn-primary" onclick="exportToCSV()">导出</button>
<a name="aaa"></a>
<div class="word-container">
{% for x in lst3 %}
@ -134,10 +183,13 @@
<a class="btn btn-warning" onclick="unfamiliar('{{ word }}')" role="button">不熟悉</a>
<a class="btn btn-danger" onclick="delete_word('{{ word }}')" role="button">删除</a>
<a class="btn btn-info" onclick="read_word('{{ word }}')" role="button">朗读</a>
<a class="btn btn-primary" onclick="addNote('{{ word }}'); saveNote('{{ word }}')" role="button">笔记</a> <!-- Modify to call addNote and then saveNote -->
<input type="text" id="note_{{ word }}" class="note-input" placeholder="输入笔记内容" style="display:none;" oninput="saveNote('{{ word }}')"> <!-- Added oninput event -->
</p>
{% endfor %}
</div>
<input id="selected-words2" type="hidden" value="{{ words }}">
{% endif %}
</div>
{{ yml['footer'] | safe }}
@ -146,7 +198,79 @@
<script src="{{ js }}"></script>
{% endfor %}
{% endif %}
<script type="text/javascript">
// Function to show/hide note input and load saved note content from localStorage
function addNote(word) {
var noteInput = document.getElementById("note_" + word);
var savedNote = localStorage.getItem(word); // Get the saved note from localStorage
if (savedNote) {
noteInput.value = savedNote; // Set the saved note if it exists
}
noteInput.style.display = (noteInput.style.display === 'none') ? 'inline-block' : 'none'; // Toggle display
}
// Example function to save the note to localStorage
function saveNote(word) {
var noteContent = document.getElementById("note_" + word).value;
localStorage.setItem(word, noteContent); // Save the note content in localStorage
console.log('Note saved for ' + word + ': ' + noteContent); // Log for debugging purposes
}
</script>
<script type="text/javascript">
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').addClass('gray-background');
}
// 事件监听器
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(){
$.ajax({
url: '/get_next_article/{{username}}',
@ -154,6 +278,9 @@
success: function(data) {
// 更新页面内容
if(data['today_article']){
// answer不可见
const e = document.getElementById('answer');
e.style.display = 'none';
update(data['today_article']);
check_pre(data['visited_articles']);
check_next(data['result_of_generate_article']);
@ -169,6 +296,9 @@
success: function(data) {
// 更新页面内容
if(data['today_article']){
// answer不可见
const e = document.getElementById('answer');
e.style.display = 'none';
update(data['today_article']);
check_pre(data['visited_articles']);
toggleHighlighting();
@ -177,7 +307,7 @@
});
}
function update(today_article){
$('#user-level').html(today_article['user_level']);
$('#user_level').html(today_article['user_level']);
$('#text_level').html(today_article["text_level"]);
$('#date').html('Article added on: '+today_article["date"]);
$('#article_title').html(today_article["article_title"]);
@ -185,13 +315,19 @@
$('#source').html(today_article['source']);
$('#question').html(today_article["question"]);
$('#answer').html(today_article["answer"]);
$('#ratio').html(Math.round(today_article["ratio"] * 100) + '%');
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)){
$('#load_pre_article').hide();
$('#load_pre_article').addClass('gray-background'); // 设置为灰色
sessionStorage.setItem('pre_page_button', 'display')
}else{
$('#load_pre_article').show();
$('#load_pre_article').removeClass('gray-background'); // 设置为正常蓝色
sessionStorage.setItem('pre_page_button', 'show')
}
}
function check_next(result_of_generate_article){
@ -208,11 +344,95 @@
$('#read_all').show();
}
}
function saveArticle() {
const article = {
user_level: document.getElementById('user_level').innerText,
text_level: document.getElementById('text_level').innerText,
date: document.getElementById('date').innerText.replace('Article added on: ', ''),
article_title: document.getElementById('article_title').innerText,
article_body: document.getElementById('article').innerText,
source: document.getElementById('source').innerText,
question: document.getElementById('question').innerText,
answer: document.getElementById('answer').innerText
};
const articleJSON = JSON.stringify(article);
const articleTitle = article.article_title;
const savedArticlesDropdown = document.getElementById('saved_articles_dropdown');
var option = document.createElement('option');
option.text = articleTitle;
option.value = articleJSON; // 存储序列化的JSON字符串
option.title = article.article_title;
savedArticlesDropdown.appendChild(option);
localStorage.setItem(articleTitle, articleJSON); // 以文章标题为键序列化的JSON字符串为值存储
}
function loadSelectedArticle() {
const selectedOption = document.getElementById('saved_articles_dropdown');
const selectedTitle = selectedOption.options[selectedOption.selectedIndex].text;
const articleJSON = localStorage.getItem(selectedTitle);
if (articleJSON) {
const today_article = JSON.parse(articleJSON); // 解析JSON字符串为对象
update(today_article); // 使用解析出的对象更新页面
}
}
window.onload = function() {
const savedArticlesDropdown = document.getElementById('saved_articles_dropdown');
savedArticlesDropdown.addEventListener('change', loadSelectedArticle);
// 先清空dropdown以防有多余的选项或重新加载页面时出现重复
savedArticlesDropdown.innerHTML = '';
// 获取localStorage中最后一个最新的键值对
let latestKey, latestValue;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
if (!latestKey) { // 第一次迭代时设置最新文章
latestKey = key;
latestValue = value;
}
}
// 首先添加最新保存的文章到下拉菜单
if (latestKey && latestValue) {
var latestOption = document.createElement('option');
latestOption.text = latestKey;
latestOption.value = latestValue;
latestOption.title = latestValue;
savedArticlesDropdown.appendChild(latestOption);
}
// 接着遍历其余文章并添加到下拉菜单
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
// 确保不重复添加最新文章
if (key !== latestKey && key !== 'selectedWords') {
var option = document.createElement('option');
option.text = key;
option.value = value;
option.title = value;
savedArticlesDropdown.appendChild(option);
}
}
savedArticlesDropdown.selectedIndex = -1;
}
document.getElementById('rangeComponent').addEventListener('input', function() {
var rate = this.value;
Reader.updateRate(rate);
});
</script>
</body>
<style>
mark {
color: #{{ yml['highlight']['color'] }};
color: red;
background-color: rgba(0, 0, 0, 0);
}
</style>

View File

@ -1,45 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" 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" />
<head>
<meta charset="UTF-8">
<meta name="viewport" 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" />
{{ yml['header'] | safe }}
{% if yml['css']['item'] %}
{% for css in yml['css']['item'] %}
<link href="{{ css }}" rel="stylesheet">
{% endfor %}
{% endif %}
{% if yml['js']['head'] %}
{% for js in yml['js']['head'] %}
<script src="{{ js }}" ></script>
{% endfor %}
{% endif %}
{{ yml['header'] | safe }}
{% if yml['css']['item'] %}
{% for css in yml['css']['item'] %}
<link href="{{ css }}" rel="stylesheet">
{% endfor %}
{% endif %}
{% if yml['js']['head'] %}
{% for js in yml['js']['head'] %}
<script src="{{ js }}" ></script>
{% endfor %}
{% endif %}
<title>EnglishPal Study Room for {{username}}</title>
</head>
<body>
<p>取消勾选认识的单词</p>
<form method="post" action="/{{username}}/mark">
<input type="submit" name="add-btn" value="加入我的生词簿"/>
{% for x in lst %}
{% set word = x[0]%}
<p>
<font color="grey">{{loop.index}}</font>
:
<a href='http://youdao.com/w/eng/{{word}}/#keyfrom=dict2.index' title={{word}}>{{word}}</a>
({{x[1]}})
<input type="checkbox" name="marked" value="{{word}}" checked>
</p>
<title>EnglishPal Study Room for {{username}}</title>
</head>
<body>
<div class="container-fluid">
<p class="mt-md-3">
<input type="button" id="btn-cancel-selection" value="取消勾选" onclick="toggleCheckboxSelection(false)" />
<input type="button" id="btn-selection" value="全部勾选" onclick="toggleCheckboxSelection(true)" />
</p>
<form method="post" action="/{{username}}/mark">
<button type="submit" name="add-btn" class="btn btn-outline-primary btn-lg" onclick="clearSelectedWords()">加入我的生词簿</button>
{% for x in lst %}
{% set word = x[0]%}
<p>
<font color="grey">{{loop.index}}</font>
:
<a href='http://youdao.com/w/eng/{{word}}/#keyfrom=dict2.index' title={{word}}>{{word}}</a>
({{x[1]}})
<input type="checkbox" name="marked" value="{{word}}" checked>
</p>
{% endfor %}
</form>
{{ yml['footer'] | safe }}
{% if yml['js']['bottom'] %}
{% for js in yml['js']['bottom'] %}
<script src="{{ js }}" ></script>
{% endfor %}
{% endif %}
</body>
{% endfor %}
</form>
{{ yml['footer'] | safe }}
{% if yml['js']['bottom'] %}
{% for js in yml['js']['bottom'] %}
<script src="{{ js }}" ></script>
{% endfor %}
{% endif %}
</div>
<script>window.history.replaceState(null, null, window.location.href);
</script>
</body>
</html>

View File

@ -1,6 +1,9 @@
import pytest
import sqlite3
import time
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from pathlib import Path
@pytest.fixture
def URL():
@ -9,5 +12,25 @@ def URL():
@pytest.fixture
def driver():
my_driver = webdriver.Chrome()
return my_driver
return webdriver.Edge() # follow the "End-to-end testing" section in README.md to install the web driver executable
@pytest.fixture
def restore_sqlite_database():
'''
Automatically restore SQLite database file app/db/wordfreqapp.db
using SQL statements from app/static/wordfreqapp.sql
'''
con = sqlite3.connect('../db/wordfreqapp.db')
with con:
con.executescript('DROP TABLE IF EXISTS user;')
con.executescript('DROP TABLE IF EXISTS article;')
con.executescript(open('../static/wordfreqapp.sql', encoding='utf8').read())
con.close()
@pytest.fixture(autouse=True)
def restart_englishpal(restore_sqlite_database):
(Path(__file__).parent / '../main.py').touch()
time.sleep(1)

33
app/test/helper.py Normal file
View File

@ -0,0 +1,33 @@
import uuid
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import UnexpectedAlertPresentException, NoAlertPresentException
def signup(URL, driver):
username = 'TestUser' + str(uuid.uuid1()).split('-')[0].title()
password = '[Abc+123]'
driver.get(URL)
elem = driver.find_element_by_link_text('注册')
elem.click()
elem = driver.find_element_by_id('username')
elem.send_keys(username)
elem = driver.find_element_by_id('password')
elem.send_keys(password)
elem = driver.find_element_by_id('password2')
elem.send_keys(password)
elem = driver.find_element_by_class_name('btn') # 找到"注册"按钮
elem.click()
try:
WebDriverWait(driver, 1).until(EC.alert_is_present())
driver.switch_to.alert.accept()
except (UnexpectedAlertPresentException, NoAlertPresentException):
pass
return username, password

View File

@ -1,76 +1,31 @@
# -*- coding: utf-8 -*-
# Run the docker image using the following command:
# docker run -d -p 4444:4444 selenium/standalone-chrome
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import random, time
import string
driver = webdriver.Remote('http://localhost:4444/wd/hub', DesiredCapabilities.FIREFOX)
driver.implicitly_wait(10)
HOME_PAGE = 'http://121.4.94.30:91/'
import time
from helper import signup
def has_punctuation(s):
return [c for c in s if c in string.punctuation] != []
def test_add_word():
def test_add_word(URL, driver):
try:
driver.get(HOME_PAGE)
assert 'English Pal -' in driver.page_source
# login
elem = driver.find_element_by_link_text('登录')
elem.click()
uname = 'lanhui'
password = 'l0ve1t'
elem = driver.find_element_by_name('username')
elem.send_keys(uname)
elem = driver.find_element_by_name('password')
elem.send_keys(password)
elem = driver.find_element_by_xpath('//form[1]/p[3]/input[1]') # 找到登录按钮
elem.click()
assert 'EnglishPal Study Room for ' + uname in driver.title
# get essay content
elem = driver.find_element_by_id('text-content')
essay_content = elem.text
elem = driver.find_element_by_id('selected-words')
word = random.choice(essay_content.split())
while 'font>' in word or 'br>' in word or 'p>' in word or len(word) < 6 or has_punctuation(word):
word = random.choice(essay_content.split())
username, password = signup(URL, driver) # sign up a new account and automatically log in
time.sleep(1)
# enter the word in the text area
elem = driver.find_element_by_id('selected-words')
word = 'devour'
elem.send_keys(word)
elem = driver.find_element_by_xpath('//form[1]//input[1]') # 找到get所有词频按钮
elem.click()
elems = driver.find_elements_by_xpath("//input[@type='checkbox']")
for elem in elems:
if elem.get_attribute('name') == 'marked':
elem.click()
elem = driver.find_element_by_name('add-btn') # 找到加入我的生词簿按钮
elem = driver.find_element_by_xpath('//form[1]//button[1]') # 找到"把生词加入我的生词库"按钮
elem.click()
elem = driver.find_element_by_name('add-btn') # 找到"加入我的生词簿"按钮
elem.click()
driver.refresh()
driver.refresh()
driver.refresh()
elems = driver.find_elements_by_xpath("//p[@class='new-word']/a")
found = 0
for elem in elems:
if word in elem.text:
found = 1
break
assert found == 1
finally:
finally:
driver.quit()

View File

@ -0,0 +1,95 @@
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException
# 测试登录页面输入密码包含空格的情况
def test_login_password_with_space(driver, URL):
try:
driver.get(URL+"/login")
# 输入用户名
username_elem = driver.find_element_by_id('username')
username_elem.send_keys("test_user")
# 输入包含空格的密码
password_elem = driver.find_element_by_id('password')
password_elem.send_keys("password with space")
# 提交登录表单
elem = driver.find_element_by_class_name('btn') # 找到提交按钮
elem.click()
# 显式等待直到警告框出现
WebDriverWait(driver, 10).until(EC.alert_is_present())
# 检查是否弹出警告框
alert = driver.switch_to.alert
assert "输入不能包含空格!" in alert.text
except (NoSuchElementException, TimeoutException) as e:
pytest.fail("页面元素未找到或超时: {}".format(e))
# 测试注册页面输入密码包含空格的情况
def test_signup_password_with_space(driver, URL):
try:
driver.get(URL+"/signup")
# 输入用户名
username_elem = driver.find_element_by_id('username')
username_elem.send_keys("new_user")
# 输入包含空格的密码
password_elem = driver.find_element_by_id('password')
password_elem.send_keys("password with space")
# 再次输入密码
password2_elem = driver.find_element_by_id('password2')
password2_elem.send_keys("password with space")
# 提交注册表单
elem = driver.find_element_by_class_name('btn') # 找到提交按钮
elem.click()
# 显式等待直到警告框出现
WebDriverWait(driver, 10).until(EC.alert_is_present())
# 检查是否弹出警告框
alert = driver.switch_to.alert
assert "输入不能包含空格!" in alert.text
except (NoSuchElementException, TimeoutException) as e:
pytest.fail("页面元素未找到或超时: {}".format(e))
# 测试重设密码页面输入新密码包含空格的情况
def test_reset_password_with_space(driver, URL):
try:
driver.get(URL+"/reset")
# 输入用户名
username_elem = driver.find_element_by_id('username')
username_elem.send_keys("test_user")
# 输入包含空格的密码
password_elem = driver.find_element_by_id('password')
password_elem.send_keys("password with space")
# 提交重设密码表单
elem = driver.find_element_by_class_name('btn') # 找到提交按钮
elem.click()
# 显式等待直到警告框出现
WebDriverWait(driver, 10).until(EC.alert_is_present())
# 检查是否弹出警告框
alert = driver.switch_to.alert
assert "输入不能包含空格!" in alert.text
except (NoSuchElementException, TimeoutException) as e:
pytest.fail("页面元素未找到或超时: {}".format(e))

View File

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

View File

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

View File

@ -0,0 +1,55 @@
import random
import string
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from helper import signup
def has_punctuation(s):
return any(c in string.punctuation for c in s)
def login(driver, home, uname, password):
driver.get(home)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, '登录'))).click()
driver.find_element(By.ID, 'username').send_keys(uname)
driver.find_element(By.ID, 'password').send_keys(password)
driver.find_element(By.XPATH, '//button[text()="登录"]').click()
WebDriverWait(driver, 10).until(EC.title_is(f"EnglishPal Study Room for {uname}"))
def select_valid_word(driver):
elem = driver.find_element(By.ID, 'text-content')
essay_content = elem.text
valid_word = random.choice([word for word in essay_content.split() if len(word) >= 6 and not has_punctuation(
word) and 'font>' not in word and 'br>' not in word and 'p>' not in word])
driver.find_element(By.ID, 'selected-words').send_keys(valid_word)
return valid_word
def test_save_selected_word(driver, URL):
try:
username, password = signup(URL, driver)
word = select_valid_word(driver)
stored_words = driver.execute_script('return localStorage.getItem("selectedWords");')
assert word == stored_words, "Selected word not saved to localStorage correctly"
# 退出并重新登录以检查存储的单词
driver.find_element(By.LINK_TEXT, '退出').click()
driver.execute_script("window.open('');window.close();")
# 等待一会儿,让浏览器有足够的时间关闭标签页
WebDriverWait(driver, 2)
# 重新打开一个新的标签页
driver.execute_script("window.open('');")
driver.switch_to.window(driver.window_handles[-1]) # 切换到新打开的标签页
login(driver, URL, username, password)
textarea_content = driver.find_element(By.ID, 'selected-words').get_attribute('value')
assert word == textarea_content, "Selected word not preserved after re-login"
finally:
driver.quit()

View File

@ -0,0 +1,44 @@
import random
import string
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from helper import signup
def has_punctuation(s):
return any(c in string.punctuation for c in s)
def select_one(driver):
elem = driver.find_element(By.ID, 'article')
essay_content = elem.text
valid_word = random.choice([word for word in essay_content.split() if len(word) >= 6 and not has_punctuation(
word) and 'font>' not in word and 'br>' not in word and 'p>' not in word])
driver.find_element(By.ID, 'selected-words').send_keys(valid_word)
driver.find_element(By.ID, 'article').click()
return valid_word
def select_two(driver):
word = driver.find_element(By.CLASS_NAME, 'highlighted')
# 创建ActionChains对象
actions = ActionChains(driver)
actions.move_to_element(word)
# 模拟鼠标按下并拖动以选择文本
actions.double_click()
actions.perform()
def test_selected_second_word(driver, URL):
try:
signup(URL, driver)
selected_words = select_one(driver);
assert selected_words.strip() != "", "选中的单词被放置框中"
select_two(driver)
selected_second_words = driver.find_element(By.ID, 'selected-words').get_attribute('value')
assert selected_second_words.strip() == "", "选中的单词被删除"
finally:
driver.quit()

View File

@ -0,0 +1,39 @@
from selenium.webdriver.common.action_chains import ActionChains
from helper import signup
def test_highlight(driver, URL):
try:
# 打开网页
driver.get(URL)
driver.maximize_window()
# 注册
signup(URL, driver)
# 取消勾选“划词入库按钮”
highlight_checkbox = driver.find_element_by_id("chooseCheckbox")
driver.execute_script("arguments[0].click();", highlight_checkbox)
article = driver.find_element_by_id("article")
# 创建 ActionChains 对象
actions = ActionChains(driver)
# 移动鼠标到起点位置
actions.move_to_element(article)
# actions.move_to_element_with_offset(article, 50, 100)
# 按下鼠标左键
actions.click_and_hold()
# 拖动鼠标到结束位置
actions.move_by_offset(400,50)
# 释放鼠标左键
actions.release()
# 执行操作链
actions.perform()
# time.sleep(10)
assert driver.find_elements_by_class_name("highlighted") is not None
finally:
# 测试结束后关闭浏览器
driver.quit()

View File

@ -0,0 +1,37 @@
import time
import pytest
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from helper import signup
def test_bug551(driver, URL):
driver.maximize_window()
driver.get(URL)
username, password = signup(URL, driver)
article = driver.find_element(By.ID, 'article')
actions = ActionChains(driver)
actions.move_to_element(article)
actions.click_and_hold()
actions.move_by_offset(450, 200)
actions.release()
actions.perform()
# 获取选中高亮部分的单词的元素
highlighted_words = driver.find_elements(By.CLASS_NAME, 'highlighted')
# 验证选中部分的单词是否同时应用了需求样式
expected_font_weight = "400"
for word in highlighted_words:
font_weight = word.value_of_css_property("font-weight")
assert font_weight == expected_font_weight, f"选中部分的单词的字体样式错误"
time.sleep(5)
driver.quit()

View File

@ -0,0 +1,58 @@
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support import expected_conditions as EC
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import logging
import time
import pytest
@pytest.mark.parametrize("test_input,expected",
[("test1", "test1"),
("'test2'", "test2"),
("“test3”", "test3"),
("it's", "it's"),
("hello,I'm linshan", ["hello","i'm","linshan"]),
("Happy New Year", ["happy","new","year"]),
("My favorite book is 《Harry Potter》。", ["potter","harry","my","favorite","book","is"])])
def test_bug553_LinShan(test_input,expected, driver, URL):
try:
# 打开对应地址的网页
driver.get(URL)
# 浏览器最大窗口化
driver.maximize_window()
# 判断网页源代码中是否有English Pal -文字
assert 'English Pal -' in driver.page_source
# 将测试的数据输入到主页的textarea里
driver.find_element_by_xpath("//textarea[@name='content']").send_keys(Keys.CONTROL, "a")
driver.find_element_by_xpath("//textarea[@name='content']").send_keys(test_input)
time.sleep(1)
# 点击按钮获取单词
driver.find_element_by_xpath("//input[@value='get文章中的词频']").click()
time.sleep(1)
# 获取筛选后的单词
words = driver.find_elements_by_xpath("//p/a")
# 遍历获取到的单词,并判断单词与预期的相同
for word in words:
# 判断单词是否在预期结果中
assert word.text in expected
# 返回上一页网页
driver.find_element_by_xpath("//input[@value='确定并返回']").click()
time.sleep(0.1)
except Exception as e:
# 输出异常信息
logging.error(e)
# 关闭浏览器
driver.quit()
finally:
driver.quit()

View File

@ -0,0 +1,27 @@
import random
import string
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def test_bug561_LiangZiyue(driver, URL):
try:
driver.get(home)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, '登录'))).click()
driver.find_element(By.ID, 'username').send_keys("wrr")
driver.find_element(By.ID, 'password').send_keys("1234")
driver.find_element(By.XPATH, '//button[text()="登录"]').click()
ele = driver.find_element(By.XPATH,'//font[@id="article"]')
driver.execute_script('arguments[0].scrollIntoView();',ele)
action = ActionChains(driver)
action.click_and_hold(ele)
action.move_by_offset(0,500)
action.perform()
next_ele = driver.find_element(By.ID,'//button[@id="load_next_article"]')
driver.execute_script('arguments[0].scrollIntoView();',next_ele)
next_ele.click()
driver.execute_script('arguments[0].scrollIntoView();',ele)
ele.click()
finally:
driver.quit()

43
app/test/test_stress.py Executable file
View File

@ -0,0 +1,43 @@
''' Contributed by Lin Junhong et al. 2023-06.'''
import requests
import multiprocessing
import time
def stress(username):
try:
data = {
'username': username,
'password': '123123'
}
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36 Edg/114.0.1823.51'
}
session = requests.session()
response = session.post(url='http://127.0.0.1:5000/signup', data=data, headers=headers)
print('Sign up ', response.status_code)
time.sleep(0.5)
response = session.post(url='http://127.0.0.1:5000/login', data=data, headers=headers)
print('Sign in ', response.status_code)
time.sleep(0.5)
response = session.get(url=f'http://127.0.0.1:5000/{username}/userpage', headers=headers)
print('User page', response.status_code)
time.sleep(0.5)
print(session.cookies)
for i in range(5):
response = session.get(url=f'http://127.0.0.1:5000/get_next_article/{username}', headers=headers, cookies=session.cookies)
time.sleep(0.5)
print(f'Next page ({i}) [{username}]')
print(response.status_code)
print(response.json()['today_article']['article_title'])
except Exception as e:
print(e)
if __name__ == '__main__':
username = 'Learner'
pool = multiprocessing.Pool(processes=10)
for i in range(10):
pool.apply_async(stress, (f'{username}{i}',))
pool.close()
pool.join()

52
app/translate.py Normal file
View File

@ -0,0 +1,52 @@
import requests
import hashlib
import time
from urllib.parse import urlencode
# 假设这是从某个配置文件中读取的
class BaiduContent:
APPID = '20240702002090356'
SECRET = '3CcqcMAJdIIpgG0uMS_f'
def generate_sign(q, salt):
"""生成百度翻译API所需的签名"""
appid = BaiduContent.APPID
secret = BaiduContent.SECRET
appid_with_data = appid + q + salt + secret
md5_obj = hashlib.md5(appid_with_data.encode('utf-8'))
return md5_obj.hexdigest()
def translate(q, from_lang, to_lang):
"""调用百度翻译API进行翻译"""
salt = str(int(time.time())) # 生成一个时间戳作为salt
sign = generate_sign(q, salt)
# 封装请求参数
params = {
'q': q,
'from': from_lang,
'to': to_lang,
'appid': BaiduContent.APPID,
'salt': salt,
'sign': sign
}
# 构造请求URL百度翻译API使用POST请求并将参数放在请求体中
url = "http://api.fanyi.baidu.com/api/trans/vip/translate"
# 发送POST请求
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
data = urlencode(params).encode('utf-8') # 注意需要编码为bytes
response = requests.post(url, data=data, headers=headers)
# 检查响应状态码
if response.status_code == 200:
# 解析并返回JSON响应体中的翻译结果
try:
return response.json()['trans_result'][0]['dst']
except (KeyError, IndexError):
return "Invalid response from API"
else:
# 返回错误信息或状态码
return {"error": f"Failed with status code {response.status_code}"}

View File

@ -15,6 +15,9 @@ from wordfreqCMD import sort_in_descending_order
import pickle_idea
import pickle_idea2
import logging
logging.basicConfig(filename='log.txt', format='%(asctime)s %(message)s', level=logging.DEBUG)
# 初始化蓝图
userService = Blueprint("user_bp", __name__)
@ -32,7 +35,9 @@ def get_next_article(username):
else: # 当前不为“null”直接 index+=1
visited_articles["index"] += 1
session["visited_articles"] = visited_articles
logging.debug('/get_next_article: start calling get_today_arcile()')
visited_articles, today_article, result_of_generate_article = get_today_article(user_freq_record, session.get('visited_articles'))
logging.debug('/get_next_arcile: done.')
data = {
'visited_articles': visited_articles,
'today_article': today_article,
@ -129,7 +134,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 = escape(request.form['content'])
content = request.form['content']
f = WordFreq(content)
lst = f.get_freq()
return render_template('userpage_post.html',username=username,lst = lst, yml=Yaml.yml)
@ -176,7 +181,11 @@ def user_mark_word(username):
for word in request.form.getlist('marked'):
lst.append((word, [get_time()]))
d = pickle_idea2.merge_frequency(lst, lst_history)
pickle_idea2.save_frequency_to_pickle(d, user_freq_record)
if len(lst_history) > 999:
flash('You have way too many words in your difficult-words book. Delete some first.')
else:
pickle_idea2.save_frequency_to_pickle(d, user_freq_record)
flash('Added %s.' % (', '.join(request.form.getlist('marked'))))
return redirect(url_for('user_bp.userpage', username=username))
else:
return 'Under construction'

View File

@ -4,11 +4,38 @@
###########################################################################
import collections
import html
import string
import operator
import os, sys # 引入模块sys因为我要用里面的sys.argv列表中的信息来读取命令行参数。
import pickle_idea
def map_percentages_to_levels(percentages):
'''
功能按照加权平均难度给生词本计算难度分计算权重的规则是(10 - 该词汇难度) * 该难度词汇占总词汇的比例再进行归一化处理
输入难度占比字典键代表难度3~8值代表每种难度的单词的占比
输出权重字典键代表难度3~8值代表每种难度的单词的权重
'''
# 已排序的键
sorted_keys = sorted(percentages.keys())
# 计算权重和权重总和
sum = 0 # 总和
levels_proportions = {}
for k in sorted_keys:
levels_proportions[k] = 10 - k
for k in sorted_keys:
levels_proportions[k] *= percentages[k]
sum += levels_proportions[k]
# 归一化权重到权重总和为1
for k in sorted_keys:
levels_proportions[k] /= sum
return levels_proportions
def freq(fruit):
'''
功能 把字符串转成列表 目的是得到每个单词的频率
@ -39,7 +66,8 @@ def file2str(fname):#文件转字符
def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。
special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|' # 把里面的字符都去掉
special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|,。?!¥……()、《》【】:;·' # 把里面的字符都去掉
s = html.unescape(s) # 将HTML实体转换为对应的字符比如<会被识别为小于号
for c in special_characters:
s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况
s = s.replace('--', ' ')
@ -104,7 +132,7 @@ if __name__ == '__main__':
print('%s\t%d\t%s' % (x[0], x[1], youdao_link(x[0])))#函数导出
# 把频率的结果放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')
if os.path.exists('frequency.p'):

View File

@ -2,10 +2,7 @@
DEPLOYMENT_DIR=/home/lanhui/englishpal2/EnglishPal
cd $DEPLOYMENT_DIR
# Install dependencies
pip3 install -r requirements.txt
pwd
# Stop service
sudo docker stop EnglishPal
@ -15,7 +12,7 @@ sudo docker rm EnglishPal
sudo docker build -t englishpal .
# Run the application
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
sudo docker run --restart=always -d --name EnglishPal -p 90:80 -v ${DEPLOYMENT_DIR}/app/static/frequency:/app/static/frequency --mount type=volume,src=englishpal-db,target=/app/db -t englishpal # for permanently saving data
# Save space. Run it after sudo docker run
sudo docker system prune -a -f

View File

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