PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / trunk
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses vtrunk
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
learnpress / assets / src / apps / js / admin / react / question / index.js

index.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses trunk, at assets/src/apps/js/admin/react/question/index.js

351 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { Component } from '@wordpress/element';
2 import { withSelect, withDispatch, select as wpSelect } from '@wordpress/data';
3 import { compose } from '@wordpress/compose';
4 import { __ } from '@wordpress/i18n';
5
6 const { debounce } = lodash;
7
8 import store from './store';
9
10 const stripSlashes = function stripSlashes( str ) {
11 return ( str + '' ).replace( /\\(.?)/g, function( s, n1 ) {
12 switch ( n1 ) {
13 case '\\':
14 return '\\';
15 case '0':
16 return '\u0000';
17 case '':
18 return '';
19 default:
20 return n1;
21 }
22 } );
23 };
24
25 class Editor extends Component {
26 constructor( props ) {
27 super( ...arguments );
28
29 this.state = {
30 blanks: [],
31 };
32 }
33
34 componentDidMount() {
35 const {
36 setData,
37 initSettings,
38 } = this.props;
39 const { blankOptions } = initSettings;
40 const newBlanks = {};
41
42 blankOptions.map( ( option ) => {
43 const optionBlanks = this.getBlanks( option.text );
44 newBlanks[ option.question_answer_id ] = optionBlanks ? optionBlanks[ 0 ] : [];
45 } );
46
47 setData( {
48 question: {
49 ...initSettings,
50 blanks: newBlanks,
51 },
52 } );
53 }
54
55 /**
56 * Parse blanks options from content and update state.
57 *
58 * @param option
59 * @param text
60 */
61 setContent = ( option, text ) => {
62 const optionBlanks = this.getBlanks( text );
63 const {
64 blankOptions,
65 blanks,
66 updateOption,
67 setData,
68 id,
69 } = this.props;
70
71 const newState = {
72 blankOptions: blankOptions.map( ( opt ) => {
73 return opt.question_answer_id == option.question_answer_id ? {
74 ...opt,
75 text,
76 } : opt;
77 } ),
78 blanks: {
79 ...blanks,
80 [ option.question_answer_id ]: optionBlanks ? optionBlanks[ 0 ] : [],
81 },
82 };
83
84 setData( newState, 'question' );
85 this.queue = this.queue || {};
86 this.queue[ option.question_answer_id ] = [ {
87 text,
88 blanks: newState.blanks[ option.question_answer_id ],
89 }, option.question_answer_id, id ];
90
91 this.updateOption();
92 };
93
94 updateOption = debounce( () => {
95 const {
96 updateOption,
97 } = this.props;
98
99 const queue = this.queue ? Object.values( this.queue ) : [];
100
101 queue.map( ( item ) => {
102 if ( item ) {
103 updateOption( item[ 0 ], item[ 1 ], item[ 2 ] );
104 delete this.queue[ item[ 1 ] ];
105 }
106 } );
107 }, 1000 );
108
109 /**
110 * Get blanks from a content and return.
111 *
112 * @param content
113 * @return {Array}
114 */
115 getBlanks = ( content ) => {
116 // if (undefined === content) {
117 // content = this.state.passage;
118 // }
119
120 const blanks = [];
121 const shortcodes = content.match( /\{\{([^\{\"\'].*?)\}\}/g );
122
123 if ( shortcodes ) {
124 shortcodes.map( ( shortcode ) => {
125 blanks.push( this.getBlank( shortcode ) );
126 } );
127 }
128 return blanks;
129 };
130
131 /**
132 * Parse details from a blank.
133 *
134 * @param blank
135 * @return {{words: Array, tip: string, corrects: Array}}
136 */
137 getBlank = ( blank ) => {
138 const contents = blank.match( /\{\{(.*)\}\}/ );
139 const words = contents && contents[ 1 ] ? contents[ 1 ].split( '/' ) : [];
140 const matchTip = words.length ? words[ words.length - 1 ].match( /(.*)(\s(\"(.*)\"|\'(.*)\'))/ ) : false;
141 const corrects = [];
142
143 // Remove tip from last word. (last-word "This is tip of the blank")
144 if ( matchTip ) {
145 words[ words.length - 1 ] = matchTip[ 1 ];
146 }
147
148 words.map( ( word, i ) => {
149 // Match the word wrapped by [ and ] is CORRECT word
150 const matchCorrect = word.match( /\[(.*)\]/ );
151
152 if ( matchCorrect ) {
153 // Remove the "[" and "]"
154 matchCorrect[ 1 ] = stripSlashes( matchCorrect[ 1 ] );
155 corrects.push( matchCorrect[ 1 ] );
156 words[ i ] = matchCorrect[ 1 ];
157 } else {
158 words[ i ] = stripSlashes( word );
159 }
160 } );
161
162 return {
163 words: words.filter( ( w ) => {
164 return w && w.length;
165 } ),
166 tip: matchTip ? stripSlashes( matchTip[ 4 ] || matchTip[ 5 ] || '' ) : '',
167 corrects: corrects.filter( ( w ) => {
168 return w && w.length;
169 } ),
170 };
171 };
172
173 /**
174 * Event handler for each input in blanks.
175 *
176 * @param answer
177 */
178 onChangeOption = ( answer ) => ( event ) => {
179 const {
180 instantParseBlanks,
181 } = this.props;
182
183 if ( instantParseBlanks ) {
184 this.setContent( answer, event.target.value );
185 }
186 };
187
188 /**
189 * Event handler to add new blank to question
190 *
191 * @param event
192 */
193 addBlank = ( event ) => {
194 const {
195 addOption,
196 id,
197 } = this.props;
198
199 addOption( id, { text: 'asdasdasd' } );
200 };
201
202 /**
203 * Event handler to remove a blank.
204 *
205 * @param option
206 */
207 removeBlank = ( option ) => () => {
208 const {
209 id,
210 removeOption,
211 } = this.props;
212
213 removeOption( id, option.question_answer_id );
214 };
215
216 /**
217 * Get html of passage content for preview.
218 *
219 * @return {string}
220 */
221 getPreview = () => {
222 const {
223 blankOptions,
224 blankFillsStyle,
225 blanksStyle,
226 } = this.props;
227
228 if ( ! blankOptions ) {
229 return '';
230 }
231
232 const preview = blankOptions.map( ( answer ) => {
233 const blanks = this.getBlanks( answer.text );
234 const blank = blanks ? blanks[ 0 ] : {};
235
236 let html = '';
237
238 if ( blank && blank.words.length ) {
239 html = '<span class="blank-input"></span>';
240
241 if ( blank.words.length > 1 ) {
242 switch ( blankFillsStyle ) {
243 case 'select':
244 html += '<select>' + blank.words.map( ( word ) => {
245 return `<option value=${ word }>${ word }</option>`;
246 } ).join( '' ) + '</select>';
247
248 break;
249 case 'enumeration':
250 html += '(' + blank.words.map( ( word ) => {
251 return `<code>${ word }</code>`;
252 } ).join( ', ' ) + ')';
253 break;
254 }
255 }
256
257 if ( blank.tip ) {
258 html += '?';
259 }
260 }
261
262 return ( '' + answer.text ).replace( /\{\{(.*)\}\}/, html );
263 } ).join( blanksStyle === 'paragraphs' ? '</div><div>' : ( blanksStyle === 'ordered' ? '</li><li>' : ' ' ) );
264
265 return blanksStyle === 'paragraphs' ? `<div>${ preview }</div>` : ( blanksStyle === 'ordered' ? `<ol><li>${ preview }</li></ol>` : preview );
266 };
267
268 render() {
269 const {
270 blanks,
271 blankOptions,
272 } = this.props;
273
274 return <React.Fragment>
275 <div className="blank-options">
276 { blankOptions && (
277 <ul className="blanks">
278 {
279 blankOptions.map( ( answer ) => {
280 const blankOptions = blanks[ answer.question_answer_id ] || {};
281
282 return <li className="blank" key={ answer.question_answer_id }>
283 <textarea className="blank-content" onChange={ this.onChangeOption( answer ) }
284 value={ answer.text }>
285 </textarea>
286 { blankOptions.words && (
287 <div className="blank-words">
288 <label>{ __( 'Words fill', 'learnpress' ) }</label>
289 <p>
290 {
291 blankOptions.words && blankOptions.words.map( ( word, i ) => {
292 const className = [ 'word' ];
293 ( blankOptions.corrects || [] ).indexOf( word ) !== -1 && className.push( 'correct' );
294
295 return <code key={ `word-${ word }-${ i }` }
296 className={ className.join( ' ' ) }>{ word }</code>;
297 } )
298 }
299 </p>
300 </div>
301 )
302 }
303
304 { blankOptions.tip && (
305 <div className="blank-tip">
306 <label>{ __( 'Tip', 'learnpress' ) }</label>
307 <p>{ blankOptions.tip }</p>
308 </div>
309 )
310 }
311
312 <button className="button button-remove"
313 onClick={ this.removeBlank( answer ) }>{ __( 'Remove', 'learnpress' ) }</button>
314 </li>;
315 } )
316 }
317 </ul> )
318 }
319 <button className="button" onClick={ this.addBlank }>{ __( 'Add Blank', 'learnpress' ) }</button>
320 </div>
321 <div className="passage-preview" dangerouslySetInnerHTML={ { __html: this.getPreview() } }>
322 </div>
323 </React.Fragment>;
324 }
325 }
326
327 export default compose( [
328 withSelect( ( select ) => {
329 const {
330 getData,
331 } = select( 'learnpress/question' );
332
333 return getData( 'question' );
334 } ),
335 withDispatch( ( dispatch ) => {
336 const {
337 setData,
338 addOption,
339 removeOption,
340 updateOption,
341 } = dispatch( 'learnpress/question' );
342
343 return {
344 setData,
345 addOption,
346 removeOption,
347 updateOption,
348 };
349 } ),
350 ] )( Editor );
351