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.
 
 
 
 
 
 

365 lignes
20 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. </div>
  25. <div class="editor-main">
  26. <!--<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">-->
  27. <editor-menu-bubble class="menububble" :editor="editor" @hide="hideLinkMenu"
  28. v-slot="{ commands, isActive, getMarkAttrs, menu }">
  29. <div
  30. class="menububble"
  31. :class="{ 'is-active': menu.isActive }"
  32. :style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
  33. >
  34. <div class="menubar char-menubar">
  35. <div v-bind:key="opt.label" v-for="opt in environment.getCharacterOptions()" class="dropdown">
  36. <button class="dropbtn border-left">{{opt.label}}</button>
  37. <div class="dropdown-content">
  38. <a v-for="style in opt.styles" v-bind:key="style.label">
  39. <button
  40. class="menubar__button"
  41. :class="{ 'is-active': isActive.customstyle({ type: style.class }) }"
  42. @click="commands.customstyle({ type: style.class })">
  43. {{style.label}}
  44. </button>
  45. </a>
  46. </div>
  47. </div>
  48. <form class="menububble__form" v-if="linkMenuIsActive"
  49. @submit.prevent="setLinkUrl(commands.link, linkUrl)">
  50. <input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://"
  51. ref="linkInput" @keydown.esc="hideLinkMenu"/>
  52. <button class="menububble__button" @click="setLinkUrl(commands.link, null)" type="button">
  53. Supprimer le lien
  54. </button>
  55. </form>
  56. <template v-else>
  57. <button id="link__button"
  58. class="menububble__button"
  59. @click="showLinkMenu(getMarkAttrs('link'))"
  60. :class="{ 'is-active': isActive.link() }"
  61. >
  62. <span>{{ isActive.link() ? 'Modifier le lien' : 'Lien'}}</span>
  63. </button>
  64. </template>
  65. </div>
  66. </div>
  67. </editor-menu-bubble>
  68. <editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
  69. <div class="menubar paragraph-menubar">
  70. <button
  71. class="menubar__button"
  72. :class="{ 'is-active': isActive.paragraph() }"
  73. @click="commands.paragraph">
  74. </button>
  75. <button v-if="environment.hasParagraphOption('T1')"
  76. class="menubar__button"
  77. :class="{ 'is-active': isActive.heading({ level: 1 }) }"
  78. @click="commands.heading({ level: 1 })">
  79. T1
  80. </button>
  81. <button v-if="environment.hasParagraphOption('T2')"
  82. class="menubar__button"
  83. :class="{ 'is-active': isActive.heading({ level: 2 }) }"
  84. @click="commands.heading({ level: 2 })">
  85. T2
  86. </button>
  87. <button v-if="environment.hasParagraphOption('T3')"
  88. class="menubar__button"
  89. :class="{ 'is-active': isActive.heading({ level: 3 }) }"
  90. @click="commands.heading({ level: 3 })">
  91. T3
  92. </button>
  93. <button v-if="environment.hasParagraphOption('T4')"
  94. class="menubar__button"
  95. :class="{ 'is-active': isActive.heading({ level: 3 }) }"
  96. @click="commands.heading({ level: 3 })">
  97. T4
  98. </button>
  99. <button v-if="environment.hasParagraphOption('quote')"
  100. class="menubar__button"
  101. :class="{ 'is-active': isActive.blockquote() }"
  102. @click="commands.blockquote">
  103. « »
  104. </button>
  105. </div>
  106. </editor-menu-bar>
  107. <editor-content id="dedediteur" class="editor__content" :editor="editor"/>
  108. </div>
  109. </div>
  110. </template>
  111. <script>
  112. import {Editor, EditorContent, EditorMenuBar, EditorMenuBubble} from 'tiptap'
  113. import Countable from 'countable'
  114. import {
  115. Blockquote,
  116. Heading,
  117. OrderedList,
  118. BulletList,
  119. ListItem,
  120. Link
  121. } from 'tiptap-extensions'
  122. import Environment from "../environments/Environement"
  123. import CustomStyle from "./extensions/CustomStyle";
  124. export default {
  125. components: {
  126. EditorContent,
  127. EditorMenuBar,
  128. EditorMenuBubble
  129. },
  130. data() {
  131. return {
  132. nbWords : 0,
  133. nbParagraphs: 0,
  134. nbSentences : 0,
  135. environmentName: 'ar',
  136. environment : new Environment(),
  137. keepInBounds: true,
  138. editor: new Editor({
  139. extensions: [
  140. new Blockquote(),
  141. new BulletList(),
  142. new Heading({levels: [1, 2, 3]}),
  143. new ListItem(),
  144. new OrderedList(),
  145. new Link(),
  146. new CustomStyle()
  147. ],
  148. content: `
  149. <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>.
  150. </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>.
  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>
  157. `,
  158. onInit: () => {
  159. //Countable.count(document.getElementById('dedediteur'), counter => this.updateCountInfos(counter), {stripTags: true})
  160. },
  161. onUpdate: () => {
  162. Countable.count(document.getElementById('dedediteur'), counter => this.updateCountInfos(counter), {stripTags: false})
  163. }
  164. }),
  165. linkUrl: null,
  166. linkMenuIsActive: false,
  167. }
  168. },
  169. beforeDestroy() {
  170. this.editor.destroy()
  171. },
  172. mounted(){
  173. let self = this;
  174. fetch('/dedediteur/sample-mounier.html').then(function (response) {
  175. return response.text();
  176. }).then(function (html) {
  177. self.editor.setContent(html);
  178. self.$nextTick().then(() => {
  179. Countable.count(document.getElementById('dedediteur'), counter => self.updateCountInfos(counter), {stripTags: false})
  180. })
  181. }).catch(function (err) {
  182. // There was an error
  183. console.warn('Something went wrong.', err);
  184. });
  185. // const area = document.getElementById('dedediteur')
  186. //
  187. // const callback = counter => this.updateCountInfos(counter)
  188. // Countable.on(area, callback, {stripTags: true})
  189. },
  190. methods: {
  191. loadFile: function () {
  192. let ed = this.editor;
  193. let fr = new FileReader();
  194. fr.onload = function () {
  195. console.log(fr.result);
  196. ed.setContent(fr.result)
  197. };
  198. fr.readAsText(document.getElementById('input').files[0]);
  199. },
  200. saveFile: function () {
  201. //console.log(this.editor.getHTML())
  202. var text = this.editor.getHTML()
  203. var data = new Blob([text], {type: 'text/html'});
  204. var url = window.URL.createObjectURL(data);
  205. document.getElementById('download_link').href = url;
  206. document.getElementById('download_link').click()
  207. },
  208. showLinkMenu(attrs) {
  209. this.linkUrl = attrs.href
  210. this.linkMenuIsActive = true
  211. this.$nextTick(() => {
  212. this.$refs.linkInput.focus()
  213. })
  214. },
  215. hideLinkMenu() {
  216. this.linkUrl = null
  217. this.linkMenuIsActive = false
  218. },
  219. setLinkUrl(command, url) {
  220. command({href: url})
  221. this.hideLinkMenu()
  222. },
  223. loadEnvironment(){
  224. let self = this;
  225. Environment.loadEnvironment(this.environmentName).then(function(env){
  226. self.environment = env;
  227. })
  228. },
  229. updateCountInfos(counter){
  230. console.log(counter);
  231. this.nbWords = counter.words;
  232. this.nbSentences = counter.sentences;
  233. this.nbParagraphs = document.getElementById("dedediteur").querySelectorAll('p').length;
  234. }
  235. }
  236. }
  237. </script>
  238. <style lang="scss">
  239. @import '../assets/_vars';
  240. @import '../assets/menuchar';
  241. @import '../assets/menupara';
  242. @import '../assets/dedediteur.css';
  243. .bottom-bar{
  244. position: fixed;
  245. width: 100vw;
  246. height: 25px;
  247. background-color: $bg-color;
  248. bottom: 0px;
  249. z-index: 50;
  250. border-top:1px solid #ccc;
  251. font-size: 0.8rem;
  252. display: flex;
  253. align-items: center;
  254. div {
  255. display: flex;
  256. span {
  257. margin-right: 10px;
  258. padding-left : 5px;
  259. }
  260. }
  261. }
  262. .env-select{
  263. font-size: 0.8rem;
  264. position : fixed;
  265. right : 2px;
  266. bottom:2px;
  267. z-index: 100;
  268. select {
  269. min-width: 80px;
  270. border: $button-border;
  271. background-color: $bg-color;
  272. }
  273. }
  274. #file_actions {
  275. /*border-bottom : 1px solid #2c3e50;*/
  276. padding: 12px;
  277. position: fixed;
  278. top: 10px;
  279. left: 10px;
  280. z-index: 200;
  281. }
  282. #output_button, #input_button {
  283. border: 1px solid black;
  284. width: 24px;
  285. height: 24px;
  286. cursor: pointer;
  287. }
  288. #input_button {
  289. background-color: white;
  290. }
  291. #output_button {
  292. background-color: black;
  293. margin-left: 12px;
  294. }
  295. /* editor*/
  296. .editor {
  297. position: relative;
  298. }
  299. .ProseMirror:read-write:focus {
  300. outline: none;
  301. }
  302. </style>