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.
 
 
 
 
 
 

390 lines
21 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 html2pdf from 'html2pdf.js'
  124. import Environment from "../environments/Environement"
  125. import CustomStyle from "./extensions/CustomStyle";
  126. export default {
  127. components: {
  128. EditorContent,
  129. EditorMenuBar,
  130. EditorMenuBubble
  131. },
  132. data() {
  133. return {
  134. nbWords : 0,
  135. nbParagraphs: 0,
  136. nbSentences : 0,
  137. environmentName: 'ar',
  138. environment : new Environment(),
  139. keepInBounds: true,
  140. editor: new Editor({
  141. extensions: [
  142. new Blockquote(),
  143. new BulletList(),
  144. new Heading({levels: [1, 2, 3]}),
  145. new ListItem(),
  146. new OrderedList(),
  147. new Link(),
  148. new CustomStyle()
  149. ],
  150. content: `
  151. <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><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>.
  158. </p>
  159. `,
  160. onInit: () => {
  161. //Countable.count(document.getElementById('dedediteur'), counter => this.updateCountInfos(counter), {stripTags: true})
  162. },
  163. onUpdate: () => {
  164. Countable.count(document.getElementById('dedediteur'), counter => this.updateCountInfos(counter), {stripTags: false})
  165. }
  166. }),
  167. linkUrl: null,
  168. linkMenuIsActive: false,
  169. }
  170. },
  171. beforeDestroy() {
  172. this.editor.destroy()
  173. },
  174. mounted(){
  175. let self = this;
  176. fetch('/dedediteur/sample-mounier.html').then(function (response) {
  177. return response.text();
  178. }).then(function (html) {
  179. self.editor.setContent(html);
  180. self.$nextTick().then(() => {
  181. Countable.count(document.getElementById('dedediteur'), counter => self.updateCountInfos(counter), {stripTags: false})
  182. })
  183. }).catch(function (err) {
  184. // There was an error
  185. console.warn('Something went wrong.', err);
  186. });
  187. // const area = document.getElementById('dedediteur')
  188. //
  189. // const callback = counter => this.updateCountInfos(counter)
  190. // Countable.on(area, callback, {stripTags: true})
  191. },
  192. methods: {
  193. loadFile: function () {
  194. let ed = this.editor;
  195. let fr = new FileReader();
  196. fr.onload = function () {
  197. console.log(fr.result);
  198. ed.setContent(fr.result)
  199. };
  200. fr.readAsText(document.getElementById('input').files[0]);
  201. },
  202. saveFile: function () {
  203. //console.log(this.editor.getHTML())
  204. var text = this.editor.getHTML()
  205. var data = new Blob([text], {type: 'text/html'});
  206. var url = window.URL.createObjectURL(data);
  207. document.getElementById('download_link').href = url;
  208. document.getElementById('download_link').click()
  209. },
  210. showLinkMenu(attrs) {
  211. this.linkUrl = attrs.href
  212. this.linkMenuIsActive = true
  213. this.$nextTick(() => {
  214. this.$refs.linkInput.focus()
  215. })
  216. },
  217. hideLinkMenu() {
  218. this.linkUrl = null
  219. this.linkMenuIsActive = false
  220. },
  221. setLinkUrl(command, url) {
  222. command({href: url})
  223. this.hideLinkMenu()
  224. },
  225. loadEnvironment(){
  226. let self = this;
  227. Environment.loadEnvironment(this.environmentName).then(function(env){
  228. self.environment = env;
  229. })
  230. },
  231. updateCountInfos(counter){
  232. console.log(counter);
  233. this.nbWords = counter.words;
  234. this.nbSentences = counter.sentences;
  235. this.nbParagraphs = document.getElementById("dedediteur").querySelectorAll('p').length;
  236. },
  237. pdfExport(){
  238. var element = document.getElementById('dedediteur');
  239. var opt = {
  240. margin: 1,
  241. filename: 'dedediteur-export.pdf',
  242. image: { type: 'jpeg', quality: 0.98 },
  243. html2canvas: { scale: 2 },
  244. jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
  245. };
  246. // New Promise-based usage:
  247. html2pdf().set(opt).from(element).save();
  248. }
  249. }
  250. }
  251. </script>
  252. <style lang="scss">
  253. @import '../assets/_vars';
  254. @import '../assets/menuchar';
  255. @import '../assets/menupara';
  256. @import '../assets/dedediteur.css';
  257. .bottom-bar{
  258. position: fixed;
  259. width: 100vw;
  260. height: 25px;
  261. background-color: $bg-color;
  262. bottom: 0px;
  263. z-index: 50;
  264. border-top:1px solid #ccc;
  265. font-size: 0.8rem;
  266. display: flex;
  267. align-items: center;
  268. div {
  269. display: flex;
  270. span {
  271. margin-right: 10px;
  272. padding-left : 5px;
  273. }
  274. }
  275. }
  276. .env-select{
  277. font-size: 0.8rem;
  278. position : fixed;
  279. right : 2px;
  280. bottom:2px;
  281. z-index: 100;
  282. select {
  283. min-width: 80px;
  284. border: $button-border;
  285. background-color: $bg-color;
  286. }
  287. }
  288. #file_actions {
  289. /*border-bottom : 1px solid #2c3e50;*/
  290. padding: 12px;
  291. position: fixed;
  292. top: 10px;
  293. left: 10px;
  294. z-index: 200;
  295. }
  296. #output_button, #input_button, #pdf_button {
  297. border: 1px solid black;
  298. width: 24px;
  299. height: 24px;
  300. cursor: pointer;
  301. outline:none;
  302. &:focus {
  303. outline: none;
  304. }
  305. }
  306. #input_button {
  307. background-color: white;
  308. }
  309. #output_button {
  310. background-color: black;
  311. margin-left: 12px;
  312. }
  313. #pdf_button{
  314. background-color: red;
  315. margin-left: 12px;
  316. }
  317. /* editor*/
  318. .editor {
  319. position: relative;
  320. }
  321. .ProseMirror:read-write:focus {
  322. outline: none;
  323. }
  324. </style>