html小工具——文章註釋編輯器

在網上閱讀文章時,讀者時常會想針對某段文字寫一些本身的感想,惋惜大部分閱讀網站並不提供這樣的功能,讀者每每只能將文本複製到本地或在線的編輯器中編輯註釋,以後若是想在其餘地方回顧這些註釋也必須先本地安裝或聯網登陸(雲筆記)編輯器。如此操做,麻煩倒在其次,錯過靈感纔是最讓人可惜的,因而決定編寫一個簡單的小工具嘗試解決這一問題,html

1、用法:正則表達式

用Chrome瀏覽器打開此工具瀏覽器

點擊「選擇文本文件」選擇txt文件,或者直接將純文本粘貼到文本框中,點擊肯定則以綠色背景顯示出文本內容,鼠標雙擊綠色正文段落即在下面彈出黃色註釋段落,雙擊註釋段落便可編輯註釋內容(正文不可編輯),編輯時點擊其餘地方便可取消編輯,再次雙擊綠色正文段落則隱藏本段註釋。app

點擊導出html便可將正文和註釋內容保存爲一個html文檔,用Chrome瀏覽器打開此html文檔便可以查看已經錄入的正文和註釋,另外這個導出的html文檔也具備本工具的全部特性,使用者能夠繼續編輯註釋內容,或者徹底替換性的加載新的文本,編輯完畢後又能夠導出爲新的html文檔。dom

2、編寫方法:編輯器

一、首先拼一個html結構出來:ide

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>實用版,處理事件問題</title>
 6     <style>
 7         /*通用屬性*/
 8         body{    margin: 0;    padding: 0;    border: 0;    text-align: center;    overflow: hidden;width: 100%;
 9             height: 100%;position: fixed;    font-family: verdana,arial,sans-serif;    touch-action: none;
10             -ms-touch-action: none;font-size: 12px;min-width: 600px;}
11         ul {    list-style: none;    margin: 0;    padding: 0;}
12         li{    list-style: none;    margin: 0;    padding: 0;}
13         ul li {    float: left;}
14         button{    cursor: pointer;    height: 23px;}
15         a:link{    text-decoration: none;}
16 
17         #div_top span,input,button,textarea{margin-left: 20px;float: left;}
18         .p_h1{font-size: 24px;font-weight: bolder;background-color: darkseagreen}
19         .p_text{font-size: 20px;font-weight: normal;text-align: left;background-color: darkseagreen}
20 
21         .div_section{position:relative}
22         .p_a1{font-size: 20px;font-weight: normal;text-align: left;background-color: beige;margin-left: 60px;margin-right: 60px;
23         ;min-height: 80px;display: block;word-break: break-all;overflow-wrap: break-word;white-space: pre-wrap}
24         .p_a2{font-size: 20px;font-weight: normal;text-align: left;background-color: beige;margin-left:120px}
25     </style>
26 </head>
27 <body>
28     <div id="div_allbase" style="top:0px;bottom:0px;width:100%;height: 100%;position:relative;overflow-x: hidden;overflow-y: scroll
29         ">
30         <div id="div_top" style="top:0px;left:0px;width:100%;height: 30px;position:absolute;">
31             <span style="margin-left: 20px">選擇文本文件</span>
32             <input type="file" id="str_local" onchange="ShowStr()" style="margin-left: 20px">
33             <span style="margin-left: 20px">粘貼文本</span>
34             <textarea type="text" id="str_local2" style="word-break: break-all;overflow-wrap: break-word;white-space: pre-wrap;height:40px"></textarea>
35             <button onclick="ShowStr2()">肯定</button>
36             <button onclick="ExportHtml()">導出Html</button>
37         </div>
38         <div id="div_main" style="top:30px;left:0px;width:100%;position:absolute;">
39 
40         </div>
41     </div>
42 </body>

其中div_top是上面的操做區,div_main用來存放正文段落和註釋段落。這裏文本框使用textarea標籤而非input標籤,由於input標籤會自動將多行文本的換行符替換爲空格,white-space: pre-wrap樣式則負責告知textarea標籤保留換行符,不然換行符也會被替換掉。工具

