Simple text editor based on tiptap. HTML format.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

401 lignes
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>