9 | *
10 | * @copyright 2017
11 | */
12 |
13 | console.log( "==== simpread component: SelectField ====" )
14 |
15 | let style, styles = new Map();
16 |
17 | const color = 'rgba(51, 51, 51, .87)',
18 | secondary_color = 'rgba(204, 204, 204, 1)',
19 |
20 | focus_color = 'rgba(0, 137, 123, .8)',
21 | border_color = 'rgba(224, 224, 224, 1)',
22 | error_color = 'rgba(244, 67, 54, 1)',
23 |
24 | selected_color = 'rgba(255, 64, 129, 1)',
25 | hover_color = 'rgba(238, 238, 238, 1)',
26 | background_color = 'rgba(255, 255, 255, 1)';
27 |
28 | const cssinjs = () => {
29 |
30 | const display = 'block',
31 | width = '100%',
32 | margin = '8px 0 0 0',
33 | medium = '14px',
34 | large = '16px',
35 | lineHeight = 1.5,
36 | fontWeight = 'bold',
37 | styles = {
38 | hidden : 'none',
39 | root: {},
40 | root_normal: {
41 | display,
42 | position: 'relative',
43 | margin: 0,
44 | padding: 0,
45 |
46 | width,
47 | height: '45px',
48 | lineHeight: 1,
49 |
50 | cursor: 'pointer',
51 | userSelect: 'none',
52 | },
53 |
54 | disable: {
55 | color: secondary_color,
56 | cursor: 'not-allowed',
57 | },
58 |
59 | border: {
60 | display,
61 |
62 | width,
63 | margin,
64 |
65 | borderTop: `none ${border_color}`,
66 | borderLeft: `none ${border_color}`,
67 | borderRight: `none ${border_color}`,
68 | borderBottom: `1px solid ${border_color}`,
69 | boxSizing: 'content-box',
70 | },
71 |
72 | border_disable: {
73 | borderBottom: `1px dashed ${border_color}`,
74 | },
75 |
76 | float: {},
77 |
78 | float_normal: {
79 | display,
80 | position: 'absolute',
81 |
82 | margin,
83 |
84 | fontSize: medium,
85 | color: secondary_color,
86 |
87 | userSelect: 'none',
88 | pointerEvents: 'none',
89 |
90 | transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
91 | transform: 'scale(1) translate( 0px, 0px )',
92 | transformOrigin: 'left top 0px',
93 | },
94 |
95 | float_focus: {
96 | color: focus_color,
97 |
98 | margin: `-${margin}`,
99 |
100 | fontSize: medium,
101 | fontWeight,
102 |
103 | transform: 'scale(0.75) translate( 0px, -8px )',
104 | },
105 |
106 | error: {
107 | display,
108 | position: 'absolute',
109 |
110 | margin,
111 | width: '110%',
112 |
113 | fontSize: medium,
114 | fontWeight,
115 | lineHeight,
116 |
117 | userSelect: 'none',
118 |
119 | color: error_color,
120 | transform: 'scale(0.75) translate( -80px, 0 )',
121 | },
122 |
123 | text: {
124 | display,
125 |
126 | /*height: '20px',*/
127 |
128 | margin: 0,
129 | padding: 0,
130 | },
131 |
132 | name: {},
133 | name_normal: {
134 | display,
135 |
136 | margin,
137 | padding: '0 20px 0 0',
138 |
139 | fontFamily: 'sans-serif',
140 | fontSize: medium,
141 | textAlign: 'left',
142 | lineHeight,
143 | },
144 |
145 | placeholder: {
146 | color: secondary_color,
147 | },
148 |
149 | icon: {
150 | display: 'block',
151 | position: 'absolute',
152 |
153 | width: '24px',
154 | height: '24px',
155 |
156 | top: '6px',
157 | right: 0,
158 |
159 | border: 'none',
160 | backgroundPosition: 'center',
161 | backgroundRepeat: 'no-repeat',
162 | backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAABqSURBVEiJ7dQxCsAgDIXhZ8ktgmetVw31GIF06lI0yeIWJyH4f4hgMzOcXNfRegEFFAAAoGA+ROR2A0STmftu7t5ARAYRTS+uqtt4CACAqvYVkomngBWSjQPxG/yR59tnz7X6rgso4DzwAnJQKlbAmFdgAAAAAElFTkSuQmCC)',
163 | },
164 |
165 | bg: {
166 | display: 'none',
167 | position: 'fixed',
168 |
169 | top: 0,
170 | left: 0,
171 |
172 | width: '100%',
173 | height: '100%',
174 | },
175 |
176 | };
177 |
178 | return styles;
179 | }
180 |
181 | const cssinjs_list = () => {
182 |
183 | const styles = {
184 | hidden : 'none',
185 | root : {},
186 | root_normal : {
187 | display: 'block',
188 | position: 'absolute',
189 |
190 | top: 0,
191 | left: 0,
192 |
193 | margin: 0,
194 | padding: 0,
195 |
196 | width: '100%',
197 | minHeight: '100px',
198 | maxHeight: '718px',
199 |
200 | color,
201 | backgroundColor: background_color,
202 |
203 | boxSizing: 'border-box',
204 | boxShadow: '0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2)',
205 | borderRadius: '2px',
206 |
207 | zIndex: 2100,
208 |
209 | overflowY: 'auto',
210 |
211 | opacity: 0,
212 | transform: 'scaleY(0)',
213 | transformOrigin: 'left top 0px',
214 | transition : 'transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',
215 | },
216 |
217 | open: {
218 | opacity: 1,
219 | transform: 'scaleY(1)',
220 | },
221 |
222 | list_filed: {
223 | display: 'flex',
224 | alignItems: 'center',
225 |
226 | padding: '0 16px',
227 |
228 | height: '56px',
229 | width: '100%',
230 |
231 | textAlign: 'left',
232 |
233 | boxSizing: 'border-box',
234 | transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',
235 | },
236 |
237 | list_filed_icon: {
238 | display: 'block',
239 |
240 | width: '24px',
241 | height: '24px',
242 |
243 | margin: '0 16px 0 0',
244 | padding: '16px',
245 |
246 | border: 'none',
247 | backgroundPosition: 'center',
248 | backgroundRepeat: 'no-repeat',
249 | },
250 |
251 | list_filed_value: {
252 | display: 'inline',
253 | width: '100%',
254 | fontSize: 'inherit',
255 | },
256 |
257 | list_filed_info: {
258 | display: 'inline',
259 | padding: '0 0 0 16px',
260 |
261 | fontSize: '13px',
262 | textAlign: 'right',
263 |
264 | minWidth: '100px',
265 | },
266 |
267 | }
268 |
269 | return styles;
270 | }
271 |
272 | class ListView extends React.Component {
273 |
274 | static defaultProps = {
275 | waves : "",
276 | items : [],
277 | active : "",
278 | };
279 |
280 | static propTypes = {
281 | waves : React.PropTypes.string,
282 | items : React.PropTypes.array,
283 | active : React.PropTypes.string,
284 | onChange : React.PropTypes.func,
285 | };
286 |
287 | state = {
288 | id : Math.round(+new Date()),
289 | };
290 |
291 | onMouseOver( event ) {
292 | const $target = $( event.target );
293 | if ( $target.is( "list-field" ) ) {
294 | $( "list-field[active=true]" ).css( "background-color", "transparent" ).attr( "active", false );
295 | $target.attr( "active", true ).css( "background-color", hover_color );
296 | }
297 | }
298 |
299 | onClick( event ) {
300 | let $target = $( event.target );
301 | while ( !$target.is( "list-field" )) { $target = $target.parent(); }
302 | setTimeout( ()=>this.props.onChange && this.props.onChange( $target.find( "list-field-name" ).attr( "value" ), $target.find( "list-field-name" ).text() ), 130 );
303 | }
304 |
305 | render() {
306 | styles.set( this.state.id, cssinjs_list() );
307 | style = styles.get( this.state.id );
308 |
309 | style.root = this.props.items.length > 1 ? { ...style.root_normal, ...style.open } : { ...style.root_normal };
310 |
311 | const list = this.props.items.map( ( item, idx ) => {
312 | let [ name_style, icon_style, info_style ] =[ { ...style.list_filed_value }, { ...style.list_filed_icon }, { ...style.list_filed_info } ];
313 | ( !item.info || item.info == "" ) && ( info_style.display = style.hidden );
314 | if ( item.icon && item.icon != "" ) {
315 | icon_style.backgroundImage = `url(${ item.icon })`;
316 | } else {
317 | icon_style.display = style.hidden;
318 | }
319 | item.name == this.props.active && ( name_style.color = selected_color );
320 | item.style && item.style.root && ( style.list_filed = { ...style.list_filed, ...item.style.root });
321 | item.style && item.style.icon && ( icon_style = { ...icon_style, ...item.style.icon });
322 | item.style && item.style.text && ( name_style = { ...name_style, ...item.style.text });
323 | item.style && item.style.state && ( info_style = { ...info_style, ...item.style.state });
324 | return (
325 | this.onMouseOver(e) } onClick={ (e)=>this.onClick(e) }>
326 |
327 | { item.name }
328 | { item.info }
329 |
330 | )
331 | });
332 |
333 | return (
334 |
335 | { list }
336 |
337 | )
338 | }
339 | }
340 |
341 | /**
342 | * Custom component: SelectField
343 | *
344 | * Reference:
345 | * - https://material.io/guidelines/components/lists-controls.html
346 | * - http://www.material-ui.com/#/components/select-field
347 | *
348 | * @class
349 | */
350 | export default class SelectField extends React.Component {
351 |
352 | static defaultProps = {
353 | name : "",
354 | disable : false,
355 | width : undefined,
356 | placeholder : "",
357 | floatingtext : "",
358 | errortext : "",
359 | items : [],
360 | waves : "",
361 | tooltip : {},
362 | };
363 |
364 | static propTypes = {
365 | name : React.PropTypes.string,
366 | disable : React.PropTypes.bool,
367 | width : React.PropTypes.string,
368 | placeholder : React.PropTypes.string,
369 | floatingtext : React.PropTypes.string,
370 | errortext : React.PropTypes.string,
371 | items : React.PropTypes.array,
372 | waves : React.PropTypes.string,
373 | tooltip : React.PropTypes.object,
374 | onChange : React.PropTypes.func,
375 | };
376 |
377 | state = {
378 | id : Math.round(+new Date()),
379 | name : this.props.name,
380 | };
381 |
382 | onClick() {
383 | !this.props.disable && this.props.items.length > 0 && this.setState({ items: this.props.items });
384 | !this.props.disable && this.props.items.length > 0 && $( this.refs.bg ).css( "display", "block" );
385 | }
386 |
387 | bgOnClick() {
388 | $( this.refs.bg ).css( "display", "none" );
389 | this.setState({ items : [] });
390 | }
391 |
392 | onChange( value, name ) {
393 | this.props.onChange && this.props.onChange( value, name );
394 | this.setState({
395 | items : [],
396 | name,
397 | });
398 | $( this.refs.bg ).css( "display", "none" );
399 | }
400 |
401 | componentDidMount() {
402 | style = styles.get( this.state.id );
403 | const $error = $( this.refs.error );
404 | this.props.errortext != "" &&
405 | $error.parent().height( Number.parseInt(style.root.height) + $error.height() );
406 | }
407 |
408 | render() {
409 | styles.set( this.state.id, cssinjs() );
410 | style = styles.get( this.state.id );
411 |
412 | this.props.width && ( style.root.width = this.props.width );
413 | this.props.disable && ( style.border = { ...style.border, ...style.border_disable });
414 | this.props.floatingtext == "" && ( style.float.display = style.hidden );
415 |
416 | style.root = this.props.disable ? { ...style.root_normal, ...style.disable } : { ...style.root_normal };
417 | style.name = this.state.name == "" ? { ...style.name_normal, ...style.placeholder } : { ...style.name_normal };
418 | style.float = this.props.placeholder == "" && this.state.name == "" ? style.float_normal : { ...style.float_normal, ...style.float_focus }
419 |
420 | const tooltip = this.props.tooltip;
421 | return (
422 |
424 | { this.props.floatingtext }
425 | this.onClick() }>
426 |
427 | { this.state.name == "" ? this.props.placeholder : this.state.name
428 | }
429 |
430 |
431 |
432 | { this.props.errortext }
433 | this.onChange(v,n) } />
434 | this.bgOnClick() }>
435 |
436 | )
437 |
438 | }
439 | }
--------------------------------------------------------------------------------
/src/module/search.jsx:
--------------------------------------------------------------------------------
1 | console.log( "==== sov2ex module: Search ====" )
2 |
3 | import TextField from 'textfield';
4 | import Button from 'button';
5 |
6 | import * as filter from 'filter';
7 |
8 | import pangu from 'pangu';
9 |
10 | /**
11 | * Result Card
12 | *
13 |
14 |
19 |
20 | 简悦- SimpRead 让你瞬间进入沉浸式阅读的 Chrome 扩展,还原阅读的本质,提升你的阅读体验。 简悦是什么: 简悦是 沉浸式阅读的 Chrome 扩展,取自:「简单阅读,心情...
21 |
22 |
23 |
kenshin
24 | 于
25 |
2017-07-03
26 | 发表,共计
27 |
154 个回复
28 |
29 |
30 | *
31 | * @param {object} props
32 | */
33 | const ResultCard = props => {
34 | let content = props.highlight.content || props.highlight["reply_list.content"] || props.highlight["postscript_list.content"];
35 | content = content && content.length > 0 ? content[0] : props.content;
36 | return (
37 |
38 |
43 |
44 | { pangu.spacing( content.replace( /<\/?em>/ig, "" ))}
45 |
46 |
47 |
{props.member}
48 | 于
49 |
{props.created.replace( "T", " " )}
50 | 发表,共计
51 |
{props.replies} 个回复
52 |
53 |
54 | );
55 | }
56 |
57 | /**
58 | * Empty Card
59 | */
60 | const EmptyCard = props => {
61 | return (
62 |
63 |
64 | { props.text }
65 |
66 | )
67 | }
68 |
69 | /**
70 | * Loading Card
71 | */
72 | const LoadingCard = () => {
73 | return (
74 |
75 |
78 |
79 | )
80 | }
81 |
82 | /**
83 | * Paging divider
84 | *
85 | * @param {object} props
86 | */
87 | const PagingHR = props => {
88 | return (
89 |
90 |
91 |
92 |
{ `第 ${props.page} 页,共计 ${ props.count } 页` }
93 |
94 |
95 |
96 | )
97 | }
98 |
99 | const search_icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABWUlEQVQ4T61UbVUDQRBLFIADWgXgAFAADgAFFAW0CigOWgfgABRQBxwOQEF4uTfbt7fduwPK/Lv9yGaSzBH/XKzhSZoAuABwBuAQwCeAFwDPJJshDjuAkuYAbgOodndJ8q4PtAMoaQXgKg5/AdgEs5NgexB7Xj8naead2gIGs/vYXQOY5RckufXE3sceSc6qgKHZW7S5Jnnd11Lx8LTUtGUoyS89AHCbk1or+QOSbMxRjWUCfApXX0na2cGStAzjds4nQEfiFMCCpHUaA/QZ690LmBg6Z5c/ABxlmDR0DCz0Thx+q6En4z0uVeOQADMDvVR3OZxObfhzTnJRtl6ANSSnvcEOUE/AcRxyNKyt23eora07yWtF8iZfKEevnIaaPx+RwbTXAR3629goz3Aqs/ePoSlm3vtb0CrgWGxCnvxH4qXWzD8DVkDbDO8FmKXDZjkZm70BS3m+Adf+mhXFbtFFAAAAAElFTkSuQmCC",
100 | arrow_icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAtklEQVQ4T+2TUQ0CMRBE3ygACTjgJCABHIACkAIKwAFIQAI4QAIoGNKkJBxpr71wn7ef7fbtZHYqBi4NzGME/u9oy0PbM+DYE7uR9Pi8+QVOgSswr4TegYWkZxIYDqPKGzApQF9A860u9CdjY7uJSnPQAAvKwuBWZXNoewmcMypXki6pu85g214nlhSWcMrZUfwptvfANgIOknZd3haBcVEBSgmWXUplZJJtVQr7DBiBfdxK974BrbYqFVDVxssAAAAASUVORK5CYII=";
101 |
102 | export default class Search extends React.Component {
103 |
104 | static defaultProps = {
105 | url : "https://www.sov2ex.com/api/search",
106 | q : undefined,
107 | page : 1,
108 | size : 10,
109 | max : 1000,
110 | sort : "sumup",
111 | node : undefined,
112 | order: 0,
113 | gte : 0,
114 | lte : 0,
115 | }
116 |
117 | static propTypes = {
118 | page : React.PropTypes.number,
119 | size : React.PropTypes.number,
120 | order : React.PropTypes.oneOf([ 0, 1 ]),
121 | sort : React.PropTypes.oneOf([ "sumup", "created" ]),
122 | }
123 |
124 | state = {
125 | cost : undefined,
126 | list : [],
127 | count : 0,
128 | disable: false,
129 | }
130 |
131 | onSearchClick() {
132 | setTimeout( ()=>this.search( this.refs.search.refs.target.value ), 500 );
133 | }
134 |
135 | arrowOnClick() {
136 | filter.Render( $( ".filtergp" )[0] );
137 | $( ".filtergp" ).toggleClass( "filtergp-top" );
138 | }
139 |
140 | onKeyDown( event ) {
141 | event.keyCode == 13 &&
142 | this.search( event.target.value );
143 | }
144 |
145 | search( value ) {
146 | if ( /[%#&]/ig.test( value ) ) {
147 | new Notify().Render( "不能包含特殊字符 % # &" );
148 | } else if ( value.trim() != "" ) {
149 | let url = window.location.origin + window.location.pathname + `?q=${value}`;
150 | Object.keys( sessionStorage ).forEach( key => url += `&${key}=${sessionStorage[key]}`);
151 | sessionStorage.clear();
152 | console.log( sessionStorage, url )
153 | window.location.href = url.replace( /&page=\d+/ig, "" );
154 | } else {
155 | new Notify().Render( "不能为空,请输入正确的值。" );
156 | }
157 | }
158 |
159 | validation( key, value ) {
160 | switch ( key ) {
161 | case "page":
162 | if ( !/\d+$/.test( value ) || value < 1 ) {
163 | value = 1;
164 | new Notify().Render( 2, "page 参数错误,取值范围最小值为 1 的正整数,请确认。" );
165 | }
166 | break;
167 | case "size":
168 | if ( !/[1-9]+/.test( value ) || value < 1 || value > 50 ) {
169 | value = 10;
170 | new Notify().Render( 2, "size 参数错误,取值范围 1 ~ 50 的正整数,请确认。" );
171 | }
172 | break;
173 | case "order":
174 | if ( !/^(0|1)$/.test( value ) ) {
175 | value = 0;
176 | new Notify().Render( 2, "order 参数错误,取值范围 0 和 1,请确认。" );
177 | }
178 | break;
179 | case "sort":
180 | if ( !/^(sumup|created)$/.test( value ) ) {
181 | value = "sumup";
182 | new Notify().Render( 2, "sort 参数错误,取值范围 sumup 和 created,请确认。" );
183 | }
184 | break;
185 | case "gte":
186 | case "lte":
187 | if ( !/\d+$/.test( value ) ) {
188 | new Notify().Render( 2, `${key} 参数错误,正确格式为 yyyy-mm-dd,请确认。` );
189 | }
190 | break;
191 | }
192 | return value;
193 | }
194 |
195 | parse( result ) {
196 | let count = Math.floor( result.total / this.props.size ),
197 | list = this.state.list.concat( result.hits );
198 | count = count * this.props.size > this.props.max ? Math.floor( this.props.max / this.props.size ) : count;
199 | this.setState({
200 | list,
201 | cost: {
202 | took : result.took,
203 | total: result.total
204 | },
205 | disable: this.props.page >= count,
206 | count: count == 0 ? 1 : count,
207 | });
208 | }
209 |
210 | fetch() {
211 | const page = this.props.page,
212 | from = ( page - 1 ) * this.props.size,
213 | url = `${this.props.url}?q=${this.props.q}&sort=${this.props.sort}&order=${this.props.order}&from=${from}&size=${this.props.size}&node=${this.props.node}<e=${parseInt(this.props.lte)/1000}>e=${parseInt(this.props.gte)/1000}`;
214 | $.ajax({
215 | url,
216 | dataType: "json",
217 | crossDomain: true,
218 | })
219 | .done( result => {
220 | console.log( result, url, page, from )
221 | this.parse( result )
222 | })
223 | .fail( error => {
224 | console.error( error )
225 | new Notify().Render( 2, "当前发生了一些错误,请重新输入。" );
226 | this.failed();
227 | });
228 | }
229 |
230 | onPagingClick() {
231 | this.props.page++;
232 | if ( this.props.page > this.state.count ) {
233 | this.setState({ disable: true });
234 | new Notify().Render( "当前已经是最后一页。" );
235 | } else {
236 | this.fetch();
237 | /page=\d+/.test( window.location.search ) &&
238 | history.pushState( "", "", window.location.search.replace( /page=\d+/, `page=${this.props.page}` ) );
239 | }
240 | }
241 |
242 | failed() {
243 | this.props.q = "";
244 | this.setState({ list: [], cost: { total: 0 }});
245 | }
246 |
247 | componentWillMount() {
248 | try {
249 | const search = decodeURI( location.search.trim() );
250 | if ( search.startsWith( "?q=" ) && search != "?q=" ) {
251 | const query = search.replace( "?", "" ).split( "&" );
252 | query && query.length > 0 && query.forEach( item => {
253 | const [ key, value ] = item.split( "=" );
254 | this.props[key] = this.validation( key, value );
255 | });
256 | if ( this.props.q != "" ) {
257 | this.fetch();
258 | $( "head title" ).text( `${this.props.q} - SOV2EX 搜索结果` );
259 | } else {
260 | new Notify().Render( "搜索内容有误,请重新搜索。" );
261 | this.failed();
262 | }
263 | } else {
264 | new Notify().Render( "搜索内容有误,请重新搜索。" );
265 | this.failed();
266 | }
267 | } catch ( error ) {
268 | new Notify().Render( "不能包含特殊字符 % # &" );
269 | this.failed();
270 | }
271 | }
272 |
273 | render() {
274 |
275 | let hidden = false, list = this.state.list.map( item => {
276 | return
277 | });
278 |
279 | if ( !this.state.cost ) {
280 | hidden = true;
281 | list = ;
282 | }
283 | else if ( this.state.cost.total == 0 ) {
284 | hidden = true;
285 | list = ;
286 | } else if ( this.props.page > this.state.count ) {
287 | hidden = true;
288 | list = ;
289 | }
290 |
291 | return (
292 |
293 |
294 |
299 |
300 |
301 |
this.onKeyDown(e) }
306 | />
307 |
308 |
315 |
316 |
323 |
324 |
325 |
326 |
327 |
328 |
329 | 共计 { this.state.cost && this.state.cost.total} 个结果,耗时 {this.state.cost && this.state.cost.took} 毫秒
330 |
331 |
332 | { list }
333 |
334 |
335 |
336 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
364 |
365 |
368 |
369 |
370 | )
371 | }
372 | }
--------------------------------------------------------------------------------
/src/vender/mduikit/list.jsx:
--------------------------------------------------------------------------------
1 | /*!
2 | * React Material Design: List
3 | *
4 | * @version : 0.0.1
5 | * @update : 2017/04/17
6 | * @homepage: https://github.com/kenshin/react-md-ui
7 | * @license : MIT https://github.com/kenshin/react-md/blob/master/LICENSE
8 | * @author : Kenshin Wang
9 | *
10 | * @copyright 2017
11 | */
12 |
13 | console.log( "==== simpread component: List ====" )
14 |
15 | let styles = new Map();
16 |
17 | const color = "rgba( 51, 51, 51, .87 )",
18 | secondary_color = "rgba( 51, 51, 51, .54 )",
19 | disable_color = "rgba( 51, 51, 51, .38 )",
20 | hover_color = "rgba( 238, 238, 238, 1 )",
21 | border_color = "rgba( 224, 224, 224, 1 )",
22 | transparent_color= "transparent",
23 | background_color = "rgba( 255, 255, 255, 1 )";
24 |
25 | const cssinjs = () => {
26 | const styles = {
27 |
28 | root: {
29 | display: 'flex',
30 | flexDirection: 'column',
31 | },
32 |
33 | header: {
34 | display: 'block',
35 |
36 | paddingLeft: '72px',
37 |
38 | textAlign: 'left',
39 |
40 | color,
41 | fontSize: '1.5rem',
42 | fontWeight: 700,
43 | },
44 |
45 | list_item: {
46 | display: 'flex',
47 | justifyContent: 'center',
48 | alignItems: 'center',
49 |
50 | height: '56px',
51 | },
52 |
53 | pri_item: {},
54 |
55 | pri_item_text: {
56 | display: 'block',
57 |
58 | minWidth: '40px',
59 | minHeight: '40px',
60 |
61 | margin: '0 16px',
62 | padding: 0,
63 |
64 | lineHeight: '40px',
65 | fontWeight: 600,
66 |
67 | borderRadius: '50%',
68 |
69 | color,
70 | backgroundColor: transparent_color,
71 | },
72 |
73 | sec_item: {},
74 |
75 | sec_item_text: {
76 | display: '-webkit-box',
77 |
78 | WebkitLineClamp: 1,
79 | '-webkit-box-orient': 'vertical',
80 |
81 | margin: '0 16px',
82 |
83 | minWidth: '50px',
84 |
85 | overflow: 'hidden',
86 | textOverflow: 'ellipsis',
87 |
88 | color,
89 | backgroundColor: transparent_color,
90 | },
91 |
92 | state_none: {
93 | width: 0,
94 | opacity: 0,
95 | visibility: 'hidden',
96 | paddingLeft: '72px',
97 | },
98 |
99 | state_avatar: {
100 | display: 'block',
101 |
102 | minWidth: '40px',
103 | minHeight: '40px',
104 |
105 | margin: '0 16px',
106 | padding: 0,
107 |
108 | lineHeight: '40px',
109 |
110 | borderRadius: '50%',
111 | backgroundPosition: 'center',
112 | backgroundRepeat: 'no-repeat',
113 | },
114 |
115 | state_icon: {
116 | display: 'block',
117 |
118 | minWidth: '24px',
119 | minHeight: '24px',
120 |
121 | margin: '8px 24px',
122 | padding: 0,
123 |
124 | lineHeight: '24px',
125 |
126 | borderRadius: '50%',
127 | backgroundPosition: 'center',
128 | backgroundRepeat: 'no-repeat',
129 | },
130 |
131 | state_action: {},
132 |
133 | content: {
134 | display: 'flex',
135 | flexDirection: 'column',
136 | alignItems: 'flex-start',
137 | width: '100%',
138 | },
139 |
140 | link: {
141 | display: '-webkit-box',
142 | flexShrink: 1,
143 |
144 | WebkitLineClamp: 1,
145 | '-webkit-box-orient': 'vertical',
146 |
147 | overflow: 'hidden',
148 | textOverflow: 'ellipsis',
149 | textAlign: 'left',
150 |
151 | fontSize: '1.6rem',
152 | color,
153 | },
154 |
155 | subtitle: {
156 | display: '-webkit-box',
157 | flexShrink: 2,
158 |
159 | WebkitLineClamp: 1,
160 | '-webkit-box-orient': 'vertical',
161 |
162 | overflow: 'hidden',
163 | textOverflow: 'ellipsis',
164 | textAlign: 'left',
165 |
166 | color: secondary_color,
167 | },
168 |
169 | action: {
170 | display: 'block',
171 | position: 'relative',
172 |
173 | margin: '0 16px 0 0',
174 | padding: 0,
175 | },
176 |
177 | action_icon: {
178 | display: 'block',
179 |
180 | width: '27px',
181 | height: '27px',
182 |
183 | lineHeight: '27px',
184 |
185 | borderRadius: '50%',
186 |
187 | cursor: 'pointer',
188 |
189 | backgroundPosition: 'center',
190 | backgroundRepeat: 'no-repeat',
191 | backgroundImage: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBAwKJDqU6cY9AAAAgElEQVQ4y+3PsQnCYBRF4U+xcQYRHEcdwLhASOE8avEjSsApBIOWOoFYSRZxgBh8WueU73Lu49Lxnd7ncxrKvPKqmfRbip72zmkZF0ZgGhd2YN0MBi3CykWd33/ZUKpSFhfGYBYXDmAT31A4qfNb/MPD0TUt4sIEzONCCbY6/uIN+soX7VMadxMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDQtMTJUMTA6MzY6NTgrMDg6MDBBhh2RAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA0LTEyVDEwOjM2OjU4KzA4OjAwMNulLQAAAABJRU5ErkJggg==)',
192 | },
193 |
194 | action_items: {
195 | display: 'block',
196 | position: 'absolute',
197 |
198 | top: 0,
199 | right: 0,
200 |
201 | margin: 0,
202 | padding: '8px 0',
203 |
204 | minWidth: '150px',
205 |
206 | backgroundColor: background_color,
207 |
208 | borderRadius: '2px',
209 | boxShadow: 'rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px',
210 |
211 | opacity: 0,
212 | transformOrigin: 'left top 0px',
213 | transform: 'scaleY(0)',
214 | transition : 'transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',
215 |
216 | zIndex: 0,
217 | },
218 |
219 | action_items_active: {
220 | opacity: 1,
221 | transform: 'scaleY(1)',
222 |
223 | zIndex: 100,
224 | },
225 |
226 | action_items_over: {
227 | backgroundColor: hover_color,
228 | transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',
229 | },
230 |
231 | action_bg: {
232 | display: 'none',
233 | position: 'fixed',
234 |
235 | top: 0,
236 | left: 0,
237 |
238 | width: '100%',
239 | height: '100%',
240 | },
241 |
242 | action_group: {},
243 |
244 | action_item: {
245 | display: '-webkit-box',
246 |
247 | WebkitLineClamp: 1,
248 | '-webkit-box-orient': 'vertical',
249 |
250 | margin: 0,
251 | padding: '0 24px',
252 |
253 | height: '32px',
254 |
255 | color,
256 | fontSize: '1.6rem',
257 |
258 | overflow: 'hidden',
259 | textOverflow: 'ellipsis',
260 | textAlign: 'left',
261 | lineHeight: '32px',
262 |
263 | cursor: 'pointer',
264 |
265 | transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',
266 | },
267 |
268 | hr: {
269 | margin: '8px 0',
270 | height: '1px',
271 | border: 'none',
272 | backgroundColor: border_color,
273 | },
274 |
275 | disable: {
276 | color: disable_color,
277 | cursor: 'not-allowed',
278 | },
279 |
280 | };
281 | return styles;
282 | }
283 |
284 | /**
285 | * ListItem react stateless component
286 | *
287 | * @param {object} props, include:
288 | * - idx : [PropTypes.number] index
289 | * - url : [PropTypes.string] url
290 | * - title : [PropTypes.string] title
291 | * - desc : [PropTypes.string] subtitle
292 | *
293 | * - style : [PropTypes.object] style
294 | *
295 | * - priType : [PropTypes.string] primary type, include: text, icon, avatar, action, none
296 | * - secType : [PropTypes.string] Secondary type, include: text, icon, avatar, action, none
297 | * - priValue : [PropTypes.any] primary value, value tyupe include: string, any
298 | * - secValue : [PropTypes.any] Secondary value, value tyupe include: string, any
299 | *
300 | * - events : [PropTypes.object] events, include:
301 | * - priOnClick : [PropTypes.func] list item primary onClick event
302 | * - secOnClick : [PropTypes.func] list item secondary onClick event
303 | *
304 | * - iconOnClick : [PropTypes.func] action icon onClick event
305 | * - bgOnClick : [PropTypes.func] action bg onClick event
306 | * - itemOnClick : [PropTypes.func] action item onClick event
307 | * - itemMouseOver : [PropTypes.func] action item mouse over event
308 | *
309 | * - action : [PropTypes.array] include: id, title, icon, disable, hr
310 | */
311 | const ListItem = props => {
312 | const { idx, url, title, desc, style,
313 | acIconWaves, acItemWaves,
314 | priType, priValue, secType, secValue,
315 | action, events } = props,
316 | content_style = props.contentStyle ? { ...props.contentStyle } : { ...style.content };
317 |
318 | let pri_style = priType == "text" ? { ...style[ `pri_item_${ priType }` ] } : { ...style[ `state_${ priType }` ] },
319 | sec_style = secType == "text" ? { ...style[ `sec_item_${ secType }` ] } : { ...style[ `state_${ secType }` ] },
320 | pri_value = [ "avatar", "icon" ].includes( priType ) ? "" : priValue,
321 | sec_value = [ "avatar", "icon" ].includes( secType ) ? "" : secValue;
322 |
323 | props.priStyle && ( pri_style = { ...props.priStyle } );
324 | props.secStyle && ( sec_style = { ...props.secStyle } );
325 |
326 | [ "avatar", "icon" ].includes( priType ) && ( pri_style.backgroundImage = `url(${priValue})` );
327 | [ "avatar", "icon" ].includes( secType ) && ( sec_style.backgroundImage = `url(${secValue})` );
328 | priType == "none" && ( pri_style = { ...pri_style, ...style.state_none } );
329 | secType == "none" && ( sec_style = { ...sec_style, ...style.state_none } );
330 |
331 | props.priBgColor && ( pri_style.backgroundColor = props.priBgColor );
332 | props.secBgColor && ( sec_style.backgroundColor = props.secBgColor );
333 |
334 | const actionItems = action ? action.map( item => {
335 | const { id, title, disable, hr } = item,
336 | root = disable ? { ...style.action_item, ...style.disable } : { ...style.action_item };
337 | return
338 | events.itemOnClick( event, props )) }
340 | onMouseOver={ ()=>events.itemMouseOver() }>
341 | { title }
342 |
343 | { hr &&
}
344 |
345 | }) : undefined;
346 | return (
347 |
348 | events.priOnClick( event, props ) }>{ pri_value }
349 |
350 | { title }
351 | { desc }
352 |
353 | events.secOnClick( event, props ) }>{ sec_value }
354 |
355 | events.iconOnClick() }>
356 |
357 | { actionItems }
358 |
359 | events.bgOnClick() }>
360 |
361 |
362 | );
363 | }
364 |
365 | /**
366 | * Custom tag component: List, component e.g.
367 | *
368 |
369 | 未读列表:100 条
370 |
371 | 换
372 |
373 | 换壳为本?Nokia 6 银白色版 1499 元起正式开卖
374 | 4 月 4 日的时候,诺基亚官方宣传,将于在 4 月 11 日正式发售全新配色的 Nokia 6 智能手机,即银白色版本,并且从那时起已经正式提供预约服务4 月 4 日的时候,诺基亚官方宣传,将于在 4 月 11 日正式发售全新配色的 Nokia 6 智能手机,即银白色版本,并且从那时起已经正式提供预约服务
375 |
376 | 2 days
377 |
378 |
379 |
380 |
381 | 发送到 Pocket
382 |
383 |
384 |
385 | 删除
386 |
387 |
388 |
389 |
390 |
391 |
392 | *
393 | * Reference:
394 | * - https://material.io/guidelines/components/lists.html
395 | * - http://www.material-ui.com/#/components/list
396 | * - chrome://history
397 | *
398 | * @class
399 | */
400 | export default class List extends React.Component {
401 |
402 | static defaultProps = {
403 | title : "",
404 |
405 | items : [],
406 | actionItems : [],
407 |
408 | acIconWaves : "",
409 | acItemWaves : "",
410 |
411 | priStyle : undefined,
412 | secStyle : undefined,
413 | contentStyle: undefined,
414 |
415 | priBgColor : "",
416 | secBgColor : "",
417 |
418 | };
419 |
420 | static PropTypes = {
421 | title : React.PropTypes.string,
422 |
423 | items : React.PropTypes.array,
424 | actionItems : React.PropTypes.array,
425 |
426 | onAction : React.PropTypes.func,
427 |
428 | acIconWaves : React.PropTypes.string,
429 | acItemWaves : React.PropTypes.string,
430 |
431 | priStyle : React.PropTypes.object,
432 | secStyle : React.PropTypes.object,
433 | contentStyle: React.PropTypes.object,
434 |
435 | priBgColor : React.PropTypes.string,
436 | secBgColor : React.PropTypes.string,
437 | };
438 |
439 | state = {
440 | id : Math.round(+new Date()),
441 | };
442 |
443 | acIconOnClick() {
444 | const style = styles.get( this.state.id );
445 | $( event.target ).next().css({ ...style.action_items, ...style.action_items_active });
446 | $( event.target ).parent().find( "action-bg" ).css( "display", "block" );
447 | }
448 |
449 | acBgOnClick() {
450 | const style = styles.get( this.state.id );
451 | $( event.target )
452 | .css( "display", "none" )
453 | .prev().css({ ...style.action_items });
454 | }
455 |
456 | acItemOnClick( event, data ) {
457 | const $target = $( event.target ),
458 | id = $target.attr( "id" ),
459 | title = $target.text(),
460 | style = styles.get( this.state.id );
461 | $target.parent().parent()
462 | .css({ ...style.action_items })
463 | .next().css( "display", "none" );
464 | this.props.onAction && this.props.onAction( event, id, title, data )
465 | }
466 |
467 | acItemMouseOver() {
468 | const $target = $( event.target );
469 | if ( $target.is( "action-item" ) ) {
470 | $( "action-item[active=true]" ).css( "background-color", "transparent" ).attr( "active", false );
471 | $target
472 | .attr( "active", true )
473 | .css( "background-color", "rgb(238, 238, 238)" );
474 | }
475 | }
476 |
477 | priOnClick( event, data ) {
478 | console.log( "priOnClick", event, data )
479 | this.props.priOnClick && this.props.priOnClick( event, data );
480 | }
481 |
482 | secOnClick( event, data ) {
483 | console.log( "secOnClick", event, data )
484 | this.props.secOnClick && this.props.secOnClick( event, data );
485 | }
486 |
487 | render() {
488 | const style = { ...cssinjs() };
489 | styles.set( this.state.id, style );
490 |
491 | const { items, title, actionItems, ...others } = this.props,
492 | list = items.map( item => {
493 | const events = {
494 | iconOnClick : () => this.acIconOnClick(),
495 | bgOnClick : () => this.acBgOnClick(),
496 | itemOnClick : ( e, d ) => this.acItemOnClick( e, d ),
497 | itemMouseOver: () => this.acItemMouseOver(),
498 | priOnClick : ( e, d ) => this.priOnClick( e, d ),
499 | secOnClick : ( e, d ) => this.secOnClick( e, d ),
500 | };
501 | return
502 | });
503 | return (
504 |
505 | { title }
506 | { list }
507 |
508 | )
509 | }
510 | }
--------------------------------------------------------------------------------