二、將純文本轉換爲正文段落:網站

 1 function ShowStr()//從文件讀取文本
 2     {
 3         var str_local=document.getElementById("str_local");
 4         var file=str_local.files[0];
 5         var reader=new FileReader();
 6         reader.readAsText(file);
 7         reader.onload=function(e)
 8         {
 9             var str=e.target.result;
10             loadArticle(str);
11         }
12     }
13     function ShowStr2()//從文本框讀取文本
14     {
15         var str_local=document.getElementById("str_local2");
16         var str=str_local.value;
17         loadArticle(str);
18     }
19     var currentSectionid=null;
20     function loadArticle(str)
21     {
22         var div_main=document.getElementById("div_main");
23         div_main.innerHTML="";
24         var arr_section=str.split("\r\n");
25         var len=arr_section.length;
26         if(len==1)//若是按\r\n分段後只有一段
27         {
28             arr_section=str.split("\n");
29         }
30         len=arr_section.length;
31         var count_p=0;//包含主標題在內一共分紅幾段
32         var arr_p=[];
33         for(var i=0;i<len;i++)
34         {
35             var section=arr_section[i];
36             if(section.length>0)
37             {
38                 let div_section=document.createElement("div");
39                 div_section.className="div_section";//這樣能夠更方便的在段內插入元素
40                 div_section.id="div_section_"+count_p;
41 
42                 let p=document.createElement("p");
43                 if(count_p==0)//標題段
44                 {
45                     p.className="p_h1";
46                 }
47                 else
48                 {
49                     p.className="p_text";
50                 }
51                 p.innerHTML="    "+section;
52                 p.id="p_section_"+count_p;
53 
54                 p.ondblclick=function()
55                 {
56                     addAnnotate(div_section.id);
57                 }
58                 count_p++;
59                 div_section.appendChild(p);
60                 div_main.appendChild(div_section);
61             }
62         }
63     }

 

代碼 並不複雜,根據\r\n對文本進行分段(從文本框讀取時須要用\n分段),每一段生成對應的html文檔插入dom中,每一個「section」都有惟一的dom id。目前尚未調試好的是如何在導出導入html時保持每一段前面的四個空格,瀏覽器會自動將它們去掉。ui

注意在爲批量創建的標籤設定事件響應時,須要用let型變量而非var型變量,不然同一循環中定義的全部事件響應都將以循環操做的最後一個標籤爲目標。

三、雙擊正文段落時展開註釋段落:

 1 function addAnnotate(id)
 2     {
 3         var div_section=document.getElementById(id);
 4         currentSectionid=id;//這個好像沒用到
 5         var children=div_section.childNodes;
 6         var len=children.length;
 7         if(len==1)//此時尚未第一級註釋
 8         {
 9             let p_a1=document.createElement("p");
10             p_a1.className="p_a1";
11             //點擊第一級註釋,進行編輯
12             p_a1.ondblclick=function(){openEdit(p_a1)};
13             p_a1.onblur=function(){closeEdit(p_a1)};//失去焦點時關閉註釋段的編輯狀態
14             div_section.appendChild(p_a1)
15         }
16         else
17         {
18             if(children[1].style.display=="none")//若是當前是隱藏狀態
19             {
20                 for(var i=1;i<len;i++)
21                 {
22                     children[i].style.display="block";//顯示註釋段
23                 }
24             }
25             else
26             {
27                 for(var i=1;i<len;i++)
28                 {
29                     var child=children[i];
30                     child.style.display="none";//隱藏註釋段
31                     if(child.className=="p_a1")
32                     {
33                         closeEdit(child);
34                     }
35                 }
36             }
37         }
38     }

這裏根據sectionid找到當前操做的正文段落div,將空白的註釋段落插進去。

四、編輯註釋及關閉編輯狀態:

 1 function openEdit(p)
 2     {
 3         p.style.border="2px solid cornflowerblue";
 4         p.style.borderRadius="5px";
 5         p.contentEditable="true";
 6     }
 7     function closeEdit(p)
 8     {
 9         p.style.border="0px";
10         p.contentEditable="false";
11         //Chrome的默認編輯模式會在p內插入一個div,這個div是用來分行的,空白的換行也會致使div!!
12         // 但在從新導入以後(在取innerHTML導出時尚正常)瀏覽器會自動把這個div繪製在p的外面!!!!
13         //屢次換行是嵌套的!!!!因此簡單的替換還不行!!
14         p.innerHTML=p.innerHTML.replace(new RegExp(("<[^>]+>"),"gm"),"@");//若是有連續的多個xml標籤
15         p.innerHTML=p.innerHTML.replace(new RegExp(("[@]+"),"gm"),"\r\n");
16         //p.innerHTML=p.innerHTML.replace(new RegExp(("<[^>]+>"),"gm"),"\r\n");
17         //p.innerHTML=p.innerHTML.replace(new RegExp(("<[^/][\\S]{1,5}>"),"gm"),"\r\n");
18         //p.innerHTML=p.innerHTML.replace(new RegExp(("</[\\S]{1,6}>"),"gm"),"");
19     }

