forked from mrlan/EnglishPal
				
			Merge branch 'Bug533-Yuyikai' of http://118.25.96.118:3000/mrlan/EnglishPal into Bug533-Yuyikai
						commit
						4d9cc1fe08
					
				|  | @ -150,6 +150,8 @@ Run English Pal first, then run the test using pytest as follows: pytest --html= | ||||||
| 
 | 
 | ||||||
| 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. | 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 | ## TODO | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| css: | css: | ||||||
|   item: |   item: | ||||||
|     - ../static/css/bootstrap.css |     - ../static/css/bootstrap.css | ||||||
| 
 |     - ../static/css/highlighted.css | ||||||
| # 全局引入的js文件地址 | # 全局引入的js文件地址 | ||||||
| js: | js: | ||||||
|   head: # 在页面加载之前加载 |   head: # 在页面加载之前加载 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | 
 | ||||||
|  | .highlighted { | ||||||
|  |     color: red; | ||||||
|  |     font-weight: normal; | ||||||
|  | } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| let isRead = true; | let isRead = localStorage.getItem('readChecked') !== 'false';  // default to true
 | ||||||
| let isChoose = true; | let isChoose = localStorage.getItem('chooseChecked') !== 'false'; | ||||||
| 
 | 
 | ||||||
| function getWord() { | function getWord() { | ||||||
|     return window.getSelection ? window.getSelection() : document.selection.createRange().text; |     return window.getSelection ? window.getSelection() : document.selection.createRange().text; | ||||||
|  | @ -10,7 +10,9 @@ function fillInWord() { | ||||||
|     if (isRead) Reader.read(word, inputSlider.value); |     if (isRead) Reader.read(word, inputSlider.value); | ||||||
|     if (!isChoose) return; |     if (!isChoose) return; | ||||||
|     const element = document.getElementById("selected-words"); |     const element = document.getElementById("selected-words"); | ||||||
|  |     localStorage.setItem('nowWords', element.value); | ||||||
|     element.value = element.value + " " + word; |     element.value = element.value + " " + word; | ||||||
|  |     localStorage.setItem('selectedWords', element.value); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| document.getElementById("text-content").addEventListener("click", fillInWord, false); | document.getElementById("text-content").addEventListener("click", fillInWord, false); | ||||||
|  | @ -24,13 +26,16 @@ inputSlider.oninput = () => { | ||||||
| 
 | 
 | ||||||
| function onReadClick() { | function onReadClick() { | ||||||
|     isRead = !isRead; |     isRead = !isRead; | ||||||
|  |     localStorage.setItem('readChecked', isRead); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function onChooseClick() { | function onChooseClick() { | ||||||
|     isChoose = !isChoose; |     isChoose = !isChoose; | ||||||
|  |     localStorage.setItem('chooseChecked', isChoose); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 如果网页刷新,停止播放声音
 | // 如果网页刷新,停止播放声音
 | ||||||
| if (performance.getEntriesByType("navigation")[0].type == "reload") { | if (performance.getEntriesByType("navigation")[0].type == "reload") { | ||||||
|     Reader.stopRead(); |     Reader.stopRead(); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| let isHighlight = true; | let isHighlight = localStorage.getItem('highlightChecked') !== 'false'; // default to true
 | ||||||
| 
 | 
 | ||||||
| function cancelBtnHandler() { | function cancelBtnHandler() { | ||||||
|     cancelHighlighting(); |     cancelHighlighting(); | ||||||
|  | @ -22,38 +22,46 @@ function getWord() { | ||||||
| 
 | 
 | ||||||
| function highLight() { | function highLight() { | ||||||
|     if (!isHighlight) return; |     if (!isHighlight) return; | ||||||
|  |     let word = (getWord() + "").trim(); | ||||||
|     let articleContent = document.getElementById("article").innerHTML; // innerHTML保留HTML标签来保持部分格式,且适配不同的浏览器
 |     let articleContent = document.getElementById("article").innerHTML; // innerHTML保留HTML标签来保持部分格式,且适配不同的浏览器
 | ||||||
|     let pickedWords = document.getElementById("selected-words");  // words picked to the text area
 |     let pickedWords = document.getElementById("selected-words");  // words picked to the text area
 | ||||||
|     let dictionaryWords = document.getElementById("selected-words2"); // words appearing in the user's new words list
 |     let dictionaryWords = document.getElementById("selected-words2"); // words appearing in the user's new words list
 | ||||||
|     let allWords = dictionaryWords === null ? pickedWords.value + " " : pickedWords.value + " " + dictionaryWords.value; |     let allWords = dictionaryWords === null ? pickedWords.value + " " : pickedWords.value + " " + dictionaryWords.value; | ||||||
|     const list = allWords.split(" "); // 将所有的生词放入一个list中
 |     const list = allWords.split(" "); // 将所有的生词放入一个list中
 | ||||||
|  |     if(word !== null && word !== "" && word !== " "){ | ||||||
|  |         let articleContent_fb2 = articleContent; | ||||||
|  |         if(localStorage.getItem("nowWords").indexOf(word) !== -1 || localStorage.getItem("nowWords").indexOf(word.toLowerCase()) !== -1){ | ||||||
|  |             articleContent = articleContent.replace(new RegExp('<span class="highlighted">' + word + '</span>', "gi"), word); | ||||||
|  |             pickedWords.value = localStorage.getItem("nowWords").replace(word,""); | ||||||
|  |             document.getElementById("article").innerHTML = articleContent; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     let totalSet = new Set(); |     let totalSet = new Set(); | ||||||
|     for (let i = 0; i < list.length; ++i) { |     for (let i = 0; i < list.length; ++i) { | ||||||
|         list[i] = list[i].replace(/(^\W*)|(\W*$)/g, ""); // 消除单词两边的非单词字符
 |         list[i] = list[i].replace(/(^\W*)|(\W*$)/g, ""); // 消除单词两边的非单词字符
 | ||||||
|  |         list[i] = list[i].replace('|', ""); | ||||||
|  |         list[i] = list[i].replace('?', ""); | ||||||
|         if (list[i] != "" && !totalSet.has(list[i])) { |         if (list[i] != "" && !totalSet.has(list[i])) { | ||||||
|             // 返回所有匹配单词的集合, 正则表达式RegExp()中, "\b"匹配一个单词的边界, g 表示全局匹配, i 表示对大小写不敏感。
 |             // 返回所有匹配单词的集合, 正则表达式RegExp()中, "\b"匹配一个单词的边界, g 表示全局匹配, i 表示对大小写不敏感。
 | ||||||
|             let matches = new Set(articleContent.match(new RegExp("\\b" + list[i] + "\\b", "gi"))); |             let matches = new Set(articleContent.match(new RegExp("\\b" + list[i] + "\\b", "gi"))); | ||||||
|             if (matches.has("mark")) { |  | ||||||
|                 // 优先处理单词为 "mark" 的情况
 |  | ||||||
|                 totalSet = new Set(["mark", ...totalSet]); |  | ||||||
|             } |  | ||||||
|             totalSet = new Set([...totalSet, ...matches]); |             totalSet = new Set([...totalSet, ...matches]); | ||||||
|         } |         } | ||||||
|     }  |     } | ||||||
|     // 删除所有的mark标签,防止标签发生嵌套
 |     // 删除所有的"<span class='highlighted'>"标签,防止标签发生嵌套
 | ||||||
|     articleContent = articleContent.replace(/<(mark)[^>]*>/gi, ""); |     articleContent = articleContent.replace(new RegExp('<span class="highlighted">',"gi"), "") | ||||||
|     articleContent = articleContent.replace(/<(\/mark)[^>]*>/gi, ""); |     articleContent = articleContent.replace(new RegExp("</span>","gi"), ""); | ||||||
|     // 将文章中所有出现该单词word的地方改为:"<mark>" + word + "<mark>"。
 |     // 将文章中所有出现该单词word的地方改为:"<span class='highlighted'>" + word + "</span>"。
 | ||||||
|     for (let word of totalSet) { |     for (let word of totalSet) { | ||||||
|         articleContent = articleContent.replace(new RegExp("\\b" + word + "\\b", "g"), "<mark>" + word + "</mark>"); |         articleContent = articleContent.replace(new RegExp("\\b" + word + "\\b", "g"), "<span class='highlighted'>" + word + "</span>"); | ||||||
|     } |     } | ||||||
|     document.getElementById("article").innerHTML = articleContent; |     document.getElementById("article").innerHTML = articleContent; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function cancelHighlighting() { | function cancelHighlighting() { | ||||||
|     let articleContent = document.getElementById("article").innerHTML; |     let articleContent = document.getElementById("article").innerHTML; | ||||||
|     articleContent = articleContent.replace(/<(mark)[^>]*>/gi, ""); |     articleContent = articleContent.replace(new RegExp('<span class="highlighted">',"gi"), "") | ||||||
|     articleContent = articleContent.replace(/<(\/mark)[^>]*>/gi, ""); |     articleContent = articleContent.replace(new RegExp("</span>","gi"), ""); | ||||||
|     document.getElementById("article").innerHTML = articleContent; |     document.getElementById("article").innerHTML = articleContent; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -73,6 +81,7 @@ function toggleHighlighting() { | ||||||
|         isHighlight = true; |         isHighlight = true; | ||||||
|         highLight(); |         highLight(); | ||||||
|     } |     } | ||||||
|  |      localStorage.setItem('highlightChecked', isHighlight); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| showBtnHandler(); | showBtnHandler(); | ||||||
|  | @ -15,6 +15,10 @@ | ||||||
|             alert('输入不能为空!'); |             alert('输入不能为空!'); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |         if (password.includes(' ')) { | ||||||
|  |             alert('输入不能包含空格!'); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|         $.post( |         $.post( | ||||||
|             "/login", {'username': username, 'password': password}, |             "/login", {'username': username, 'password': password}, | ||||||
|             function (response) { |             function (response) { | ||||||
|  |  | ||||||
|  | @ -34,9 +34,9 @@ | ||||||
|         <div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span> 篇     其中Oxford覆盖率为: <span class="badge bg-success"> {{ coverage_percentage }}% </span></div> |         <div class="alert alert-success" role="alert">共有文章 <span class="badge bg-success"> {{ number_of_essays }} </span> 篇     其中Oxford覆盖率为: <span class="badge bg-success"> {{ coverage_percentage }}% </span></div> | ||||||
|         <p>粘贴1篇文章 (<E></E>nglish only)</p> |         <p>粘贴1篇文章 (<E></E>nglish only)</p> | ||||||
|         <form method="post" action="/"> |         <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="submit" value="get文章中的词频"/> | ||||||
|             <input type="reset" value="清除"/> |             <input type="reset" value="清除" onclick="clearArticle()"/> | ||||||
|         </form> |         </form> | ||||||
|         {% if d_len > 0 %} |         {% if d_len > 0 %} | ||||||
|             <p><b>最常见的词</b></p> |             <p><b>最常见的词</b></p> | ||||||
|  | @ -52,5 +52,22 @@ | ||||||
|             <script src="{{ js }}" ></script> |             <script src="{{ js }}" ></script> | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|     {% endif %} |     {% 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> | </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <title>Title</title> |     <title>单词词频</title> | ||||||
| 
 | 
 | ||||||
|     {{ yml['header'] | safe }} |     {{ yml['header'] | safe }} | ||||||
|     {% if yml['css']['item'] %} |     {% if yml['css']['item'] %} | ||||||
|  |  | ||||||
|  | @ -12,6 +12,10 @@ | ||||||
|                 alert('输入不能为空!'); |                 alert('输入不能为空!'); | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|  |             if (old_password.includes(' ') || new_password.includes(' ')) { | ||||||
|  |                 alert('输入不能包含空格!'); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|             if (new_password !== re_new_password) { |             if (new_password !== re_new_password) { | ||||||
|                 alert('新密码不匹配,请重新输入'); |                 alert('新密码不匹配,请重新输入'); | ||||||
|                 return false; |                 return false; | ||||||
|  |  | ||||||
|  | @ -16,6 +16,10 @@ You're logged in already! <a href="/logout">Logout</a>. | ||||||
|                 alert('输入不能为空!'); |                 alert('输入不能为空!'); | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|  |             if (password.includes(' ') || password2.includes(' ')) { | ||||||
|  |                 alert('输入不能包含空格!'); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|             if (password !== password2) { |             if (password !== password2) { | ||||||
|                 alert('确认密码与输入密码不一致!'); |                 alert('确认密码与输入密码不一致!'); | ||||||
|                 return false; |                 return false; | ||||||
|  |  | ||||||
|  | @ -86,7 +86,7 @@ | ||||||
|             <div> |             <div> | ||||||
|                 <p><small class="text-muted" id="source">{{ today_article['source'] }}</small></p><br/> |                 <p><small class="text-muted" id="source">{{ today_article['source'] }}</small></p><br/> | ||||||
|             </div> |             </div> | ||||||
|              | 
 | ||||||
|             <p><b id="question">{{ today_article['question'] }}</b></p><br/> |             <p><b id="question">{{ today_article['question'] }}</b></p><br/> | ||||||
|                 <script type="text/javascript"> |                 <script type="text/javascript"> | ||||||
|                     function toggle_visibility(id) { {# https://css-tricks.com/snippets/javascript/showhide-element/#} |                     function toggle_visibility(id) { {# https://css-tricks.com/snippets/javascript/showhide-element/#} | ||||||
|  | @ -109,22 +109,22 @@ | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <input type="checkbox" onclick="toggleHighlighting()" checked/>生词高亮 |     <input type="checkbox" id="highlightCheckbox" onclick="toggleHighlighting()" />生词高亮 | ||||||
|     <input type="checkbox" onclick="onReadClick()" checked/>大声朗读 |     <input type="checkbox" id="readCheckbox" onclick="onReadClick()" />大声朗读 | ||||||
|     <input type="checkbox" onclick="onChooseClick()" checked/>划词入库 |     <input type="checkbox" id="chooseCheckbox" onclick="onChooseClick()" />划词入库 | ||||||
|     <div class="range"> |     <div class="range"> | ||||||
|         <div class="field"> |         <div class="field"> | ||||||
|             <div class="sliderValue"> |             <div class="sliderValue"> | ||||||
|                 <span id="rangeValue">1×</span> |                 <span id="rangeValue">1×</span> | ||||||
|             </div> |             </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> | ||||||
|     </div> |     </div> | ||||||
|     <p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p> |     <p><b>收集生词吧</b> (可以在正文中划词,也可以复制黏贴)</p> | ||||||
|     <form method="post" action="/{{ username }}/userpage"> |     <form method="post" action="/{{ username }}/userpage"> | ||||||
|         <textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/> |         <textarea name="content" id="selected-words" rows="10" cols="120"></textarea><br/> | ||||||
|         <button class="btn btn-primary btn-lg" type="submit" onclick="Reader.stopRead()">把生词加入我的生词库</button> |         <button class="btn btn-primary btn-lg" type="submit" onclick="Reader.stopRead()">把生词加入我的生词库</button> | ||||||
|         <button class="btn btn-primary btn-lg" type="reset">清除</button> |         <button class="btn btn-primary btn-lg" type="reset"  onclick="clearSelectedWords()">清除</button> | ||||||
|     </form> |     </form> | ||||||
|     {% if session.get['thisWord'] %} |     {% if session.get['thisWord'] %} | ||||||
|         <script type="text/javascript"> |         <script type="text/javascript"> | ||||||
|  | @ -139,7 +139,7 @@ | ||||||
| 
 | 
 | ||||||
|     {% if d_len > 0 %} |     {% if d_len > 0 %} | ||||||
|         <p> |         <p> | ||||||
|             <b>我的生词簿</b>  |             <b>我的生词簿</b> | ||||||
|             <label for="move_dynamiclly"> |             <label for="move_dynamiclly"> | ||||||
|                 <input type="checkbox" name="move_dynamiclly" id="move_dynamiclly" checked> |                 <input type="checkbox" name="move_dynamiclly" id="move_dynamiclly" checked> | ||||||
|                 允许动态调整顺序 |                 允许动态调整顺序 | ||||||
|  | @ -174,11 +174,54 @@ | ||||||
| {% endif %} | {% endif %} | ||||||
| <script type="text/javascript"> | <script type="text/javascript"> | ||||||
|     window.onload = function () { // 页面加载时执行 |     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')){ |         if (sessionStorage.getItem('pre_page_button') !== 'display' && sessionStorage.getItem('pre_page_button')) { | ||||||
|             $('#load_pre_article').show(); |             $('#load_pre_article').show(); | ||||||
|         } |         } | ||||||
|      }; | 
 | ||||||
|  |         // 事件监听器 | ||||||
|  |         elements.selectedWordsInput.addEventListener('input', () => { | ||||||
|  |             localStorage.setItem('selectedWords', elements.selectedWordsInput.value); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         elements.rangeComponent.addEventListener('input', () => { | ||||||
|  |             const rangeValue = elements.rangeComponent.value; | ||||||
|  |             elements.rangeValueDisplay.textContent = `${rangeValue}x`; | ||||||
|  |             localStorage.setItem('rangeValue', rangeValue); | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     function clearSelectedWords() { | ||||||
|  |         localStorage.removeItem('selectedWords'); | ||||||
|  |         document.querySelector('#selected-words').value = ''; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     function load_next_article(){ |     function load_next_article(){ | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|             url: '/get_next_article/{{username}}', |             url: '/get_next_article/{{username}}', | ||||||
|  | @ -248,7 +291,7 @@ | ||||||
| </body> | </body> | ||||||
| <style> | <style> | ||||||
|     mark { |     mark { | ||||||
|         color: #{{ yml['highlight']['color'] }}; |         color: red; | ||||||
|         background-color: rgba(0, 0, 0, 0); |         background-color: rgba(0, 0, 0, 0); | ||||||
|     } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,50 +1,50 @@ | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
|     <head> |     <head> | ||||||
| 	<meta charset="UTF-8"> |        <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="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" /> |        <meta name="format-detection" content="telephone=no" /> | ||||||
| 
 | 
 | ||||||
| 	{{ yml['header'] | safe }} |        {{ yml['header'] | safe }} | ||||||
| 	{% if yml['css']['item'] %} |        {% if yml['css']['item'] %} | ||||||
|         {% for css in yml['css']['item'] %} |          {% for css in yml['css']['item'] %} | ||||||
|         <link href="{{ css }}" rel="stylesheet"> |          <link href="{{ css }}" rel="stylesheet"> | ||||||
|         {% endfor %} |          {% endfor %} | ||||||
| 	{% endif %} |         {% endif %} | ||||||
| 	{% if yml['js']['head'] %} |         {% if yml['js']['head'] %} | ||||||
|         {% for js in yml['js']['head'] %} |          {% for js in yml['js']['head'] %} | ||||||
|         <script src="{{ js }}" ></script> |          <script src="{{ js }}" ></script> | ||||||
|         {% endfor %} |          {% endfor %} | ||||||
| 	{% endif %} |         {% endif %} | ||||||
| 
 | 
 | ||||||
| 	<title>EnglishPal Study Room for {{username}}</title> |        <title>EnglishPal Study Room for {{username}}</title> | ||||||
|     </head> |     </head> | ||||||
|     <body> |     <body> | ||||||
| 	<div class="container-fluid"> |       <div class="container-fluid"> | ||||||
| 	    <p class="mt-md-3"> |           <p class="mt-md-3"> | ||||||
| 		<input type="button" id="btn-cancel-selection" value="取消勾选" onclick="toggleCheckboxSelection(false)" /> |                <input type="button" id="btn-cancel-selection" value="取消勾选" onclick="toggleCheckboxSelection(false)" /> | ||||||
| 		<input type="button" id="btn-selection" value="全部勾选" onclick="toggleCheckboxSelection(true)" /> |                <input type="button" id="btn-selection" value="全部勾选" onclick="toggleCheckboxSelection(true)" /> | ||||||
| 	    </p> |           </p> | ||||||
| 	    <form method="post" action="/{{username}}/mark"> |           <form method="post" action="/{{username}}/mark"> | ||||||
| 		<button type="submit" name="add-btn" class="btn btn-outline-primary btn-lg">加入我的生词簿</button> |               <button type="submit" name="add-btn" class="btn btn-outline-primary btn-lg" onclick="clearSelectedWords()">加入我的生词簿</button> | ||||||
| 		{% for x in lst %} |               {% for x in lst %} | ||||||
| 		{% set word = x[0]%} |               {% set word = x[0]%} | ||||||
| 		<p> |               <p> | ||||||
| 		    <font color="grey">{{loop.index}}</font> |                     <font color="grey">{{loop.index}}</font> | ||||||
| 		    : |                     : | ||||||
| 		    <a href='http://youdao.com/w/eng/{{word}}/#keyfrom=dict2.index' title={{word}}>{{word}}</a> |                     <a href='http://youdao.com/w/eng/{{word}}/#keyfrom=dict2.index' title={{word}}>{{word}}</a> | ||||||
| 		    ({{x[1]}}) |                     ({{x[1]}}) | ||||||
| 		    <input type="checkbox" name="marked" value="{{word}}" checked> |                     <input type="checkbox" name="marked" value="{{word}}" checked> | ||||||
| 		</p> |               </p> | ||||||
| 
 | 
 | ||||||
| 		{% endfor %} |                {% endfor %} | ||||||
| 	    </form> |            </form> | ||||||
| 	    {{ yml['footer'] | safe }} |            {{ yml['footer'] | safe }} | ||||||
| 	    {% if yml['js']['bottom'] %} |            {% if yml['js']['bottom'] %} | ||||||
|             {% for js in yml['js']['bottom'] %} |             {% for js in yml['js']['bottom'] %} | ||||||
|             <script src="{{ js }}" ></script> |              <script src="{{ js }}" ></script> | ||||||
|             {% endfor %} |             {% endfor %} | ||||||
| 	    {% endif %} |         {% endif %} | ||||||
| 	</div> |       </div> | ||||||
|     </body> |     </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import pytest | ||||||
| import sqlite3 | import sqlite3 | ||||||
| import time | import time | ||||||
| from selenium import webdriver | from selenium import webdriver | ||||||
|  | 
 | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
|  | @ -11,7 +12,7 @@ def URL(): | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def driver(): | def driver(): | ||||||
|     return webdriver.Edge()  # uncomment this line if you wish to run the test on your laptop |     return webdriver.Edge()  # follow the "End-to-end testing" section in README.md to install the web driver executable | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
|  |  | ||||||
|  | @ -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)) | ||||||
|  | @ -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() | ||||||
|  | @ -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() | ||||||
|  | @ -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() | ||||||
|  | @ -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() | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| ########################################################################### | ########################################################################### | ||||||
| 
 | 
 | ||||||
| import collections | import collections | ||||||
|  | import html | ||||||
| import string | import string | ||||||
| import operator | import operator | ||||||
| import os, sys # 引入模块sys,因为我要用里面的sys.argv列表中的信息来读取命令行参数。 | import os, sys # 引入模块sys,因为我要用里面的sys.argv列表中的信息来读取命令行参数。 | ||||||
|  | @ -39,7 +40,8 @@ def file2str(fname):#文件转字符 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。 | def remove_punctuation(s): # 这里是s是形参 (parameter)。函数被调用时才给s赋值。 | ||||||
|     special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|' # 把里面的字符都去掉 |     special_characters = '\_©~<=>+/[]*&$%^@.,?!:;#()"“”—‘’{}|,。?!¥……()、《》:;·' # 把里面的字符都去掉 | ||||||
|  |     s = html.unescape(s) # 将HTML实体转换为对应的字符,比如<会被识别为小于号 | ||||||
|     for c in special_characters: |     for c in special_characters: | ||||||
|         s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况 |         s = s.replace(c, ' ') # 防止出现把 apple,apple 移掉逗号后变成 appleapple 情况 | ||||||
|     s = s.replace('--', ' ') |     s = s.replace('--', ' ') | ||||||
|  | @ -104,7 +106,7 @@ if __name__ == '__main__': | ||||||
|         print('%s\t%d\t%s' % (x[0], x[1], youdao_link(x[0])))#函数导出 |         print('%s\t%d\t%s' % (x[0], x[1], youdao_link(x[0])))#函数导出 | ||||||
| 
 | 
 | ||||||
|     # 把频率的结果放result.html中 |     # 把频率的结果放result.html中 | ||||||
|     make_html_page(sort_in_descending_order(L), 'result.html')  |     make_html_page(sort_in_descending_order(L), 'result.html') | ||||||
| 
 | 
 | ||||||
|     print('\nHistory:\n') |     print('\nHistory:\n') | ||||||
|     if os.path.exists('frequency.p'): |     if os.path.exists('frequency.p'): | ||||||
|  |  | ||||||
|  | @ -4,3 +4,5 @@ PyYAML~=6.0 | ||||||
| pony==0.7.16 | pony==0.7.16 | ||||||
| snowballstemmer==2.2.0 | snowballstemmer==2.2.0 | ||||||
| Werkzeug==2.2.2 | Werkzeug==2.2.2 | ||||||
|  | 
 | ||||||
|  | pytest~=8.1.1 | ||||||
		Loading…
	
		Reference in New Issue