Compare commits
30 Commits
b6c825cc83
...
master
Author | SHA1 | Date | |
---|---|---|---|
d3deb4cb11 | |||
3cd86bf4c3 | |||
a04ffb3e70 | |||
e4ba4dd3f1 | |||
b99d233abc | |||
04cd5f2e7d | |||
1163b3c47e | |||
e903917179 | |||
0f2fa218f3 | |||
99db143007 | |||
c7c90101a2 | |||
|
b9ab9b6a64 | ||
|
c67adcdc99 | ||
be08136a31 | |||
9bc8dfbdfc | |||
5cbac62de3 | |||
c27f9628f4 | |||
ef0d35ccee | |||
2b54735175 | |||
9c2151f182 | |||
a507f24326 | |||
20713b8b5d | |||
72b6fb2d91 | |||
9815ddfed0 | |||
24216a4dff | |||
61401cb3c0 | |||
2dd06fcef4 | |||
32690de8a7 | |||
0ff4e96c0a | |||
5e52b71242 |
@@ -91,8 +91,8 @@ Kategorije pravopisnih pravil so:
|
||||
|
||||
Privzeto servis uporablja podčrtovanje pravopisnih napak (nastavitev `'underline'`). Videz lahko spreminjamo.
|
||||
|
||||
<img src="samples/markup_underline.png" alt="underline" width="448"/>
|
||||
<img src="samples/markup_lector.png" alt="lector" width="448"/>
|
||||
<img src="samples/images/markup_underline.png" alt="underline" width="448"/>
|
||||
<img src="samples/images/markup_lector.png" alt="lector" width="448"/>
|
||||
|
||||
Levo `'underline'`, desno `'lector'`.
|
||||
|
||||
|
@@ -15,8 +15,8 @@
|
||||
<p class="my-block">This is an example of a CKEditor edit control. Edit the text, resize the control or browser window, scroll around, click...</p>
|
||||
<div id="editor">
|
||||
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno <i>storiti</i>. Predavanje je trajalo dve ure. S njim grem v Kamnik.<br>Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik.<br/>On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Na mizo nisem položil knjigo.</p>
|
||||
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
|
||||
</div>
|
||||
|
@@ -14,8 +14,8 @@
|
||||
<p class="my-block">This is an example of a simple <code><div contenteditable="true"></code> edit control. Edit the text, resize the control or browser window, scroll around, click...</p>
|
||||
<div id="my-editor" class="my-block my-control" contenteditable="true">
|
||||
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno <i>storiti</i>. Predavanje je trajalo dve ure. S njim grem v Kamnik.<br>Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik.<br/>On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Na mizo nisem položil knjigo.</p>
|
||||
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
|
||||
</div>
|
||||
|
BIN
samples/images/markup_lector.png
Normal file
BIN
samples/images/markup_lector.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
BIN
samples/images/markup_underline.png
Normal file
BIN
samples/images/markup_underline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
@@ -75,8 +75,8 @@ Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi
|
||||
let my_ckeditor = null
|
||||
ClassicEditor.create(document.querySelector('#ckeditor-control'))
|
||||
.then(newEditor => {
|
||||
my_ckeditor = newEditor
|
||||
BesCKService.register(newEditor.ui.view.editable.element, newEditor, new BesCKStatusIconEventSink())
|
||||
my_ckeditor = newEditor.ui.view.editable.element
|
||||
BesCKService.register(my_ckeditor, newEditor, new BesCKStatusIconEventSink())
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
</script>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 174 KiB |
Binary file not shown.
Before Width: | Height: | Size: 151 KiB |
@@ -19,23 +19,10 @@
|
||||
scroll around, click...</p>
|
||||
<div id="editor">
|
||||
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim
|
||||
grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo.
|
||||
Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar
|
||||
daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v
|
||||
Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek.
|
||||
Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ
|
||||
kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem
|
||||
skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov
|
||||
življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni
|
||||
zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje
|
||||
z dvemi vesli.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno <i>storiti</i>. Predavanje je trajalo dve ure. S njim grem v Kamnik.<br>Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik.<br/>On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Na mizo nisem položil knjigo.</p>
|
||||
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd
|
||||
obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je
|
||||
odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji
|
||||
je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
|
||||
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
|
||||
</div>
|
||||
<bes-popup-el />
|
||||
<script>
|
||||
|
@@ -14,8 +14,8 @@
|
||||
<p class="my-block">This is an example of a simple <code><div contenteditable="true"></code> edit control with style grammar rules disabled. Compare the proofing results with <a href="div-contenteditable.html">the default example</a>.</p>
|
||||
<div id="my-editor" class="my-block my-control" contenteditable="true">
|
||||
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno <i>storiti</i>. Predavanje je trajalo dve ure. S njim grem v Kamnik.<br>Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik.<br/>On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Na mizo nisem položil knjigo.</p>
|
||||
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
|
||||
</div>
|
||||
|
@@ -13,8 +13,8 @@
|
||||
<p class="my-block">This is an example of grammar-checking static HTML content. The below text contains proofing markup.</p>
|
||||
<div class="my-block my-control bes-service">
|
||||
<p>Tukaj vpišite besedilo ki ga želite popraviti.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno storiti. Predavanje je trajalo dve ure. S njim grem v Kamnik. Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik. On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Prišla je njena lepa hčera. Smatram da tega nebi bilo potrebno <i>storiti</i>. Predavanje je trajalo dve ure. S njim grem v Kamnik.<br>Janez jutri nebo prišel. Prišel je z 100 idejami.</p>
|
||||
<p>To velja tudi v Bledu. To se je zgodilo na velikemu vrtu. Prišel je na Kamnik.<br/>On je včeraj prišel z svojo torbo. Dve žemlje prosim. Pogosto brskam po temu forumu. Prišel je včeraj in sicer s otroci. To ne vem. Pogleda vse kar daš v odložišče. Nisem jo videl. Ona izgleda dobro. Pri zanikanju ne smete uporabljati tožilnik. Vlak gre v Ljubljano čez Zidani Most. Skočil je čez okno. Slovenija meji na avstrijo. Jaz pišem v Slovenščini vsak Torek. Novica, da je skupina 25 planincev hodila pod vodstvom gorskega vodnika je napačna in zavajujoča. Želim da poješ kosmizailo. Jaz pogosto brskam po temu forumu. Med tem ko je iskal ključe, so se odprla vrata. V takoimenovanem skladišču je bilo veliko ljudi. V sobi sta dve mize. Stekel je h mami. Videl sem Jurčič Micko. To je bil njegov življenski cilj. Po vrsti popravite vse kar želite. Preden zaspiva mi prebere pravljico. Prišel je s stricom. Oni zadanejo tarčo. Mi gremo teči po polju. Mi gremo peči kruh. Usedel se je k miza. Postreži kosilo! Skul je veslanje z dvemi vesli.</p>
|
||||
<p>Na mizo nisem položil knjigo.</p>
|
||||
<p>Kvazimodo ji je ponavadi prinesel hrano in pijačo, medtem ko je spala, da ne bi videla njegov iznakažen in grd obraz. Poleg tega ji je pustil tudi piščalko, da bi ga lahko priklicala, če bi bilo to potrebno. Kvazimodo se je odločil, da razveseli Esmeraldo in ji obljubi, da ji bo pripeljal Febusa. Toda Febus ni želel priti. Kvazimodo ji je raje lagal, da ni mogel najti Febusa, kot da Esmeraldi pove resnico, ker bi ona trpela.</p>
|
||||
</div>
|
||||
|
@@ -1,3 +1,14 @@
|
||||
body {
|
||||
color: #000;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
color: #000;
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.my-block {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
@@ -13,7 +24,6 @@
|
||||
border-radius: 10px;
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.ck-editor__editable {
|
||||
@@ -52,3 +62,22 @@
|
||||
.bes-status-icon.bes-status-mistakes {
|
||||
background-image: url('images/mistake-svgrepo-com.svg');
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
color: #eee;
|
||||
background-color: #444;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
color: #eee;
|
||||
background-color: #222;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.my-control {
|
||||
background-color: #222;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
556
service.js
556
service.js
@@ -1,4 +1,5 @@
|
||||
// TODO: Research if there is a way to disable languageTool & Grammarly extensions in CKEditor
|
||||
// TODO: Add mutation observer should any style of hostElement/textElement change and repaint markup (e.g. notice font-weight difference when toggling light/dark color-scheme)
|
||||
|
||||
/**
|
||||
* Collection of all grammar checking services in the document
|
||||
@@ -59,10 +60,11 @@ class BesService {
|
||||
this.hostElement.setAttribute('data-gramm', 'false')
|
||||
this.hostElement.setAttribute('data-gramm_editor', 'false')
|
||||
this.hostElement.setAttribute('data-enable-grammarly', 'false')
|
||||
this.textFont = window.getComputedStyle(this.hostElement).fontFamily
|
||||
|
||||
this.onScroll = this.onScroll.bind(this)
|
||||
this.hostElement.addEventListener('scroll', this.onScroll)
|
||||
this.onShortcutNavigation = this.onShortcutNavigation.bind(this)
|
||||
this.hostElement.addEventListener('keydown', this.onShortcutNavigation)
|
||||
|
||||
this.hostBoundingClientRect = this.hostElement.getBoundingClientRect()
|
||||
this.mutationObserver = new MutationObserver(this.onBodyMutate.bind(this))
|
||||
@@ -109,6 +111,7 @@ class BesService {
|
||||
if (this.abortController) this.abortController.abort()
|
||||
besServices = besServices.filter(item => item !== this)
|
||||
this.mutationObserver.disconnect()
|
||||
this.hostElement.removeEventListener('keydown', this.onShortcutNavigation)
|
||||
this.hostElement.removeEventListener('scroll', this.onScroll)
|
||||
this.hostElement.setAttribute('spellcheck', this.originalSpellcheck)
|
||||
this.hostElement.setAttribute('data-gramm', this.originalDataGramm)
|
||||
@@ -276,6 +279,9 @@ class BesService {
|
||||
*/
|
||||
onProofingProgress(numberOfMatches) {
|
||||
this.proofingMatches += numberOfMatches
|
||||
// Sorting the array here is preferable to sorting only in onEndProofing.This way it allows users to interact
|
||||
// with and navigate newly detected mistakes as soon as they appear.
|
||||
this.sortMatchesArray()
|
||||
if (this.eventSink && 'proofingProgress' in this.eventSink)
|
||||
this.eventSink.proofingProgress(this)
|
||||
if (--this.proofingCount <= 0) this.onEndProofing()
|
||||
@@ -324,6 +330,46 @@ class BesService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to report keydown event
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
onShortcutNavigation(e) {
|
||||
switch (e.code) {
|
||||
case 'BracketLeft':
|
||||
if (e.ctrlKey) {
|
||||
// Handle Ctrl + [ OR Ctrl + Š
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
BesService.findNextMistake(this, -1)
|
||||
}
|
||||
break
|
||||
case 'BracketRight':
|
||||
if (e.ctrlKey) {
|
||||
// Handle Ctrl + ] OR Ctrl + Đ
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
BesService.findNextMistake(this, 1)
|
||||
}
|
||||
break
|
||||
case 'Enter':
|
||||
// Handle Ctrl + Enter
|
||||
if (e.ctrlKey) {
|
||||
if (!this.highlightElements.length) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.acceptReplacement()
|
||||
}
|
||||
break
|
||||
case 'Escape':
|
||||
this.dismissPopup()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to report repositioning
|
||||
*/
|
||||
@@ -362,23 +408,33 @@ class BesService {
|
||||
* @param {*} match Grammar checking rule match
|
||||
*/
|
||||
drawMistakeMarkup(match) {
|
||||
const range = match.range
|
||||
match.highlights = Array.from(range.getClientRects())
|
||||
if (match.highlights.length === 0) return
|
||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
||||
for (let rect of match.highlights) {
|
||||
rect.x -= canvasPanelRect.x
|
||||
rect.y -= canvasPanelRect.y
|
||||
}
|
||||
match.highlights = BesService.getClientRects(
|
||||
match.range,
|
||||
canvasPanelRect.x,
|
||||
canvasPanelRect.y
|
||||
)
|
||||
if (match.highlights.length === 0) return
|
||||
const dpr = window.devicePixelRatio
|
||||
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
|
||||
let amplitude = 0
|
||||
const ruleId = match.match.rule.id
|
||||
this.ctx.strokeStyle = ruleId.startsWith('MORFOLOGIK_RULE')
|
||||
? 'rgba(0, 123, 255, 0.8)'
|
||||
: 'rgba(255, 115, 0, 0.8)'
|
||||
this.ctx.fillStyle = ruleId.startsWith('MORFOLOGIK_RULE')
|
||||
? 'rgba(0, 123, 255, 0.8)'
|
||||
: 'rgba(255, 115, 0, 0.8)'
|
||||
if (ruleId.startsWith('MORFOLOGIK_RULE')) {
|
||||
const styles = window.getComputedStyle(this.highlightSpelling)
|
||||
this.ctx.strokeStyle = styles.color
|
||||
this.ctx.fillStyle = styles.color
|
||||
amplitude = -1
|
||||
} else if (ruleId === 'BESANA_178' /*PR_VNAP_POPRAVEK_UI*/) {
|
||||
const styles = window.getComputedStyle(this.highlightAI)
|
||||
this.ctx.strokeStyle = styles.color
|
||||
this.ctx.fillStyle = styles.color
|
||||
amplitude = 1
|
||||
} else {
|
||||
const styles = window.getComputedStyle(this.highlightGrammar)
|
||||
this.ctx.strokeStyle = styles.color
|
||||
this.ctx.fillStyle = styles.color
|
||||
amplitude = 1
|
||||
}
|
||||
let markerY1, markerY2
|
||||
switch (this.markupStyle) {
|
||||
case 'lector':
|
||||
@@ -486,21 +542,29 @@ class BesService {
|
||||
const scale = (markerY2 - markerY1) / 18
|
||||
|
||||
if (/^\s+$/.test(toRemove)) {
|
||||
const rect = this.makeRange(
|
||||
const rect = BesService.getClientRects(
|
||||
this.makeRange(
|
||||
match.data,
|
||||
match.match.offset,
|
||||
match.match.offset - lengthDiff
|
||||
)?.getClientRects()[0]
|
||||
),
|
||||
canvasPanelRect.x,
|
||||
canvasPanelRect.y
|
||||
)[0]
|
||||
const x = (rect.left + rect.right) / 2
|
||||
const y1 = rect.top
|
||||
const y2 = rect.bottom
|
||||
this.drawWrongSpacing(x, y1, y2, scale)
|
||||
} else {
|
||||
for (let rect of this.makeRange(
|
||||
for (let rect of BesService.getClientRects(
|
||||
this.makeRange(
|
||||
match.data,
|
||||
match.match.offset,
|
||||
match.match.offset - lengthDiff
|
||||
)?.getClientRects())
|
||||
),
|
||||
canvasPanelRect.x,
|
||||
canvasPanelRect.y
|
||||
))
|
||||
this.drawExcessiveText(
|
||||
rect.left,
|
||||
rect.bottom,
|
||||
@@ -516,21 +580,29 @@ class BesService {
|
||||
const scale = (markerY2 - markerY1) / 18
|
||||
|
||||
if (/^\s+$/.test(toRemove)) {
|
||||
const rect = this.makeRange(
|
||||
const rect = BesService.getClientRects(
|
||||
this.makeRange(
|
||||
match.data,
|
||||
match.match.offset + match.match.length + lengthDiff,
|
||||
match.match.offset + match.match.length
|
||||
)?.getClientRects()[0]
|
||||
),
|
||||
canvasPanelRect.x,
|
||||
canvasPanelRect.y
|
||||
)[0]
|
||||
const x = (rect.left + rect.right) / 2
|
||||
const y1 = rect.top
|
||||
const y2 = rect.bottom
|
||||
this.drawWrongSpacing(x, y1, y2, scale)
|
||||
} else {
|
||||
for (let rect of this.makeRange(
|
||||
for (let rect of BesService.getClientRects(
|
||||
this.makeRange(
|
||||
match.data,
|
||||
match.match.offset + match.match.length + lengthDiff,
|
||||
match.match.offset + match.match.length
|
||||
)?.getClientRects())
|
||||
),
|
||||
canvasPanelRect.x,
|
||||
canvasPanelRect.y
|
||||
))
|
||||
this.drawExcessiveText(
|
||||
rect.left,
|
||||
rect.bottom,
|
||||
@@ -574,12 +646,14 @@ class BesService {
|
||||
}
|
||||
} else {
|
||||
// Patch differences.
|
||||
const rects = Array.from(
|
||||
const rects = BesService.getClientRects(
|
||||
this.makeRange(
|
||||
match.data,
|
||||
match.match.offset + lengthL,
|
||||
match.match.offset + match.match.length - lengthR
|
||||
)?.getClientRects()
|
||||
),
|
||||
canvasPanelRect.x,
|
||||
canvasPanelRect.y
|
||||
)
|
||||
markerY1 = Math.min(...rects.map(rect => rect.top))
|
||||
markerY2 = Math.max(...rects.map(rect => rect.bottom))
|
||||
@@ -664,7 +738,9 @@ class BesService {
|
||||
const x2 = rect.right
|
||||
const y = rect.bottom
|
||||
const scale = (rect.bottom - rect.top) / 18
|
||||
this.drawAttentionRequired(x1, x2, y, scale)
|
||||
if (ruleId !== 'MORFOLOGIK_RULE') {
|
||||
this.drawDoubleUnderline(x1, x2, y, scale)
|
||||
} else this.drawAttentionRequired(x1, x2, y, amplitude, scale)
|
||||
}
|
||||
|
||||
markerY1 = Math.min(...match.highlights.map(rect => rect.top))
|
||||
@@ -706,7 +782,7 @@ class BesService {
|
||||
this.ctx.stroke()
|
||||
|
||||
if (comment) {
|
||||
this.ctx.font = `${12 * scale * dpr}px ${this.textFont}`
|
||||
this.setCtxFont(scale, dpr)
|
||||
this.ctx.textAlign = 'center'
|
||||
this.ctx.textBaseline = 'bottom'
|
||||
this.ctx.fillText('?', (x + 2 * scale) * dpr, (y - 6 * scale) * dpr)
|
||||
@@ -790,7 +866,7 @@ class BesService {
|
||||
this.ctx.lineTo(x2 * dpr, (y2 + 6 * scale) * dpr)
|
||||
this.ctx.stroke()
|
||||
|
||||
this.ctx.font = `${12 * scale * dpr}px ${this.textFont}`
|
||||
this.setCtxFont(scale, dpr)
|
||||
this.ctx.textAlign = 'left' // Thou we want the text to be centered, we align it manually to prevent it getting off canvas.
|
||||
this.ctx.textBaseline = 'bottom'
|
||||
const textMetrics = this.ctx.measureText(text)
|
||||
@@ -825,7 +901,7 @@ class BesService {
|
||||
this.ctx.lineTo(x * dpr, (y2 + 6 * scale) * dpr)
|
||||
this.ctx.stroke()
|
||||
|
||||
this.ctx.font = `${12 * scale * dpr}px ${this.textFont}`
|
||||
this.setCtxFont(scale, dpr)
|
||||
this.ctx.textAlign = 'left' // Thou we want the text to be centered, we align it manually to prevent it getting off canvas.
|
||||
this.ctx.textBaseline = 'bottom'
|
||||
const textMetrics = this.ctx.measureText(text)
|
||||
@@ -848,21 +924,69 @@ class BesService {
|
||||
* @param {Number} x1 Sign left [px]
|
||||
* @param {Number} x2 Sign right [px]
|
||||
* @param {Number} y Sign baseline [px]
|
||||
* @param {Number} amplitude Sign amplitude [px]
|
||||
* @param {Number} scale Sign scale
|
||||
*/
|
||||
drawAttentionRequired(x1, x2, y, scale) {
|
||||
drawAttentionRequired(x1, x2, y, amplitude, scale) {
|
||||
const dpr = window.devicePixelRatio
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(x1 * dpr, (y - scale) * dpr)
|
||||
this.ctx.moveTo(x1 * dpr, (y - amplitude * scale) * dpr)
|
||||
for (let x = x1; ; ) {
|
||||
if (x >= x2) break
|
||||
this.ctx.lineTo((x += 2 * scale) * dpr, (y + scale) * dpr)
|
||||
this.ctx.lineTo((x += 2 * scale) * dpr, (y + amplitude * scale) * dpr)
|
||||
if (x >= x2) break
|
||||
this.ctx.lineTo((x += 2 * scale) * dpr, (y - scale) * dpr)
|
||||
this.ctx.lineTo((x += 2 * scale) * dpr, (y - amplitude * scale) * dpr)
|
||||
}
|
||||
this.ctx.stroke()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Number} x1 Sign left [px]
|
||||
* @param {Number} x2 Sign right [px]
|
||||
* @param {Number} y Sign baseline [px]
|
||||
* @param {Number} scale Sign scale
|
||||
*/
|
||||
drawDoubleUnderline(x1, x2, y, scale) {
|
||||
const dpr = window.devicePixelRatio
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(x1 * dpr, (y - 2 * scale) * dpr)
|
||||
this.ctx.lineTo(x2 * dpr, (y - 2 * scale) * dpr)
|
||||
this.ctx.moveTo(x1 * dpr, (y + 1 * scale) * dpr)
|
||||
this.ctx.lineTo(x2 * dpr, (y + 1 * scale) * dpr)
|
||||
this.ctx.stroke()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets markup font
|
||||
*
|
||||
* @param {Number} scale Sign scale
|
||||
* @param {Number} dpr Device pixel ratio
|
||||
*/
|
||||
setCtxFont(scale, dpr) {
|
||||
const styles = window.getComputedStyle(this.canvasPanel)
|
||||
this.ctx.font = `${styles.fontStyle} ${styles.fontWeight} ${
|
||||
14 * scale * dpr
|
||||
}px ${styles.fontFamily}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates rectangles covering a given range and compensates for scroll offset
|
||||
*
|
||||
* @param {Range} range Range to get client rectangles for
|
||||
* @param {Number} offsetX X offset to subtract from coordinates [px]
|
||||
* @param {Number} offsetY Y offset to subtract from coordinates [px]
|
||||
* @returns Array of rectangles
|
||||
*/
|
||||
static getClientRects(range, offsetX, offsetY) {
|
||||
const rects = Array.from(range.getClientRects())
|
||||
for (let rect of rects) {
|
||||
rect.x -= offsetX
|
||||
rect.y -= offsetY
|
||||
}
|
||||
return rects
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates common string prefix length
|
||||
*
|
||||
@@ -898,10 +1022,21 @@ class BesService {
|
||||
* @param {Number} x X coordinate
|
||||
* @param {Number} y Y coordinate
|
||||
* @param {DOMRect} rect Rectangle
|
||||
* @param {Number} tolerance Extra margin around the rectangle treated as "inside"
|
||||
* @returns
|
||||
*/
|
||||
static isPointInRect(x, y, rect) {
|
||||
return rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom
|
||||
static isPointInRect(x, y, rect, tolerance) {
|
||||
return (
|
||||
rect.left - tolerance <= x &&
|
||||
x < rect.right + tolerance &&
|
||||
rect.top - tolerance <= y &&
|
||||
y < rect.bottom + tolerance
|
||||
)
|
||||
}
|
||||
|
||||
static arrowBtnNavigation(value, service) {
|
||||
const direction = value === 'forward' ? 1 : value === 'back' ? -1 : 0
|
||||
BesService.findNextMistake(service, direction)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -919,7 +1054,20 @@ class BesService {
|
||||
this.ctx = this.canvasPanel.getContext('2d')
|
||||
this.ctx.scale(1, 1)
|
||||
|
||||
this.highlightSpelling = document.createElement('div')
|
||||
this.highlightSpelling.classList.add('bes-highlight-placeholder')
|
||||
this.highlightSpelling.classList.add('bes-highlight-spelling')
|
||||
this.highlightAI = document.createElement('div')
|
||||
this.highlightAI.classList.add('bes-highlight-placeholder')
|
||||
this.highlightAI.classList.add('bes-highlight-ai')
|
||||
this.highlightGrammar = document.createElement('div')
|
||||
this.highlightGrammar.classList.add('bes-highlight-placeholder')
|
||||
this.highlightGrammar.classList.add('bes-highlight-grammar')
|
||||
|
||||
this.correctionPanel.appendChild(this.scrollPanel)
|
||||
this.correctionPanel.appendChild(this.highlightSpelling)
|
||||
this.correctionPanel.appendChild(this.highlightAI)
|
||||
this.correctionPanel.appendChild(this.highlightGrammar)
|
||||
this.scrollPanel.appendChild(this.canvasPanel)
|
||||
this.textElement.parentElement.insertBefore(
|
||||
this.correctionPanel,
|
||||
@@ -942,23 +1090,6 @@ class BesService {
|
||||
this.disableMutationObserver()
|
||||
|
||||
const styles = window.getComputedStyle(this.hostElement)
|
||||
this.textFont = styles.fontFamily
|
||||
|
||||
// Resize canvas if needed.
|
||||
this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px`
|
||||
this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px`
|
||||
const dpr = window.devicePixelRatio
|
||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
||||
const newCanvasWidth = Math.round(canvasPanelRect.width * dpr)
|
||||
const newCanvasHeight = Math.round(canvasPanelRect.height * dpr)
|
||||
if (
|
||||
this.canvasPanel.width !== newCanvasWidth ||
|
||||
this.canvasPanel.height !== newCanvasHeight
|
||||
) {
|
||||
this.canvasPanel.width = newCanvasWidth
|
||||
this.canvasPanel.height = newCanvasHeight
|
||||
this.redrawAllMistakeMarkup()
|
||||
}
|
||||
|
||||
// Note: Firefox is not happy when syncing all margins at once.
|
||||
this.scrollPanel.style.marginLeft = styles.marginLeft
|
||||
@@ -982,24 +1113,46 @@ class BesService {
|
||||
this.scrollPanel.style.height = `${hostRect.height}px`
|
||||
}
|
||||
|
||||
// Resize canvas if needed.
|
||||
this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px`
|
||||
this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px`
|
||||
const dpr = window.devicePixelRatio
|
||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
||||
const newCanvasWidth = Math.round(canvasPanelRect.width * dpr)
|
||||
const newCanvasHeight = Math.round(canvasPanelRect.height * dpr)
|
||||
if (
|
||||
this.canvasPanel.width !== newCanvasWidth ||
|
||||
this.canvasPanel.height !== newCanvasHeight
|
||||
) {
|
||||
this.canvasPanel.width = newCanvasWidth
|
||||
this.canvasPanel.height = newCanvasHeight
|
||||
this.redrawAllMistakeMarkup()
|
||||
}
|
||||
|
||||
this.enableMutationObserver()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and displays popup.
|
||||
*
|
||||
* @param {*} elMatch Array containing block element/paragraph containing grammar checking rule match and a match
|
||||
* @param {*} hits Array containing block element/paragraph containing grammar checking rule match and a match
|
||||
* @param {PointerEvent} source Click event source
|
||||
*/
|
||||
preparePopup(elMatch, source) {
|
||||
preparePopup(hits, source) {
|
||||
this.dismissPopup()
|
||||
const popup = document.querySelector('bes-popup-el')
|
||||
BesPopup.clearReplacements()
|
||||
elMatch.forEach(({ el, match }) => {
|
||||
hits.forEach(({ el, match }) => {
|
||||
popup.setContent(el, match, this, this.isContentEditable())
|
||||
this.highlightMistake(match)
|
||||
const containerRect = this.hostElement.getBoundingClientRect()
|
||||
match.highlights.forEach(rect => {
|
||||
const clientX = rect.x + containerRect.left
|
||||
const clientY =
|
||||
rect.y + containerRect.top + rect.height - this.hostElement.scrollTop
|
||||
popup.show(clientX, clientY, this)
|
||||
})
|
||||
})
|
||||
popup.show(source.clientX, source.clientY)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1014,27 +1167,123 @@ class BesService {
|
||||
el.classList.add('bes-highlight-rect')
|
||||
el.classList.add(
|
||||
match.match.rule.id.startsWith('MORFOLOGIK_RULE')
|
||||
? 'bes-highlight-spelling-rect'
|
||||
: 'bes-highlight-grammar-rect'
|
||||
? 'bes-highlight-spelling'
|
||||
: match.match.rule.id === 'BESANA_178' /*PR_VNAP_POPRAVEK_UI*/
|
||||
? 'bes-highlight-ai'
|
||||
: 'bes-highlight-grammar'
|
||||
)
|
||||
el.style.left = `${rect.x + canvasPanelRect.x + window.scrollX}px`
|
||||
el.style.top = `${rect.y + canvasPanelRect.y + window.scrollY}px`
|
||||
el.style.width = `${rect.width}px`
|
||||
el.style.height = `${rect.height}px`
|
||||
document.body.appendChild(el)
|
||||
this.highlightElements.push(el)
|
||||
const matchSorted =
|
||||
this.sortedMatches.find(entry => entry.match === match) || null
|
||||
this.highlightElements.push({ el, matchSorted })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This function finds the next/previous mistake.
|
||||
* @param {Number} direction Navigation direction: 1 for next, -1 for previous
|
||||
* @returns
|
||||
*/
|
||||
static findNextMistake(service, direction = 1) {
|
||||
if (!service || !service.sortedMatches || !service.sortedMatches.length)
|
||||
return
|
||||
const active = service.highlightElements.find(
|
||||
({ matchSorted }) => matchSorted
|
||||
)
|
||||
let current = -1
|
||||
if (active && active.matchSorted) {
|
||||
current = service.sortedMatches.findIndex(
|
||||
entry => entry.match === active.matchSorted.match
|
||||
)
|
||||
}
|
||||
|
||||
const len = service.sortedMatches.length
|
||||
const next = (current + direction + len) % len
|
||||
service.activeMatchIndex = next
|
||||
const { el, match } = service.sortedMatches[next]
|
||||
|
||||
if (el && typeof el.scrollIntoView === 'function') {
|
||||
el.scrollIntoView({ behavior: 'instant', block: 'center' })
|
||||
}
|
||||
// Not the cleanest solution to setTimeout()
|
||||
setTimeout(() => {
|
||||
service.dismissPopup()
|
||||
const popup = document.querySelector('bes-popup-el')
|
||||
BesPopup.clearReplacements()
|
||||
popup.setContent(el, match, service, service.isContentEditable())
|
||||
service.highlightMistake(match)
|
||||
const containerRect = service.hostElement.getBoundingClientRect()
|
||||
match.highlights.forEach(rect => {
|
||||
const clientX = rect.x + containerRect.left
|
||||
const clientY =
|
||||
rect.y +
|
||||
containerRect.top +
|
||||
rect.height -
|
||||
service.hostElement.scrollTop
|
||||
popup.show(clientX, clientY, service)
|
||||
})
|
||||
}, 150)
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the replacement for the current grammar mistake.
|
||||
*/
|
||||
acceptReplacement() {
|
||||
const popup = document.querySelector('bes-popup-el')
|
||||
const replacementDiv = popup.shadowRoot.querySelector(
|
||||
'.bes-replacement-div'
|
||||
)
|
||||
const firstReplacement = replacementDiv?.firstChild
|
||||
if (replacementDiv.childElementCount === 1) {
|
||||
// Is this an ugly solution?
|
||||
firstReplacement.click()
|
||||
} else if (replacementDiv.childElementCount > 1) {
|
||||
firstReplacement.focus()
|
||||
} else BesService.findNextMistake(this, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears highlight and hides popup
|
||||
*/
|
||||
dismissPopup() {
|
||||
BesPopup.hide()
|
||||
this.highlightElements.forEach(el => el.remove())
|
||||
this.highlightElements.forEach(obj => {
|
||||
if (obj.el && typeof obj.el.remove === 'function') {
|
||||
obj.el.remove()
|
||||
}
|
||||
})
|
||||
this.highlightElements = []
|
||||
}
|
||||
|
||||
/**
|
||||
* This function collects all matches from the results array, flattens them into a single array,
|
||||
* and sorts them in order: first by their Y axis, then by X axis.
|
||||
*/
|
||||
sortMatchesArray() {
|
||||
this.sortedMatches = []
|
||||
this.results.forEach(element => {
|
||||
element.matches.forEach(match => {
|
||||
if (!match.highlights || !match.highlights.length) return
|
||||
this.sortedMatches.push({
|
||||
el: element.element,
|
||||
match,
|
||||
top: match.highlights[0].top
|
||||
})
|
||||
})
|
||||
})
|
||||
this.sortedMatches.sort((a, b) => {
|
||||
if (a.top !== b.top) return a.top - b.top
|
||||
|
||||
const aLeft = a.match.highlights[0].left
|
||||
const bLeft = b.match.highlights[0].left
|
||||
return aLeft - bLeft
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if host element content is editable.
|
||||
*
|
||||
@@ -1055,7 +1304,9 @@ class BesService {
|
||||
redrawAllMistakeMarkup() {
|
||||
this.ctx.clearRect(0, 0, this.canvasPanel.width, this.canvasPanel.height)
|
||||
this.results.forEach(result => {
|
||||
result.matches.forEach(match => this.drawMistakeMarkup(match))
|
||||
// Most important matches are first, we want to draw them last => iterate in reverse.
|
||||
for (let i = result.matches.length; i-- > 0; )
|
||||
this.drawMistakeMarkup(result.matches[i])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1211,9 +1462,11 @@ class BesTreeService extends BesService {
|
||||
),
|
||||
match: match
|
||||
}
|
||||
this.drawMistakeMarkup(m)
|
||||
matches.push(m)
|
||||
})
|
||||
// Most important matches are first, we want to draw them last => iterate in reverse.
|
||||
for (let i = matches.length; i-- > 0; )
|
||||
this.drawMistakeMarkup(matches[i])
|
||||
this.markProofed(node, matches)
|
||||
this.onProofingProgress(matches.length)
|
||||
})
|
||||
@@ -1221,8 +1474,22 @@ class BesTreeService extends BesService {
|
||||
}
|
||||
return [{ text: `<${node.tagName}/>`, node: node, markup: true }]
|
||||
} else {
|
||||
// Inline elements require no markup. Keep plain text only.
|
||||
let data = []
|
||||
if (this.doesElementAddSpace(node)) {
|
||||
// Inline element adds some space between text. Convert node to spaces.
|
||||
const inner = node.innerHTML
|
||||
const len =
|
||||
inner.length > 0
|
||||
? node.outerHTML.indexOf(inner)
|
||||
: node.outerHTML.length
|
||||
data = data.concat({
|
||||
text: ' '.repeat(len),
|
||||
node: node,
|
||||
markup: false
|
||||
})
|
||||
} else {
|
||||
// Inline elements require no markup. Keep plain text only.
|
||||
}
|
||||
for (const el2 of node.childNodes)
|
||||
data = data.concat(this.proofNode(el2, abortController))
|
||||
return data
|
||||
@@ -1334,6 +1601,26 @@ class BesTreeService extends BesService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if given element adds space before child/next text
|
||||
*
|
||||
* @param {Element} el DOM element
|
||||
* @returns true if adds space; false otherwise.
|
||||
*/
|
||||
doesElementAddSpace(el) {
|
||||
const prevNode = el.previousSibling
|
||||
const nextNode = el.firstChild || el.nextSibling
|
||||
if (!prevNode || !nextNode) return false
|
||||
const range = document.createRange()
|
||||
range.setStart(
|
||||
prevNode,
|
||||
prevNode.nodeType === Node.TEXT_NODE ? prevNode.length : 0
|
||||
)
|
||||
range.setEnd(nextNode, 0)
|
||||
const bounds = range.getBoundingClientRect()
|
||||
return bounds.width !== 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first block parent element of a node.
|
||||
*
|
||||
@@ -1435,19 +1722,19 @@ class BesTreeService extends BesService {
|
||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
||||
let x = source.clientX - canvasPanelRect.x
|
||||
let y = source.clientY - canvasPanelRect.y
|
||||
const pointsInRect = []
|
||||
const hits = []
|
||||
for (let result of this.results) {
|
||||
for (let m of result.matches) {
|
||||
for (let rect of m.highlights) {
|
||||
if (BesService.isPointInRect(x, y, rect)) {
|
||||
pointsInRect.push({ el, match: m })
|
||||
if (BesService.isPointInRect(x, y, rect, 5)) {
|
||||
hits.push({ el, match: m })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dismissPopup()
|
||||
if (pointsInRect.length) this.preparePopup(pointsInRect, source)
|
||||
if (hits.length) this.preparePopup(hits, source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1751,6 +2038,7 @@ class BesQuillService extends BesTreeService {
|
||||
onChangeData(delta) {
|
||||
let index = 0
|
||||
let reproofNeeded = false
|
||||
const affectedBlocks = new Set()
|
||||
|
||||
delta.ops.forEach(op => {
|
||||
if (op.retain) {
|
||||
@@ -1760,11 +2048,12 @@ class BesQuillService extends BesTreeService {
|
||||
}
|
||||
} else if (op.insert) {
|
||||
reproofNeeded = true
|
||||
index += op.insert.length
|
||||
index += typeof op.insert === 'string' ? op.insert.length : 1 // Handle string or embed
|
||||
} else if (op.delete) {
|
||||
reproofNeeded = true
|
||||
}
|
||||
})
|
||||
|
||||
if (reproofNeeded) {
|
||||
const editorLength = this.quillInstance.getLength()
|
||||
const clampedIndex = Math.min(index, editorLength - 1)
|
||||
@@ -1773,22 +2062,55 @@ class BesQuillService extends BesTreeService {
|
||||
if (leaf) {
|
||||
let domElement = leaf.domNode
|
||||
|
||||
// Traverse up to find the block element
|
||||
while (domElement && !this.isBlockElement(domElement)) {
|
||||
domElement = domElement.parentNode
|
||||
}
|
||||
if (domElement) {
|
||||
this.clearProofing(domElement)
|
||||
|
||||
setTimeout(() => {
|
||||
this.redrawAllMistakeMarkup()
|
||||
this.scheduleProofing(1000)
|
||||
}, 0)
|
||||
}
|
||||
if (domElement) affectedBlocks.add(domElement)
|
||||
} else {
|
||||
console.warn(
|
||||
'Leaf is null. The index might be out of bounds or the editor content is empty.'
|
||||
)
|
||||
}
|
||||
|
||||
// Handle pasted content spanning multiple blocks
|
||||
const selection = this.quillInstance.getSelection()
|
||||
if (selection) {
|
||||
const [startLeaf] = this.quillInstance.getLeaf(selection.index)
|
||||
const [endLeaf] = this.quillInstance.getLeaf(
|
||||
selection.index + selection.length
|
||||
)
|
||||
|
||||
if (startLeaf && endLeaf) {
|
||||
let startElement = startLeaf.domNode
|
||||
let endElement = endLeaf.domNode
|
||||
|
||||
while (startElement && !this.isBlockElement(startElement)) {
|
||||
startElement = startElement.parentNode
|
||||
}
|
||||
while (endElement && !this.isBlockElement(endElement)) {
|
||||
endElement = endElement.parentNode
|
||||
}
|
||||
|
||||
if (startElement && endElement) {
|
||||
let currentElement = startElement
|
||||
while (currentElement) {
|
||||
affectedBlocks.add(currentElement)
|
||||
if (currentElement === endElement) break
|
||||
currentElement = currentElement.nextElementSibling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear proofing for all affected blocks
|
||||
affectedBlocks.forEach(block => this.clearProofing(block))
|
||||
|
||||
// Schedule proofing for all affected blocks
|
||||
setTimeout(() => {
|
||||
this.scheduleProofing(1000)
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1966,9 +2288,11 @@ class BesPlainTextService extends BesService {
|
||||
),
|
||||
match: match
|
||||
}
|
||||
this.drawMistakeMarkup(m)
|
||||
matches.push(m)
|
||||
})
|
||||
// Most important matches are first, we want to draw them last => iterate in reverse.
|
||||
for (let i = matches.length; i-- > 0; )
|
||||
this.drawMistakeMarkup(matches[i])
|
||||
this.markProofed(paragraphRange, matches)
|
||||
this.onProofingProgress(matches.length)
|
||||
})
|
||||
@@ -2103,19 +2427,19 @@ class BesPlainTextService extends BesService {
|
||||
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
|
||||
let x = source.clientX - canvasPanelRect.x
|
||||
let y = source.clientY - canvasPanelRect.y
|
||||
const pointsInRect = []
|
||||
const hits = []
|
||||
for (let result of this.results) {
|
||||
for (let m of result.matches) {
|
||||
for (let rect of m.highlights) {
|
||||
if (BesService.isPointInRect(x, y, rect)) {
|
||||
pointsInRect.push({ el: result.range, match: m })
|
||||
if (BesService.isPointInRect(x, y, rect, 5)) {
|
||||
hits.push({ el: result.range, match: m })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dismissPopup()
|
||||
if (pointsInRect.length) this.preparePopup(pointsInRect, source)
|
||||
if (hits.length) this.preparePopup(hits, source)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2367,21 +2691,34 @@ class BesTAService extends BesPlainTextService {
|
||||
const scrollLeft = window.scrollX || document.documentElement.scrollLeft
|
||||
const styles = window.getComputedStyle(hostElement)
|
||||
|
||||
textElement.style.zIndex = hostElement.style.zIndex - 1
|
||||
textElement.style.display = styles.display
|
||||
textElement.style.font = styles.font
|
||||
textElement.style.lineHeight = styles.lineHeight
|
||||
textElement.style.whiteSpace = styles.whiteSpace
|
||||
textElement.style.whiteSpaceCollapse = styles.whiteSpaceCollapse
|
||||
textElement.style.hyphens = styles.hyphens
|
||||
textElement.style.boxSizing = styles.boxSizing
|
||||
textElement.style.scrollBehavior = styles.scrollBehavior
|
||||
textElement.style.overflow = styles.overflow
|
||||
textElement.style.border = styles.border
|
||||
textElement.style.borderRadius = styles.borderRadius
|
||||
textElement.style.borderColor = 'transparent'
|
||||
textElement.style.padding = styles.padding
|
||||
textElement.style.left = `${rect.left + scrollLeft}px`
|
||||
textElement.style.top = `${rect.top + scrollTop}px`
|
||||
textElement.style.width = styles.width
|
||||
textElement.style.height = styles.height
|
||||
textElement.style.width = `${
|
||||
rect.width -
|
||||
parseFloat(styles.borderLeftWidth) -
|
||||
parseFloat(styles.paddingLeft) -
|
||||
parseFloat(styles.paddingRight) -
|
||||
parseFloat(styles.borderRightWidth)
|
||||
}px`
|
||||
textElement.style.height = `${
|
||||
rect.height -
|
||||
parseFloat(styles.borderTopWidth) -
|
||||
parseFloat(styles.paddingTop) -
|
||||
parseFloat(styles.paddingBottom) -
|
||||
parseFloat(styles.borderBottomWidth)
|
||||
}px`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2579,7 +2916,7 @@ class BesPopup extends HTMLElement {
|
||||
padding: 3px 2px;
|
||||
}
|
||||
.bes-toolbar button {
|
||||
margin-right: 2px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
.bes-popup-title {
|
||||
color: #333;
|
||||
@@ -2604,6 +2941,12 @@ class BesPopup extends HTMLElement {
|
||||
}
|
||||
.bes-replacement-btn:hover{
|
||||
background-color: #1976f0;
|
||||
}
|
||||
.bes-replacement-btn:focus{
|
||||
outline: -webkit-focus-ring-color auto 1px;
|
||||
}
|
||||
.bes-replacement-btn:focus-visible{
|
||||
outline: -webkit-focus-ring-color auto 1px;
|
||||
}
|
||||
.bes-replacement-div{
|
||||
margin-top: 4px;
|
||||
@@ -2619,11 +2962,17 @@ class BesPopup extends HTMLElement {
|
||||
.bes-close-btn svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: #333;
|
||||
}
|
||||
.bes-mistake-nav{
|
||||
margin-right: 10px;
|
||||
}
|
||||
:host{
|
||||
--bes-close-icon: #485362;
|
||||
--hover-bg-clr: #dee3ed;
|
||||
}
|
||||
.bes-close-btn:hover {
|
||||
background: #dee3ed;
|
||||
border-radius: 8px
|
||||
background: var(--hover-bg-clr);
|
||||
border-radius: 4px
|
||||
}
|
||||
:host(.show) .bes-popup-container {
|
||||
visibility: visible;
|
||||
@@ -2652,17 +3001,45 @@ class BesPopup extends HTMLElement {
|
||||
background-color: #111213;
|
||||
border: 1px solid #2e3036;
|
||||
}
|
||||
:host{
|
||||
--bes-close-icon: #a4b5c7;
|
||||
--hover-bg-clr:rgba(189, 189, 189, 0.28);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="bes-popup-container">
|
||||
<div class="bes-toolbar">
|
||||
<div class="bes-popup-title">Besana</div>
|
||||
<div class="bes-mistake-nav">
|
||||
<button class="bes-close-btn" title="Prejšnja napaka">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="var(--bes-close-icon)" d="M11 20V7.825l-5.6 5.6L4 12l8-8l8 8l-1.4 1.425l-5.6-5.6V20z"/></svg>
|
||||
</button>
|
||||
<button class="bes-close-btn" title="Naslednja napaka">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="var(--bes-close-icon)" d="M11 4v12.175l-5.6-5.6L4 12l8 8l8-8l-1.4-1.425l-5.6 5.6V4z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<button class="bes-close-btn" onclick="BesPopup.dismiss()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.46 12L19 17.54V19h-1.46L12 13.46L6.46 19H5v-1.46L10.54 12L5 6.46V5h1.46L12 10.54L17.54 5H19v1.46z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="var(--bes-close-icon)" d="M13.46 12L19 17.54V19h-1.46L12 13.46L6.46 19H5v-1.46L10.54 12L5 6.46V5h1.46L12 10.54L17.54 5H19v1.46z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
const prevBtn = this.shadowRoot.querySelector(
|
||||
'.bes-mistake-nav .bes-close-btn[title="Prejšnja napaka"]'
|
||||
)
|
||||
const nextBtn = this.shadowRoot.querySelector(
|
||||
'.bes-mistake-nav .bes-close-btn[title="Naslednja napaka"]'
|
||||
)
|
||||
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (this.hostElService)
|
||||
BesService.arrowBtnNavigation('back', this.hostElService)
|
||||
})
|
||||
nextBtn.addEventListener('click', () => {
|
||||
if (this.hostElService)
|
||||
BesService.arrowBtnNavigation('forward', this.hostElService)
|
||||
})
|
||||
|
||||
this.addEventListener('mousedown', this.onMouseDown)
|
||||
}
|
||||
|
||||
@@ -2672,8 +3049,9 @@ class BesPopup extends HTMLElement {
|
||||
* @param {Number} x X location hint
|
||||
* @param {Number} y Y location hint
|
||||
*/
|
||||
show(x, y) {
|
||||
show(x, y, service) {
|
||||
this.style.position = 'fixed'
|
||||
this.hostElService = service
|
||||
|
||||
// Element needs some initial placement for the browser to provide this.offsetWidth and this.
|
||||
// offsetHeight measurements.
|
||||
|
49
styles.css
49
styles.css
@@ -1,25 +1,39 @@
|
||||
/* TODO: Dark mode theme */
|
||||
|
||||
/* Mistake types styles */
|
||||
.bes-spelling-mistake {
|
||||
border-bottom: 2px solid #ff7300;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.bes-highlight-rect {
|
||||
position: absolute;
|
||||
opacity: 0.3;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.bes-highlight-spelling-rect {
|
||||
background: rgb(0, 123, 255);
|
||||
.bes-highlight-spelling {
|
||||
color: rgb(242, 90, 90);
|
||||
background: hsla(0, 100%, 67%, 0.18);
|
||||
}
|
||||
|
||||
.bes-highlight-grammar-rect {
|
||||
background: rgb(255, 115, 0);
|
||||
.bes-highlight-ai {
|
||||
color: rgb(139, 62, 223);
|
||||
background: hsla(262, 70%, 56%, 0.18);
|
||||
}
|
||||
|
||||
.bes-highlight-grammar {
|
||||
color: rgb(60, 120, 220);
|
||||
background: hsla(220, 80%, 56%, 0.18);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.bes-highlight-spelling {
|
||||
color: rgb(255, 120, 120);
|
||||
background: hsla(0, 100%, 67%, 0.32);
|
||||
}
|
||||
|
||||
.bes-highlight-ai {
|
||||
color: rgb(180, 120, 255);
|
||||
background: hsla(262, 70%, 56%, 0.32);
|
||||
}
|
||||
|
||||
.bes-highlight-grammar {
|
||||
color: rgb(100, 164, 243);
|
||||
background: hsla(220, 80%, 56%, 0.32);
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles required to ensure full functionality and optimal user experience. */
|
||||
@@ -50,9 +64,14 @@
|
||||
|
||||
.bes-text-panel {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
margin: 0px;
|
||||
color: transparent;
|
||||
border-color: transparent;
|
||||
background: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.bes-highlight-placeholder {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
Reference in New Issue
Block a user