這裏使用了瀏覽器內置的「contentEditable」屬性編輯p標籤內容,這樣作的一個缺點是瀏覽器會在用戶編輯標籤時不受用戶控制的插入各類控制格式的標籤,而一樣的html再次導入瀏覽器時,又會按照不一樣的規則自動排列成別的順序。(相似瀏覽器的CSS調試,只預期在運行時一次性使用?)

爲解決這一問題,我在關閉p標籤的contentEditable狀態時,用正則表達式清除了全部的xml標籤,但這也將致使新的問題——使用者但願寫在註釋中的xml標籤(包括替換符號@)也會被清除掉,不知道有沒有更好的方法解決這一問題。

五、導出html文檔:

 1 function ExportHtml()
 2     {
 3         var str=str_head+window.document.body.outerHTML+"</html>";
 4         var blob=new Blob([str],{//字符串轉爲二進制流
 5             type: "text/plain"
 6         })
 7 
 8         var tmpa = document.createElement("a");
 9         var p_h1=document.getElementsByClassName("p_h1")[0];
10         tmpa.download = (p_h1?p_h1.innerHTML:"test")+".html";//自動用標題做爲文件名
11         tmpa.href = URL.createObjectURL(blob);//用臨時的a標籤下載
12         tmpa.click();//導出後事件須要從新綁定,或者直接使用innHTML定義?
13         setTimeout(function () {
14             URL.revokeObjectURL(blob);//釋放掉流
15         }, 100);
16     }

其中str_head包含了html頭部內容,加上目前正在編輯的body內容和html結束標籤組成導出的html文檔,而後轉換爲二進制文件流用臨時a標籤下載。

注意,使用.onxx方式綁定的事件並不會隨着html文檔一塊兒導出,能夠選擇在打開html時從新綁定事件,或者在創建標籤時使用innerHTML而非appendChild方法,直接將事件響應寫在innerHTML裏

六、從新打開導出的html文檔時從新綁定事件:

 1 window.onload=function(){
 2         //處理導出html的事件有兩種思路,一是使用innerHTML定義全部動態生成的標籤,二是在每次打開網頁時從新綁定事件
 3         var div_main=document.getElementById("div_main");
 4         var arr_section=div_main.getElementsByClassName("div_section");
 5         var len=arr_section.length;
 6         for(var i=0;i<len;i++)
 7         {
 8             let div_section=arr_section[i];
 9             var arr_p=div_section.getElementsByTagName("p");
10             var len2=arr_p.length;
11             for(var j=0;j<len2;j++)
12             {
13                 let p=arr_p[j];
14                 if(j==0)
15                 {
16                     p.ondblclick=function()
17                     {
18                         addAnnotate(div_section.id);
19                     }
20                 }
21                 else
22                 {
23                     p.ondblclick=function(){openEdit(p)};
24                     p.onblur=function(){closeEdit(p)};
25                 }
26             }
27         }
28     }

