Simple text editor based on tiptap. HTML format.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

401 lines
22 KiB

  1. <template>
  2. <div class="editor">
  3. <div class="bottom-bar">
  4. <div>
  5. <label>Paragraphes :</label><span>{{nbParagraphs}}</span>
  6. <label>Phrases :</label><span>{{nbSentences}}</span>
  7. <label>Mots :</label><span>{{nbWords}}</span>
  8. </div>
  9. <div class="env-select" >
  10. <!--<label>Environnement </label>-->
  11. <select v-model="environmentName" @change="loadEnvironment()">
  12. <option value="ar">AR</option>
  13. <option value="basic">Basic</option>
  14. <option value="basicplus">Basic +</option>
  15. </select>
  16. </div>
  17. </div>
  18. <div id="file_actions">
  19. <button title="Ouvrir un fichier" id="input_button"
  20. onclick="document.getElementById('input').click()"></button>
  21. <button title="Enregistrer" id="output_button" v-on:click="saveFile"></button>
  22. <input type="file" style="display:none;" id="input" v-on:change="loadFile()">
  23. <a style="display:none;" id="download_link" download="dedediteur-export-demo.html" href=”” >Download as Text File</a>
  24. <button title="Export PDF" id="pdf_button" v-on:click="pdfExport"></button>
  25. </div>
  26. <div class="editor-main">
  27. <!--<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">-->
  28. <editor-menu-bubble class="menububble" :editor="editor" @hide="hideLinkMenu"
  29. v-slot="{ commands, isActive, getMarkAttrs, menu }">
  30. <div
  31. class="menububble"
  32. :class="{ 'is-active': menu.isActive }"
  33. :style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
  34. >
  35. <div class="menubar char-menubar">
  36. <div v-bind:key="opt.label" v-for="opt in environment.getCharacterOptions()" class="dropdown">
  37. <button class="dropbtn border-left">{{opt.label}}</button>
  38. <div class="dropdown-content">
  39. <a v-for="style in opt.styles" v-bind:key="style.label">
  40. <button
  41. class="menubar__button"
  42. :class="{ 'is-active': isActive.customstyle({ type: style.class }) }"
  43. @click="commands.customstyle({ type: style.class })">
  44. {{style.label}}
  45. </button>
  46. </a>
  47. </div>
  48. </div>
  49. <form class="menububble__form" v-if="linkMenuIsActive"
  50. @submit.prevent="setLinkUrl(commands.link, linkUrl)">
  51. <input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://"
  52. ref="linkInput" @keydown.esc="hideLinkMenu"/>
  53. <button class="menububble__button" @click="setLinkUrl(commands.link, null)" type="button">
  54. Supprimer le lien
  55. </button>
  56. </form>
  57. <template v-else>
  58. <button id="link__button"
  59. class="menububble__button"
  60. @click="showLinkMenu(getMarkAttrs('link'))"
  61. :class="{ 'is-active': isActive.link() }"
  62. >
  63. <span>{{ isActive.link() ? 'Modifier le lien' : 'Lien'}}</span>
  64. </button>
  65. </template>
  66. </div>
  67. </div>
  68. </editor-menu-bubble>
  69. <editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
  70. <div class="menubar paragraph-menubar">
  71. <button
  72. class="menubar__button"
  73. :class="{ 'is-active': isActive.paragraph() }"
  74. @click="commands.paragraph">
  75. </button>
  76. <button v-if="environment.hasParagraphOption('T1')"
  77. class="menubar__button"
  78. :class="{ 'is-active': isActive.heading({ level: 1 }) }"
  79. @click="commands.heading({ level: 1 })">
  80. T1
  81. </button>
  82. <button v-if="environment.hasParagraphOption('T2')"
  83. class="menubar__button"
  84. :class="{ 'is-active': isActive.heading({ level: 2 }) }"
  85. @click="commands.heading({ level: 2 })">
  86. T2
  87. </button>
  88. <button v-if="environment.hasParagraphOption('T3')"
  89. class="menubar__button"
  90. :class="{ 'is-active': isActive.heading({ level: 3 }) }"
  91. @click="commands.heading({ level: 3 })">
  92. T3
  93. </button>
  94. <button v-if="environment.hasParagraphOption('T4')"
  95. class="menubar__button"
  96. :class="{ 'is-active': isActive.heading({ level: 3 }) }"
  97. @click="commands.heading({ level: 3 })">
  98. T4
  99. </button>
  100. <button v-if="environment.hasParagraphOption('quote')"
  101. class="menubar__button"
  102. :class="{ 'is-active': isActive.blockquote() }"
  103. @click="commands.blockquote">
  104. « »
  105. </button>
  106. </div>
  107. </editor-menu-bar>
  108. <editor-content id="dedediteur" class="editor__content" :editor="editor"/>
  109. </div>
  110. </div>
  111. </template>
  112. <script>
  113. import {Editor, EditorContent, EditorMenuBar, EditorMenuBubble} from 'tiptap'
  114. import Countable from 'countable'
  115. import {
  116. Blockquote,
  117. Heading,
  118. OrderedList,
  119. BulletList,
  120. ListItem,
  121. Link
  122. } from 'tiptap-extensions'
  123. import Environment from "../environments/Environement"
  124. import CustomStyle from "./extensions/CustomStyle"
  125. export default {
  126. components: {
  127. EditorContent,
  128. EditorMenuBar,
  129. EditorMenuBubble
  130. },
  131. data() {
  132. return {
  133. nbWords : 0,
  134. nbParagraphs: 0,
  135. nbSentences : 0,
  136. environmentName: 'ar',
  137. environment : new Environment(),
  138. keepInBounds: true,
  139. editor: new Editor({
  140. extensions: [
  141. new Blockquote(),
  142. new BulletList(),
  143. new Heading({levels: [1, 2, 3]}),
  144. new ListItem(),
  145. new OrderedList(),
  146. new Link(),
  147. new CustomStyle()
  148. ],
  149. content: `
  150. <p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  151. </p><p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  152. </p><p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  153. </p><p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  154. </p><p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  155. </p><p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  156. </p><p>Soit un <a href="https://fr.wiktionary.org/wiki/moment">moment</a> de <a href="https://fr.wikipedia.org/wiki/Perception">perception</a> &amp; son ouverture <a href="https://fr.wikipedia.org/wiki/Philosophie_de_la_perception">philosophique</a>. Pour cela, avoir accès à une connexion <a href="https://fr.wikipedia.org/wiki/Internet">internet</a> &amp; installer, sur l’ordinateur exploité pour s’y connecter, un <a href="https://fr.wikipedia.org/wiki/Navigateur_web">navigateur</a> pour consulter le [web](<a href="https://fr.wikipedia.org/wiki/World_Wide_Web">World Wide Web — Wikipédia</a>). Cette consultation se concrétise par la surface d’un écran où nous sont perceptibles des pages d’informations de diverses natures (iconographique, vidéo ou <a href="https://fr.wiktionary.org/wiki/textuel">textuelle</a>) dédiées à notre <a href="https://fr.wiktionary.org/wiki/aspectus#la"><em>aspectus</em></a>. Ces informations nous sont disponibles dans l’espace de l’écran sous différents <a href="https://fr.wiktionary.org/wiki/aspect">aspects</a>, résultats de la <a href="https://fr.wiktionary.org/wiki/mani%C3%A8re">manière</a> dont elles ont été <a href="https://fr.wiktionary.org/wiki/former">formées</a>.
  157. </p>
  158. `,
  159. onInit: () => {
  160. //Countable.count(document.getElementById('dedediteur'), counter => this.updateCountInfos(counter), {stripTags: true})
  161. },
  162. onUpdate: () => {
  163. Countable.count(document.getElementById('dedediteur'), counter => this.updateCountInfos(counter), {stripTags: false})
  164. }
  165. }),
  166. linkUrl: null,
  167. linkMenuIsActive: false,
  168. }
  169. },
  170. beforeDestroy() {
  171. this.editor.destroy()
  172. },
  173. mounted(){
  174. let self = this;
  175. fetch('/dedediteur/sample-mounier.html').then(function (response) {
  176. return response.text();
  177. }).then(function (html) {
  178. self.editor.setContent(html);
  179. self.$nextTick().then(() => {
  180. Countable.count(document.getElementById('dedediteur'), counter => self.updateCountInfos(counter), {stripTags: false})
  181. })
  182. }).catch(function (err) {
  183. // There was an error
  184. console.warn('Something went wrong.', err);
  185. });
  186. // const area = document.getElementById('dedediteur')
  187. //
  188. // const callback = counter => this.updateCountInfos(counter)
  189. // Countable.on(area, callback, {stripTags: true})
  190. },
  191. methods: {
  192. loadFile: function () {
  193. let ed = this.editor;
  194. let fr = new FileReader();
  195. fr.onload = function () {
  196. console.log(fr.result);
  197. ed.setContent(fr.result)
  198. };
  199. fr.readAsText(document.getElementById('input').files[0]);
  200. },
  201. saveFile: function () {
  202. //console.log(this.editor.getHTML())
  203. var text = this.editor.getHTML()
  204. var data = new Blob([text], {type: 'text/html'});
  205. var url = window.URL.createObjectURL(data);
  206. document.getElementById('download_link').href = url;
  207. document.getElementById('download_link').click()
  208. },
  209. showLinkMenu(attrs) {
  210. this.linkUrl = attrs.href
  211. this.linkMenuIsActive = true
  212. this.$nextTick(() => {
  213. this.$refs.linkInput.focus()
  214. })
  215. },
  216. hideLinkMenu() {
  217. this.linkUrl = null
  218. this.linkMenuIsActive = false
  219. },
  220. setLinkUrl(command, url) {
  221. command({href: url})
  222. this.hideLinkMenu()
  223. },
  224. loadEnvironment(){
  225. let self = this;
  226. Environment.loadEnvironment(this.environmentName).then(function(env){
  227. self.environment = env;
  228. })
  229. },
  230. updateCountInfos(counter){
  231. console.log(counter);
  232. this.nbWords = counter.words;
  233. this.nbSentences = counter.sentences;
  234. this.nbParagraphs = document.getElementById("dedediteur").querySelectorAll('p').length;
  235. },
  236. pdfExport(){
  237. window.print();
  238. //link rel="stylesheet" type="text/css" href=
  239. // let cssPrintElt = document.createElement("link");
  240. // cssPrintElt.setAttribute('rel', 'stylesheet');
  241. // cssPrintElt.setAttribute('type', 'text/css');
  242. // cssPrintElt.setAttribute('media','print');
  243. // cssPrintElt.setAttribute('href', './print.css');
  244. // document.head.appendChild(cssPrintElt);
  245. // printJS('dedediteur', 'html')
  246. //printJS({printable:'docs/xx_large_printjs.pdf', type:'pdf', showModal:true})
  247. // var element = document.getElementById('dedediteur');
  248. // var opt = {
  249. // margin: 1,
  250. // filename: 'dedediteur-export.pdf',
  251. // image: { type: 'jpeg', quality: 0.98 },
  252. // html2canvas: { scale: 2 },
  253. // jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
  254. //};
  255. // New Promise-based usage:
  256. // html2pdf().set(opt).from(element).save();
  257. }
  258. }
  259. }
  260. </script>
  261. <style lang="scss">
  262. @import '../assets/_vars';
  263. @import '../assets/menuchar';
  264. @import '../assets/menupara';
  265. @import '../assets/dedediteur.css';
  266. .bottom-bar{
  267. position: fixed;
  268. width: 100vw;
  269. height: 25px;
  270. background-color: $bg-color;
  271. bottom: 0px;
  272. z-index: 50;
  273. border-top:1px solid #ccc;
  274. font-size: 0.8rem;
  275. display: flex;
  276. align-items: center;
  277. div {
  278. display: flex;
  279. span {
  280. margin-right: 10px;
  281. padding-left : 5px;
  282. }
  283. }
  284. }
  285. .env-select{
  286. font-size: 0.8rem;
  287. position : fixed;
  288. right : 2px;
  289. bottom:2px;
  290. z-index: 100;
  291. select {
  292. min-width: 80px;
  293. border: $button-border;
  294. background-color: $bg-color;
  295. }
  296. }
  297. #file_actions {
  298. /*border-bottom : 1px solid #2c3e50;*/
  299. padding: 12px;
  300. position: fixed;
  301. top: 10px;
  302. left: 10px;
  303. z-index: 200;
  304. }
  305. #output_button, #input_button, #pdf_button {
  306. border: 1px solid black;
  307. width: 24px;
  308. height: 24px;
  309. cursor: pointer;
  310. outline:none;
  311. &:focus {
  312. outline: none;
  313. }
  314. }
  315. #input_button {
  316. background-color: white;
  317. }
  318. #output_button {
  319. background-color: black;
  320. margin-left: 12px;
  321. }
  322. #pdf_button{
  323. background-color: red;
  324. margin-left: 12px;
  325. }
  326. /* editor*/
  327. .editor {
  328. position: relative;
  329. }
  330. .ProseMirror:read-write:focus {
  331. outline: none;
  332. }
  333. </style>