diff --git a/Gulpfile.js b/Gulpfile.js index 5671aafe05..5c56fac08c 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -48,6 +48,8 @@ gulp.task('js', () => gulp.src([ require.resolve('jquery'), require.resolve('cookies-eu-banner'), + require.resolve('codemirror'), + require.resolve('codemirror/addon/merge/merge'), // Used by other scripts, must be first 'assets/js/modal.js', 'assets/js/tooltips.js', @@ -56,6 +58,7 @@ gulp.task('js', () => 'assets/js/accordeon.js', 'assets/js/ajax-actions.js', 'assets/js/autocompletion.js', + 'assets/js/auto-merge.js', 'assets/js/close-alert-box.js', 'assets/js/compare-commits.js', 'assets/js/dropdown-menu.js', diff --git a/assets/js/auto-merge.js b/assets/js/auto-merge.js new file mode 100644 index 0000000000..cbfa427152 --- /dev/null +++ b/assets/js/auto-merge.js @@ -0,0 +1,65 @@ +(function ($, undefined) { + "use strict"; + + $(document).ready(function () { + + /** + * Sets up the merge interface (using codemirror) in the $div Object. + * Data is generally retrieved from a form field or an aditionnal + * div exposing the old data,also generated in the form. + * @param {Object} $div - The base object used to set up the interface. Generally created in forms files. + * @param {Object} $left - The object from which we will pick the content to put in the left hand side (lhs) of the editor. + * @param {Object} $right - The object from which we will pick the content to put in the right hand side (rhs) of the editor. + */ + function mergeUISetUp(selector, $left, $right){ + var target = document.getElementsByClassName(selector)[0]; // TODO remplacer par ID ou objet + if (target) { + target.innerHTML = ""; + var merge = window.CodeMirror.MergeView(target, { + value: $left.html(), + orig: $right.html(), + lineNumbers: true, + highlightDifferences: true, + connect: "align", + collapseIdentical: true + }); + return merge; + } + } + + var mergeInterfaceList = {}; + mergeInterfaceList.introduction = mergeUISetUp("compare-introduction",$("#your_introduction"),$("#id_introduction")); + mergeInterfaceList.conclusion = mergeUISetUp("compare-conclusion",$("#your_conclusion"),$("#id_conclusion")); + mergeInterfaceList.text = mergeUISetUp("compare-text",$("#your_text"),$("#id_text")); + + $(".CodeMirror-merge-editor").append("Votre Version"); + $(".CodeMirror-merge-right").append("La version courante"); + + /** + * Merge content + */ + $(".merge-btn").on("click", function(e){ + e.stopPropagation(); + e.preventDefault(); + var button = $(this); + + Array.from(this.classList).forEach(function(element){ + if (element.indexOf("need-to-merge-") >= 0) { + var substring = element.substring(14); + var toMerge = mergeInterfaceList[substring].editor().getValue(); + $("#id_" + substring).text(toMerge); + + // Confirmation message + var msg = "
" + + "Le contenu a bien été validé." + + "Masquer l'alerte" + + "
"; + button.before(msg); + setTimeout(function() { + $(".alert-merge").fadeOut("fast"); + }, 2000); + } + }); + }); + }); +})(jQuery); diff --git a/assets/scss/components/_auto-merge.scss b/assets/scss/components/_auto-merge.scss new file mode 100644 index 0000000000..b2a9f53e5e --- /dev/null +++ b/assets/scss/components/_auto-merge.scss @@ -0,0 +1,3 @@ +#compare-lhs-margin, #compare-rhs-margin { + display: none; +} diff --git a/assets/scss/components/_codemirror.scss b/assets/scss/components/_codemirror.scss new file mode 100644 index 0000000000..62b88de4a2 --- /dev/null +++ b/assets/scss/components/_codemirror.scss @@ -0,0 +1,450 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + margin-bottom: -30px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { position: absolute; } +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } + +/* MERGE ADDON */ +.CodeMirror-merge { + position: relative; + border: 1px solid #ddd; + white-space: pre; +} + +.CodeMirror-merge, .CodeMirror-merge .CodeMirror { + height: 350px; +} + +.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; } +.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; } +.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; } +.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; } + +.CodeMirror-merge-pane { + display: inline-block; + white-space: normal; + vertical-align: top; +} +.CodeMirror-merge-pane-rightmost { + position: absolute; + right: 0px; + z-index: 1; +} + +.CodeMirror-merge-gap { + z-index: 2; + display: inline-block; + height: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + position: relative; + background: #f8f8f8; +} + +.CodeMirror-merge-scrolllock-wrap { + position: absolute; + bottom: 0; left: 50%; +} +.CodeMirror-merge-scrolllock { + position: relative; + left: -50%; + cursor: pointer; + color: #555; + line-height: 1; +} + +.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right { + position: absolute; + left: 0; top: 0; + right: 0; bottom: 0; + line-height: 1; +} + +.CodeMirror-merge-copy { + position: absolute; + cursor: pointer; + color: #44c; + z-index: 3; +} + +.CodeMirror-merge-copy-reverse { + position: absolute; + cursor: pointer; + color: #44c; +} + +.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; } +.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; } + +.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; +} + +.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; +} + +.CodeMirror-merge-r-chunk { background: #ffffe0; } +.CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; } +.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; } +.CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; } + +.CodeMirror-merge-l-chunk { background: #eef; } +.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; } +.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; } +.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; } + +.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; } +.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; } +.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; } + +.CodeMirror-merge-collapsed-widget:before { + content: "(...)"; +} +.CodeMirror-merge-collapsed-widget { + cursor: pointer; + color: #88b; + background: #eef; + border: 1px solid #ddf; + font-size: 90%; + padding: 0 3px; + border-radius: 4px; +} +.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; } + diff --git a/assets/scss/components/_mergely.scss b/assets/scss/components/_mergely.scss new file mode 100644 index 0000000000..832762d7b8 --- /dev/null +++ b/assets/scss/components/_mergely.scss @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2016 by Jamie Peabody, http://www.mergely.com + * All rights reserved. + * Version: 3.4.3 2016-09-07 + */ + +/* required */ +.mergely-column textarea { width: 80px; height: 200px; } +.mergely-column { float: left; } +.mergely-margin { float: left; } +.mergely-canvas { float: left; width: 28px; } + +/* resizeable */ +.mergely-resizer { width: 100%; height: 100%; } + +/* style configuration */ +.mergely-column { border: 1px solid #ccc; } +.mergely-active { border: 1px solid #a3d1ff; } + +.mergely.a,.mergely.d,.mergely.c { color: #000; } + +.mergely.a.rhs.start { border-top: 1px solid #a3d1ff; } +.mergely.a.lhs.start.end, +.mergely.a.rhs.end { border-bottom: 1px solid #a3d1ff; } +.mergely.a.rhs { background-color: #ddeeff; } +.mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #a3d1ff; } + +.mergely.d.lhs { background-color: #ffe9e9; } +.mergely.d.lhs.end, +.mergely.d.rhs.start.end { border-bottom: 1px solid #f8e8e8; } +.mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #f8e8e8; } +.mergely.d.lhs.start { border-top: 1px solid #f8e8e8; } + +.mergely.c.lhs, +.mergely.c.rhs { background-color: #fafafa; } +.mergely.c.lhs.start, +.mergely.c.rhs.start { border-top: 1px solid #a3a3a3; } +.mergely.c.lhs.end, +.mergely.c.rhs.end { border-bottom: 1px solid #a3a3a3; } + +.mergely.ch.a.rhs { background-color: #ddeeff; } +.mergely.ch.d.lhs { background-color: #ffe9e9; text-decoration: line-through; color: red !important; } + +.mergely.current.start { border-top: 1px solid #000 !important; } +.mergely.current.end { border-bottom: 1px solid #000 !important; } +.mergely.current.lhs.a.start.end, +.mergely.current.rhs.d.start.end { border-top: 0 !important; } +.mergely.current.CodeMirror-linenumber { color: #F9F9F9; font-weight: bold; background-color: #777; } +.CodeMirror-linenumber { cursor: pointer; } +.CodeMirror-code { color: #717171; } diff --git a/assets/scss/main.scss b/assets/scss/main.scss index c9e4c97cfa..dab5607444 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -58,6 +58,7 @@ @import "components/authors"; @import "components/autocomplete"; @import "components/breadcrumb"; +@import "components/codemirror"; @import "components/content-item"; @import "components/editor"; @import "components/featured-item"; diff --git a/package.json b/package.json index ef688e426b..e526947b48 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,12 @@ "homepage": "https://github.com/zestedesavoir/zds-site", "dependencies": { "autoprefixer": "7.1.2", + "codemirror": "^5.28.0", "cookies-eu-banner": "^1.2.10", "cssnano": "3.10.0", "del": "3.0.0", - "gulp": "3.9.1", + "diff-merge-patch": "^0.6.0", + "gulp": "^3.9.1", "gulp-concat": "2.6.1", "gulp-imagemin": "3.3.0", "gulp-postcss": "7.0.0", diff --git a/templates/base.html b/templates/base.html index d2b0db1aee..b36742dfd2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -678,11 +678,9 @@

{{ headlin {# Javascript stuff start #} - - {% block extra_js %} {% endblock %} - + {# Google Analytics #} diff --git a/templates/tutorialv2/edit/container.html b/templates/tutorialv2/edit/container.html index 4810559c08..af222c4802 100644 --- a/templates/tutorialv2/edit/container.html +++ b/templates/tutorialv2/edit/container.html @@ -3,6 +3,10 @@ {% load i18n %} {% load feminize %} +{% block extra_js %} + +{% endblock %} + {% block title %} {% trans "Éditer " %}{{ "un"|feminize:container.get_level_as_string }} {{ container.get_level_as_string|lower }} {% endblock %} @@ -44,4 +48,4 @@

{% trans "Ajouter une image à la galerie" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/tutorialv2/edit/content.html b/templates/tutorialv2/edit/content.html index 74f55f0ba0..9bbb0d2b51 100644 --- a/templates/tutorialv2/edit/content.html +++ b/templates/tutorialv2/edit/content.html @@ -4,6 +4,10 @@ {% load i18n %} {% load feminize %} +{% block extra_js %} + +{% endblock %} + {% block title %} {% trans "Éditer " %}{{ content.textual_type }} {% endblock %} @@ -42,4 +46,4 @@

{% trans "Ajouter une image à la galerie" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/tutorialv2/edit/extract.html b/templates/tutorialv2/edit/extract.html index 7979ecfbd1..ad28f2fa8b 100644 --- a/templates/tutorialv2/edit/extract.html +++ b/templates/tutorialv2/edit/extract.html @@ -2,6 +2,9 @@ {% load crispy_forms_tags %} {% load i18n %} +{% block extra_js %} + +{% endblock %} {% block title %} {% trans "Éditer la section" %} @@ -50,4 +53,4 @@

{% trans "Ajouter une image à la galerie" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/yarn.lock b/yarn.lock index a94766c5d5..02dcdb76cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -535,6 +535,10 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +codemirror@^5.28.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.28.0.tgz#2978d9280d671351a4f5737d06bbd681a0fd6f83" + color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" @@ -938,6 +942,21 @@ detect-newline@2.X: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" +diff-merge-patch@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/diff-merge-patch/-/diff-merge-patch-0.4.0.tgz#c13251422e53fa4b72b2f7c3a3afd1cba2763ec8" + dependencies: + longest-common-substring "0.0.1" + underscore "~1.4.3" + +diff-merge-patch@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/diff-merge-patch/-/diff-merge-patch-0.6.0.tgz#3ff2886f00ce1de3d5f455e0bfee0b8ad950322b" + dependencies: + diff-merge-patch "0.4.0" + longest-common-substring "0.0.1" + underscore "~1.4.3" + dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -1834,7 +1853,7 @@ gulp.spritesmith@6.5.1: underscore "~1.8.3" url2 "~1.0.4" -gulp@3.9.1: +gulp@^3.9.1: version "3.9.1" resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" dependencies: @@ -2659,6 +2678,10 @@ logalot@^2.0.0: figures "^1.3.5" squeak "^1.0.0" +longest-common-substring@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/longest-common-substring/-/longest-common-substring-0.0.1.tgz#d91f8d08ab5d2debc9914fca8d4b91413956acc8" + longest@^1.0.0, longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -4443,7 +4466,7 @@ underscore.string@~3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.0.3.tgz#4617b8c1a250cf6e5064fbbb363d0fa96cf14552" -underscore@~1.4.2: +underscore@~1.4.2, underscore@~1.4.3: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py index ae4a5f8984..1f351083ea 100644 --- a/zds/tutorialv2/forms.py +++ b/zds/tutorialv2/forms.py @@ -14,7 +14,7 @@ from django.utils.translation import ugettext_lazy as _ from zds.member.models import Profile from zds.tutorialv2.utils import slugify_raise_on_invalid, InvalidSlugError -from zds.utils.forms import TagValidator +from zds.utils.forms import TagValidator, MergeableFieldMixin class FormWithTitle(forms.Form): @@ -107,7 +107,7 @@ def clean_username(self): return cleaned_data -class ContainerForm(FormWithTitle): +class ContainerForm(FormWithTitle, MergeableFieldMixin): introduction = forms.CharField( label=_('Introduction'), @@ -149,18 +149,25 @@ def __init__(self, *args, **kwargs): self.helper.form_class = 'content-wrapper' self.helper.form_method = 'post' - self.helper.layout = Layout( - Field('title'), - Field('introduction', css_class='md-editor preview-source'), - ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', - css_class='btn btn-grey preview-btn'),), - HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \ - with text=form.introduction.value %}{% endif %}'), - Field('conclusion', css_class='md-editor preview-source'), - ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', - css_class='btn btn-grey preview-btn'),), - HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \ - with text=form.conclusion.value %}{% endif %}'), + self.helper.layout = Layout(Field('title')) + + if kwargs.get('data', None) is not None: + self.add_merge_interface_to_field('introduction', **kwargs) + self.add_merge_interface_to_field('conclusion', **kwargs) + else: + self.helper.layout.append(Layout( + Field('introduction', css_class='md-editor preview-source'), + ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', + css_class='btn btn-grey preview-btn'),), + HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \ + with text=form.introduction.value %}{% endif %}'), + Field('conclusion', css_class='md-editor preview-source'), + ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', + css_class='btn btn-grey preview-btn'), + HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \ + with text=form.conclusion.value %}{% endif %}')))) + + self.helper.layout.append(Layout( Field('msg_commit'), Field('last_hash'), ButtonHolder( @@ -168,10 +175,10 @@ def __init__(self, *args, **kwargs): _('Valider'), type='submit'), ) - ) + )) -class ContentForm(ContainerForm): +class ContentForm(ContainerForm, MergeableFieldMixin): description = forms.CharField( label=_('Description'), @@ -230,7 +237,7 @@ class ContentForm(ContainerForm): widget=forms.CheckboxSelectMultiple() ) - def _create_layout(self, hide_help): + def _create_layout(self, hide_help, **kwargs): html_part = HTML(_("

Demander de l'aide à la communauté !
" "Si vous avez besoin d'un coup de main, " "sélectionnez une ou plusieurs catégories d'aide ci-dessous " @@ -243,21 +250,28 @@ def _create_layout(self, hide_help): Field('description'), Field('tags'), Field('type'), - Field('image'), - Field('introduction', css_class='md-editor preview-source'), - ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', - css_class='btn btn-grey preview-btn'),), - HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \ - with text=form.introduction.value %}{% endif %}'), - Field('conclusion', css_class='md-editor preview-source'), - ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', - css_class='btn btn-grey preview-btn'),), - HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \ - with text=form.conclusion.value %}{% endif %}'), + Field('image')) + + if kwargs.get('data') is not None: + self.add_merge_interface_to_field('introduction', **kwargs) + self.add_merge_interface_to_field('conclusion', **kwargs) + else: + self.helper.layout.append(Layout( + Field('introduction', css_class='md-editor preview-source'), + ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', + css_class='btn btn-grey preview-btn'),), + HTML('{% if form.introduction.value %}{% include "misc/previsualization.part.html" \ + with text=form.introduction.value %}{% endif %}'), + Field('conclusion', css_class='md-editor preview-source'), + ButtonHolder(StrictButton(_('Aperçu'), type='preview', name='preview', + css_class='btn btn-grey preview-btn'), + HTML('{% if form.conclusion.value %}{% include "misc/previsualization.part.html" \ + with text=form.conclusion.value %}{% endif %}')))) + + self.helper.layout.append(Layout( Field('last_hash'), Field('licence'), - Field('subcategory', template='crispy/checkboxselectmultiple.html'), - ) + Field('subcategory', template='crispy/checkboxselectmultiple.html'))) if not hide_help: self.helper.layout.append(html_part) @@ -270,10 +284,11 @@ def __init__(self, *args, **kwargs): for_tribune = kwargs.pop('for_tribune', False) super(ContentForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() self.helper = FormHelper() self.helper.form_class = 'content-wrapper' self.helper.form_method = 'post' - self._create_layout(for_tribune) + self._create_layout(for_tribune, **kwargs) if 'type' in self.initial: self.helper['type'].wrap( diff --git a/zds/utils/forms.py b/zds/utils/forms.py index 867b330958..f831320dce 100644 --- a/zds/utils/forms.py +++ b/zds/utils/forms.py @@ -8,6 +8,24 @@ from zds.utils.misc import contains_utf8mb4 +class MergeableFieldMixin(): + def add_merge_interface_to_field(self, field_name, **kwargs): + field_old_content = kwargs.get('data').get(field_name) + if field_old_content is None: + field_old_content = '' + self.helper.layout.append(Layout(Field(field_name, css_class='hidden'))) + + self.helper.layout.append( + Layout(HTML('

' + .format(field_name, field_old_content)))) + self.helper.layout.append( + Layout(HTML('
' + .format(field_name)))) + self.helper.layout.append(Layout( + ButtonHolder(StrictButton(_('Valider cette version'), type='merge', name='merge', + css_class='btn btn-submit merge-btn need-to-merge-{0}'.format(field_name))))) + + class CommonLayoutEditor(Layout): def __init__(self, *args, **kwargs): @@ -31,12 +49,18 @@ def __init__(self, *args, **kwargs): ) -class CommonLayoutVersionEditor(Layout): +class CommonLayoutVersionEditor(Layout, MergeableFieldMixin): def __init__(self, *args, **kwargs): + + if kwargs.get('data', None) is not None: + self.add_merge_interface_to_field('text', **kwargs) + else: + text_field = Field('text', css_class='md-editor') + super(CommonLayoutVersionEditor, self).__init__( Div( - Field('text', css_class='md-editor'), + text_field, Field('msg_commit'), ButtonHolder( StrictButton(