如此就完成了文章註釋編輯器小工具的編寫,下面是完整的代碼,複製粘貼到一個空白的.html文件中便可使用。

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>實用版,處理事件問題</title>
  6     <style>
  7         /*通用屬性*/
  8         body{    margin: 0;    padding: 0;    border: 0;    text-align: center;    overflow: hidden;width: 100%;
  9             height: 100%;position: fixed;    font-family: verdana,arial,sans-serif;    touch-action: none;
 10             -ms-touch-action: none;font-size: 12px;min-width: 600px;}
 11         ul {    list-style: none;    margin: 0;    padding: 0;}
 12         li{    list-style: none;    margin: 0;    padding: 0;}
 13         ul li {    float: left;}
 14         button{    cursor: pointer;    height: 23px;}
 15         a:link{    text-decoration: none;}
 16 
 17         #div_top span,input,button,textarea{margin-left: 20px;float: left;}
 18         .p_h1{font-size: 24px;font-weight: bolder;background-color: darkseagreen}
 19         .p_text{font-size: 20px;font-weight: normal;text-align: left;background-color: darkseagreen}
 20 
 21         .div_section{position:relative}
 22         .p_a1{font-size: 20px;font-weight: normal;text-align: left;background-color: beige;margin-left: 60px;margin-right: 60px;
 23         ;min-height: 80px;display: block;word-break: break-all;overflow-wrap: break-word;white-space: pre-wrap}
 24         .p_a2{font-size: 20px;font-weight: normal;text-align: left;background-color: beige;margin-left:120px}
 25     </style>
 26 </head>
 27 <body>
 28     <div id="div_allbase" style="top:0px;bottom:0px;width:100%;height: 100%;position:relative;overflow-x: hidden;overflow-y: scroll
 29         ">
 30         <div id="div_top" style="top:0px;left:0px;width:100%;height: 30px;position:absolute;">
 31             <span style="margin-left: 20px">選擇文本文件</span>
 32             <input type="file" id="str_local" onchange="ShowStr()" style="margin-left: 20px">
 33             <span style="margin-left: 20px">粘貼文本</span>
 34             <textarea type="text" id="str_local2" style="word-break: break-all;overflow-wrap: break-word;white-space: pre-wrap;height:40px"></textarea>
 35             <button onclick="ShowStr2()">肯定</button>
 36             <button onclick="ExportHtml()">導出Html</button>
 37         </div>
 38         <div id="div_main" style="top:30px;left:0px;width:100%;position:absolute;">
 39 
 40         </div>
 41     </div>
 42 </body>
 43 <script>
 44 
 45     window.onload=function(){
 46         //處理導出html的事件有兩種思路,一是使用innerHTML定義全部動態生成的標籤,二是在每次打開網頁時從新綁定事件
 47         var div_main=document.getElementById("div_main");
 48         var arr_section=div_main.getElementsByClassName("div_section");
 49         var len=arr_section.length;
 50         for(var i=0;i<len;i++)
 51         {
 52             let div_section=arr_section[i];
 53             var arr_p=div_section.getElementsByTagName("p");
 54             var len2=arr_p.length;
 55             for(var j=0;j<len2;j++)
 56             {
 57                 let p=arr_p[j];
 58                 if(j==0)
 59                 {
 60                     p.ondblclick=function()
 61                     {
 62                         addAnnotate(div_section.id);
 63                     }
 64                 }
 65                 else
 66                 {
 67                     p.ondblclick=function(){openEdit(p)};
 68                     p.onblur=function(){closeEdit(p)};
 69                 }
 70             }
 71         }
 72     }
 73     function ShowStr()
 74     {
 75         var str_local=document.getElementById("str_local");
 76         var file=str_local.files[0];
 77         var reader=new FileReader();
 78         reader.readAsText(file);
 79         reader.onload=function(e)
 80         {
 81             var str=e.target.result;
 82             loadArticle(str);
 83         }
 84     }
 85     function ShowStr2()
 86     {
 87         var str_local=document.getElementById("str_local2");
 88         var str=str_local.value;
 89         loadArticle(str);
 90     }
 91     var currentSectionid=null;
 92     function loadArticle(str)
 93     {
 94         var div_main=document.getElementById("div_main");
 95         div_main.innerHTML="";
 96         var arr_section=str.split("\r\n");
 97         var len=arr_section.length;
 98         if(len==1)
 99         {
100             arr_section=str.split("\n");
101         }
102         len=arr_section.length;
103         var count_p=0;//包含主標題在內一共分紅幾段
104         var arr_p=[];
105         for(var i=0;i<len;i++)
106         {
107             var section=arr_section[i];
108             if(section.length>0)
109             {
110                 let div_section=document.createElement("div");
111                 div_section.className="div_section";//這樣能夠更方便的在段內插入元素
112                 div_section.id="div_section_"+count_p;
113 
114                 let p=document.createElement("p");
115                 if(count_p==0)//標題段
116                 {
117                     p.className="p_h1";
118                 }
119                 else
120                 {
121                     p.className="p_text";
122                 }
123                 p.innerHTML="    "+section;
124                 p.id="p_section_"+count_p;
125 
126                 p.ondblclick=function()
127                 {
128                     addAnnotate(div_section.id);
129                 }
130                 count_p++;
131                 div_section.appendChild(p);
132                 div_main.appendChild(div_section);
133             }
134         }
135     }
136     function addAnnotate(id)
137     {
138         var div_section=document.getElementById(id);
139         currentSectionid=id;
140         var children=div_section.childNodes;
141         var len=children.length;
142         if(len==1)//此時尚未第一級註釋
143         {
144             let p_a1=document.createElement("p");
145             p_a1.className="p_a1";
146             //點擊第一級註釋,進行編輯
147             p_a1.ondblclick=function(){openEdit(p_a1)};
148             p_a1.onblur=function(){closeEdit(p_a1)};
149             div_section.appendChild(p_a1)
150         }
151         else
152         {
153             if(children[1].style.display=="none")//若是當前是隱藏狀態
154             {
155                 for(var i=1;i<len;i++)
156                 {
157                     children[i].style.display="block";
158                 }
159             }
160             else
161             {
162                 for(var i=1;i<len;i++)
163                 {
164                     var child=children[i];
165                     child.style.display="none";
166                     if(child.className=="p_a1")
167                     {
168                         closeEdit(child);
169                     }
170                 }
171             }
172         }
173     }
174     function openEdit(p)
175     {
176         p.style.border="2px solid cornflowerblue";
177         p.style.borderRadius="5px";
178         p.contentEditable="true";
179     }
180     function closeEdit(p)
181     {
182         p.style.border="0px";
183         p.contentEditable="false";
184         //Chrome的默認編輯模式會在p內插入一個div,這個div是用來分行的,空白的換行也會致使div!!
185         // 但在從新導入以後(在取innerHTML導出時尚正常)瀏覽器會自動把這個div繪製在p的外面!!!!
186         //屢次換行是嵌套的!!!!因此簡單的替換還不行!!
187         p.innerHTML=p.innerHTML.replace(new RegExp(("<[^>]+>"),"gm"),"@");
188         p.innerHTML=p.innerHTML.replace(new RegExp(("[@]+"),"gm"),"\r\n");
189         //p.innerHTML=p.innerHTML.replace(new RegExp(("<[^>]+>"),"gm"),"\r\n");
190         //p.innerHTML=p.innerHTML.replace(new RegExp(("<[^/][\\S]{1,5}>"),"gm"),"\r\n");
191         //p.innerHTML=p.innerHTML.replace(new RegExp(("</[\\S]{1,6}>"),"gm"),"");
192     }
193 
194     function ExportHtml()
195     {
196         var str=str_head+window.document.body.outerHTML+"</html>";
197         var blob=new Blob([str],{
198             type: "text/plain"
199         })
200 
201         var tmpa = document.createElement("a");
202         var p_h1=document.getElementsByClassName("p_h1")[0];
203         tmpa.download = (p_h1?p_h1.innerHTML:"test")+".html";
204         tmpa.href = URL.createObjectURL(blob);
205         tmpa.click();//導出後事件須要從新綁定,或者直接使用innHTML定義?
206         setTimeout(function () {
207             URL.revokeObjectURL(blob);
208         }, 100);
209     }
210 
211     var str_head="<!DOCTYPE html>\n" +
212         "<html lang=\"en\">\n" +
213         "<head>\n" +
214         "    <meta charset=\"UTF-8\">\n" +
215         "    <title>實用版</title>\n" +
216         "    <style>\n" +
217         "        /*通用屬性*/\n" +
218         "        body{    margin: 0;    padding: 0;    border: 0;    text-align: center;    overflow: hidden;width: 100%;\n" +
219         "            height: 100%;position: fixed;    font-family: verdana,arial,sans-serif;    touch-action: none;\n" +
220         "            -ms-touch-action: none;font-size: 12px;min-width: 600px;}\n" +
221         "        ul {    list-style: none;    margin: 0;    padding: 0;}\n" +
222         "        li{    list-style: none;    margin: 0;    padding: 0;}\n" +
223         "        ul li {    float: left;}\n" +
224         "        button{    cursor: pointer;    height: 23px;}\n" +
225         "        a:link{    text-decoration: none;}\n" +
226         "        \n" +
227         "        #div_top span,input,button,textarea{margin-left: 20px;float: left;}\n" +
228         "        .p_h1{font-size: 24px;font-weight: bolder;background-color: darkseagreen}\n" +
229         "        .p_text{font-size: 20px;font-weight: normal;text-align: left;background-color: darkseagreen}\n" +
230         "\n" +
231         "        .div_section{position:relative}\n" +
232         "        .p_a1{font-size: 20px;font-weight: normal;text-align: left;background-color: beige;margin-left: 60px;margin-right: 60px;\n" +
233         "        ;min-height: 80px;display: block;word-break: break-all;overflow-wrap: break-word;white-space: pre-wrap}\n" +
234         "        .p_a2{font-size: 20px;font-weight: normal;text-align: left;background-color: beige;left:120px}\n" +
235         "    </style>\n" +
236         "</head>";
237 </script>
238 </html>
View Code

這個小工具代碼很少,功能也不復雜,但我認爲比較獨特的一點是它像單細胞生物同樣在一個文件中實現了信息的「保持」、「顯示」、「修改」功能,應該具有必定的可發展性,也歡迎你們在此基礎上開源更多功能。

 

用做例子的文章是康德的《純粹理性批判》(民國藍公武譯本)(http://book.sbkk8.com/waiguo/chuncuilixingpipan/

感受這本書被崇拜者們過譽了,如今更適合做爲歷史和參考。

相關文章
相關標籤/搜索