PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 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 All 139 releases
← All changes | assets/src/apps/js/admin/react/question/index.js +350 -350 4.3.84.4.8 View file →
@@ -1,350 +1,350 @@
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 );
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 );