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.
 
 
 
 
 
 

365 lines
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>