| 1 |
/** |
| 2 |
* BLOCK: codoc-block |
| 3 |
* |
| 4 |
* Registering a basic block with Gutenberg. |
| 5 |
* Simple block, renders and saves the same content without any interactivity. |
| 6 |
*/ |
| 7 |
|
| 8 |
// Import CSS. |
| 9 |
import './style.scss'; |
| 10 |
import './editor.scss'; |
| 11 |
import icon from './icon'; |
| 12 |
//import { ENTER } from '@wordpress/keycodes'; |
| 13 |
const { ENTER } = wp.keycodes; |
| 14 |
const { __ } = wp.i18n; // Import __() from wp.i18n |
| 15 |
const { Component } = wp.element; |
| 16 |
|
| 17 |
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks |
| 18 |
const { |
| 19 |
InspectorControls, |
| 20 |
RichText |
| 21 |
} = wp.editor; |
| 22 |
|
| 23 |
const { |
| 24 |
SelectControl, |
| 25 |
PanelBody, |
| 26 |
ToggleControl, |
| 27 |
CheckboxControl, |
| 28 |
RangeControl, |
| 29 |
TextControl, |
| 30 |
} = wp.components; |
| 31 |
|
| 32 |
const { |
| 33 |
getDefaultBlockName, |
| 34 |
createBlock, |
| 35 |
} = wp.blocks; |
| 36 |
|
| 37 |
const { withState } = wp.compose; |
| 38 |
|
| 39 |
const CODOC_URL = OPTIONS.codoc_url; |
| 40 |
const CODOC_USER_CODE = OPTIONS.codoc_usercode; |
| 41 |
const CODOC_PLUGIN_VERSION = OPTIONS.codoc_plugin_version; |
| 42 |
const CODOC_ENTRIES_CLASS = 'codoc-entries'; |
| 43 |
const CODOC_ACCOUNT_IS_PRO = OPTIONS.codoc_account_is_pro; |
| 44 |
const CODOC_CURRENCY_CODE = OPTIONS.codoc_currency_code; |
| 45 |
const CODOC_CURRENCY_DECIMAL_PLACES = OPTIONS.codoc_currency_decimal_places; |
| 46 |
|
| 47 |
//import Cookies from 'universal-cookie'; |
| 48 |
//const cookies = new Cookies(); |
| 49 |
|
| 50 |
class CodocControls extends Component { |
| 51 |
constructor( props ) { |
| 52 |
super( ...arguments ); |
| 53 |
this.subscriptionsFetched = []; |
| 54 |
this.subscriptionSearchTerm = ''; |
| 55 |
this.state = { |
| 56 |
searchTerm: '', |
| 57 |
filteredSubscriptions: [] |
| 58 |
}; |
| 59 |
this.fetchSubscriptions(); |
| 60 |
this.handleSearchChange = this.handleSearchChange.bind(this); |
| 61 |
} |
| 62 |
|
| 63 |
fetchSubscriptions(searchTitle = '') { |
| 64 |
const { |
| 65 |
setAttributes, |
| 66 |
attributes: { |
| 67 |
fetching, |
| 68 |
} |
| 69 |
} = this.props; |
| 70 |
|
| 71 |
if (fetching) { |
| 72 |
return |
| 73 |
} |
| 74 |
|
| 75 |
setAttributes( { fetching: true } ) |
| 76 |
let url = CODOC_URL + '/api/v1/cms/' + CODOC_USER_CODE + '/subscriptions?without_token=1'; |
| 77 |
|
| 78 |
// Add title parameter if search term exists |
| 79 |
if (searchTitle) { |
| 80 |
url += '&title=' + encodeURIComponent(searchTitle); |
| 81 |
} |
| 82 |
|
| 83 |
fetch(url) |
| 84 |
.then( res => res.json() ) |
| 85 |
.then( res => { |
| 86 |
let list = []; |
| 87 |
if (res.status && res.subscriptions) { |
| 88 |
for ( var i = 0; i < res.subscriptions.length; i++ ) { |
| 89 |
let info = { |
| 90 |
value: res.subscriptions[i].code, |
| 91 |
label: res.subscriptions[i].title, |
| 92 |
term: res.subscriptions[i].term, |
| 93 |
price: res.subscriptions[i].price, |
| 94 |
currency: res.subscriptions[i].currency |
| 95 |
} |
| 96 |
list[i] = info |
| 97 |
} |
| 98 |
} |
| 99 |
if (list.length || searchTitle) { |
| 100 |
this.subscriptionsFetched = list; |
| 101 |
this.setState({ filteredSubscriptions: list }); |
| 102 |
} |
| 103 |
setAttributes( { fetching: false } ) |
| 104 |
}) |
| 105 |
} |
| 106 |
|
| 107 |
handleSearchChange(value) { |
| 108 |
this.setState({ searchTerm: value }); |
| 109 |
|
| 110 |
// Debounce the API call |
| 111 |
clearTimeout(this.searchTimeout); |
| 112 |
this.searchTimeout = setTimeout(() => { |
| 113 |
this.fetchSubscriptions(value); |
| 114 |
}, 500); |
| 115 |
} |
| 116 |
|
| 117 |
getShowPriceHelp( checked ) { |
| 118 |
return checked ? |
| 119 |
__( 'Enable','codoc' ) : |
| 120 |
__( 'Disable','codoc' ); |
| 121 |
} |
| 122 |
getShowSupportHelp( checked ) { |
| 123 |
return checked ? |
| 124 |
__( 'Accept' ,'codoc') : |
| 125 |
__( 'Do not accept' ,'codoc'); |
| 126 |
} |
| 127 |
getShowPaywalledSupportHelp( checked ) { |
| 128 |
return checked ? |
| 129 |
__( 'The paid part will be hidden upon access, but will be displayed after tapping "Pay to Read". Viewers can freely specify the amount to purchase based on the content. Please note that there may be cases where it is not purchased.' ,'codoc') : |
| 130 |
__( ' ' ); |
| 131 |
} |
| 132 |
getStatusLimitedHelp( checked ) { |
| 133 |
return checked ? |
| 134 |
__( 'Unlisted' ,'codoc') : |
| 135 |
__( 'Published' ,'codoc'); |
| 136 |
} |
| 137 |
|
| 138 |
render() { |
| 139 |
const { |
| 140 |
setAttributes, |
| 141 |
attributes: { |
| 142 |
showPrice, |
| 143 |
price, |
| 144 |
limited, |
| 145 |
limitedCount, |
| 146 |
|
| 147 |
affiliateMode, |
| 148 |
affiliateRate, |
| 149 |
|
| 150 |
showSupport, |
| 151 |
showPaywalledSupport, |
| 152 |
statusLimited, |
| 153 |
subscriptions, |
| 154 |
} |
| 155 |
} = this.props; |
| 156 |
|
| 157 |
const toggleShowPrice = () => setAttributes( { showPrice: ! showPrice } ); |
| 158 |
const toggleLimited = () => setAttributes( { limited: ! limited } ); |
| 159 |
const toggleShowPaywalledSupport = () => setAttributes( { showPaywalledSupport: ! showPaywalledSupport } ); |
| 160 |
const toggleStatusLimited = () => setAttributes( { statusLimited: ! statusLimited } ); |
| 161 |
const toggleAffiliateMode = () => setAttributes( { affiliateMode: ! affiliateMode } ); |
| 162 |
const toggleShowSupport = () => setAttributes( { showSupport: ! showSupport } ); |
| 163 |
|
| 164 |
const SubscriptionCheckBoxes = withState({ |
| 165 |
checked_obj: Object.assign({}, subscriptions) |
| 166 |
})( ({ checked_obj, setState }) => ( |
| 167 |
<div> |
| 168 |
{this.state.filteredSubscriptions.length === 0 ? ( |
| 169 |
<p style={{ fontStyle: 'italic', color: '#666' }}> |
| 170 |
{ this.state.searchTerm |
| 171 |
? __('No plans found matching your search.','codoc') |
| 172 |
: __('No reader plans available.','codoc') |
| 173 |
} |
| 174 |
</p> |
| 175 |
) : ( |
| 176 |
<ul> |
| 177 |
{ |
| 178 |
this.state.filteredSubscriptions.map((v) => { |
| 179 |
const isChecked = !!checked_obj[v.value]; |
| 180 |
const checkedCount = Object.keys(checked_obj).length; |
| 181 |
|
| 182 |
return ( |
| 183 |
<li key={v.value}> |
| 184 |
<CheckboxControl |
| 185 |
className="check_items" |
| 186 |
label={v.label} |
| 187 |
checked={isChecked} |
| 188 |
onChange={(check) => { |
| 189 |
// 追加する場合、5個以上は無効 |
| 190 |
if (check && checkedCount >= 5) { |
| 191 |
return; // これ以上チェックできない |
| 192 |
} |
| 193 |
|
| 194 |
const newChecked = { ...checked_obj }; |
| 195 |
|
| 196 |
if (check) { |
| 197 |
newChecked[v.value] = true; |
| 198 |
} else { |
| 199 |
delete newChecked[v.value]; |
| 200 |
} |
| 201 |
|
| 202 |
setAttributes({ subscriptions: newChecked }); |
| 203 |
setState({ checked_obj: newChecked }); |
| 204 |
}} |
| 205 |
/> |
| 206 |
</li> |
| 207 |
); |
| 208 |
}) |
| 209 |
} |
| 210 |
</ul> |
| 211 |
)} |
| 212 |
</div> |
| 213 |
) ) |
| 214 |
const affiliateRateOptions = [ |
| 215 |
{ value: '0.0500', label:'5%' }, |
| 216 |
{ value: '0.1000', label:'10%' }, |
| 217 |
{ value: '0.1500', label:'15%' }, |
| 218 |
{ value: '0.2000', label:'20%' }, |
| 219 |
{ value: '0.2500', label:'25%' }, |
| 220 |
{ value: '0.3000', label:'30%' }, |
| 221 |
{ value: '0.3500', label:'35%' }, |
| 222 |
{ value: '0.4000', label:'40%' }, |
| 223 |
{ value: '0.4500', label:'45%' }, |
| 224 |
{ value: '0.5000', label:'50%' }, |
| 225 |
]; |
| 226 |
|
| 227 |
return( |
| 228 |
<InspectorControls key="CodocControls"> |
| 229 |
|
| 230 |
<PanelBody> |
| 231 |
|
| 232 |
<ToggleControl |
| 233 |
label={ __( 'individual sale','codoc' ) } |
| 234 |
checked={ !! showPrice } |
| 235 |
help={ this.getShowPriceHelp } |
| 236 |
ref="showPrice" |
| 237 |
onChange={ toggleShowPrice } |
| 238 |
/> |
| 239 |
|
| 240 |
<div style={{ display: showPrice ? 'initial' : 'none' }}> |
| 241 |
<RangeControl |
| 242 |
label={ __('price','codoc') + ' ' + (CODOC_CURRENCY_DECIMAL_PLACES > 0 ? (price / 100).toFixed(CODOC_CURRENCY_DECIMAL_PLACES) : price) + ' ' + __(CODOC_CURRENCY_CODE) + __(showPaywalledSupport ? '【' + __('Displayed as the your suggested price','codoc') + '】' : '' ) } |
| 243 |
value={ price } |
| 244 |
initialPosition={ price } |
| 245 |
onChange={ ( value ) => { |
| 246 |
// do validate this |
| 247 |
setAttributes({ price: value }); |
| 248 |
}} |
| 249 |
min="100" |
| 250 |
max={ CODOC_ACCOUNT_IS_PRO == 1 ? 100000 : 50000 } |
| 251 |
/><br /> |
| 252 |
|
| 253 |
<ToggleControl |
| 254 |
label={ __( 'Pay-what-you-want','codoc' ) } |
| 255 |
checked={ !! showPaywalledSupport } |
| 256 |
help={ this.getShowPaywalledSupportHelp } |
| 257 |
onChange={ toggleShowPaywalledSupport } |
| 258 |
/> |
| 259 |
|
| 260 |
<div style={{ display: showPaywalledSupport ? 'none' : 'initial' }}> |
| 261 |
<ToggleControl |
| 262 |
label={ __( 'Limited quantity sale','codoc' ) } |
| 263 |
checked={ !! limited } |
| 264 |
onChange={ toggleLimited } |
| 265 |
/> |
| 266 |
|
| 267 |
<div style={{ display: limited ? 'initial' : 'none' }}> |
| 268 |
<RangeControl |
| 269 |
label={ __( 'Limited quantity','codoc' ) } |
| 270 |
value={ limitedCount } |
| 271 |
onChange={ ( value ) => { |
| 272 |
// do validate this |
| 273 |
setAttributes({ limitedCount: value }); |
| 274 |
}} |
| 275 |
initialPosition={ limitedCount } |
| 276 |
min="0" |
| 277 |
max="100" |
| 278 |
/><br /> |
| 279 |
</div> |
| 280 |
|
| 281 |
<ToggleControl |
| 282 |
label={ __( 'Affiliate','codoc' ) } |
| 283 |
checked={ !! affiliateMode } |
| 284 |
onChange={ toggleAffiliateMode } |
| 285 |
/> |
| 286 |
|
| 287 |
<div style={{ display: affiliateMode ? 'initial' : 'none' }}> |
| 288 |
<SelectControl |
| 289 |
label={ __( 'Rate' , 'codoc') } |
| 290 |
description={ __( 'You can offer affiliate marketing at a specified rate for purchasers.', 'codoc') } |
| 291 |
options={ affiliateRateOptions } |
| 292 |
value={ affiliateRate } |
| 293 |
onChange={ ( value ) => { |
| 294 |
setAttributes( { affiliateRate: value } ); |
| 295 |
}}/><br /> |
| 296 |
</div> |
| 297 |
|
| 298 |
</div> |
| 299 |
</div> |
| 300 |
|
| 301 |
<div style={{ display: showPaywalledSupport ? 'none' : 'initial' }}> |
| 302 |
<ToggleControl |
| 303 |
label={ __( 'Tipping', 'codoc') } |
| 304 |
checked={ !! showSupport } |
| 305 |
value="1" |
| 306 |
onChange={ toggleShowSupport } |
| 307 |
help={ this.getShowSupportHelp } |
| 308 |
/> |
| 309 |
</div> |
| 310 |
|
| 311 |
<div style={{ display: 'initial' }}> |
| 312 |
<ToggleControl |
| 313 |
label={ __( 'Unlisted','codoc' ) } |
| 314 |
checked={ !! statusLimited } |
| 315 |
value="1" |
| 316 |
onChange={ toggleStatusLimited } |
| 317 |
help={ this.getStatusLimitedHelp } |
| 318 |
/> |
| 319 |
</div> |
| 320 |
|
| 321 |
<div class="codoc-subscription-title"> |
| 322 |
<label> |
| 323 |
{ __('Reader Plans','codoc') } |
| 324 |
</label> |
| 325 |
<a |
| 326 |
href={ CODOC_URL + '/me/subscriptions'} |
| 327 |
target="_blank" class="codoc-subscription-add" |
| 328 |
> |
| 329 |
{ __('Add','codoc') } |
| 330 |
</a> |
| 331 |
</div> |
| 332 |
|
| 333 |
<TextControl |
| 334 |
label={ __('Search Plans','codoc') } |
| 335 |
value={ this.state.searchTerm } |
| 336 |
onChange={ this.handleSearchChange } |
| 337 |
placeholder={ __('Enter title to search...','codoc') } |
| 338 |
/> |
| 339 |
|
| 340 |
<SubscriptionCheckBoxes /> |
| 341 |
|
| 342 |
|
| 343 |
</PanelBody> |
| 344 |
</InspectorControls> |
| 345 |
); |
| 346 |
} |
| 347 |
}; |
| 348 |
|
| 349 |
class EditBlockContent extends Component { |
| 350 |
constructor() { |
| 351 |
super( ...arguments ); |
| 352 |
// デフォルト値 |
| 353 |
const bd = OPTIONS.codoc_block_defaults || {}; |
| 354 |
const d = (key, fallback) => bd.hasOwnProperty(key) ? bd[key] : fallback; |
| 355 |
this.props.setAttributes({ version: CODOC_PLUGIN_VERSION }); |
| 356 |
this.props.setAttributes({ showPrice: this.props.attributes.showPrice == null ? d('showPrice', true) : this.props.attributes.showPrice }); |
| 357 |
this.props.setAttributes({ price: this.props.attributes.price == null ? d('price', 500) : this.props.attributes.price }); |
| 358 |
this.props.setAttributes({ limited: this.props.attributes.limited == null ? d('limited', false) : this.props.attributes.limited }); |
| 359 |
this.props.setAttributes({ limitedCount: this.props.attributes.limitedCount == null ? d('limitedCount', 10) : this.props.attributes.limitedCount }); |
| 360 |
this.props.setAttributes({ affiliateMode: this.props.attributes.affiliateMode == null ? d('affiliateMode', false) : this.props.attributes.affiliateMode }); |
| 361 |
this.props.setAttributes({ affiliateRate: this.props.attributes.affiliateRate == null ? d('affiliateRate', '0.0500') : this.props.attributes.affiliateRate }); |
| 362 |
this.props.setAttributes({ showSupport: this.props.attributes.showSupport == null ? d('showSupport', false) : this.props.attributes.showSupport }); |
| 363 |
this.props.setAttributes({ showPaywalledSupport: this.props.attributes.showPaywalledSupport == null ? d('showPaywalledSupport', false) : this.props.attributes.showPaywalledSupport }); |
| 364 |
this.props.setAttributes({ statusLimited: this.props.attributes.statusLimited == null ? d('statusLimited', false) : this.props.attributes.statusLimited }); |
| 365 |
this.props.setAttributes({ subscriptions: this.props.attributes.subscriptions == null ? d('subscriptions', {}) : this.props.attributes.subscriptions }); |
| 366 |
|
| 367 |
this.onChangeInput = this.onChangeInput.bind( this ); |
| 368 |
this.onKeyDown = this.onKeyDown.bind( this ); |
| 369 |
this.state = { |
| 370 |
defaultText: __( 'To continue reading, ...','codoc' ), |
| 371 |
}; |
| 372 |
} |
| 373 |
onChangeInput( event ) { |
| 374 |
this.setState( { |
| 375 |
defaultText: '', |
| 376 |
} ); |
| 377 |
|
| 378 |
const value = event.target.value.length === 0 ? undefined : event.target.value; |
| 379 |
this.props.setAttributes( { customText: value } ); |
| 380 |
} |
| 381 |
onKeyDown( event ) { |
| 382 |
const { keyCode } = event; |
| 383 |
const { insertBlocksAfter } = this.props; |
| 384 |
if ( keyCode === ENTER ) { |
| 385 |
insertBlocksAfter( [ createBlock( getDefaultBlockName() ) ] ); |
| 386 |
} |
| 387 |
} |
| 388 |
|
| 389 |
render() { |
| 390 |
const { |
| 391 |
attributes: { |
| 392 |
customText, |
| 393 |
}, |
| 394 |
setAttributes |
| 395 |
} = this.props; |
| 396 |
|
| 397 |
const { defaultText } = this.state; |
| 398 |
//const value = customText !== undefined ? customText : defaultText; |
| 399 |
const value = customText ? customText : defaultText; |
| 400 |
//const value = defaultText; |
| 401 |
const inputLength = value.length + 10; |
| 402 |
|
| 403 |
return[ |
| 404 |
<CodocControls { ...{setAttributes, ...this.props } } />, |
| 405 |
|
| 406 |
<div className="wp-block-more"> |
| 407 |
<input |
| 408 |
type="text" |
| 409 |
value={ value } |
| 410 |
size={ inputLength } |
| 411 |
onChange={ this.onChangeInput } |
| 412 |
onKeyDown={ this.onKeyDown } |
| 413 |
/> |
| 414 |
</div> |
| 415 |
|
| 416 |
]; |
| 417 |
} |
| 418 |
}; |
| 419 |
|
| 420 |
|
| 421 |
registerBlockType( 'codoc/codoc-block', { |
| 422 |
title: __( 'codoc' ,'codoc'), |
| 423 |
description: __( 'The area from this block down is a paid area that only authenticated codoc users can view.','codoc' ), |
| 424 |
icon, |
| 425 |
category: 'layout', |
| 426 |
supports: { |
| 427 |
customClassName: false, |
| 428 |
className: false, |
| 429 |
html: false, |
| 430 |
multiple: false, |
| 431 |
}, |
| 432 |
keywords: [ |
| 433 |
__( 'codoc Block' ,'codoc'), |
| 434 |
__( 'plugin for codoc' ,'codoc'), |
| 435 |
__( 'codoc-block' ,'codoc'), |
| 436 |
], |
| 437 |
attributes: { |
| 438 |
showPrice: { |
| 439 |
type: 'boolean', |
| 440 |
default: null, |
| 441 |
}, |
| 442 |
price: { |
| 443 |
type: 'number', |
| 444 |
default: null, |
| 445 |
}, |
| 446 |
limited: { |
| 447 |
type: 'boolean', |
| 448 |
default: null, |
| 449 |
}, |
| 450 |
limitedCount: { |
| 451 |
type: 'number', |
| 452 |
default: null, |
| 453 |
}, |
| 454 |
affiliateMode: { |
| 455 |
type: 'boolean', |
| 456 |
default: null, |
| 457 |
}, |
| 458 |
affiliateRate: { |
| 459 |
type: 'string', |
| 460 |
default: null, |
| 461 |
}, |
| 462 |
showSupport: { |
| 463 |
type: 'boolean', |
| 464 |
default: null, |
| 465 |
}, |
| 466 |
showPaywalledSupport: { |
| 467 |
type: 'boolean', |
| 468 |
default: null, |
| 469 |
}, |
| 470 |
statusLimited: { |
| 471 |
type: 'boolean', |
| 472 |
default: null, |
| 473 |
}, |
| 474 |
subscriptions: { |
| 475 |
type: 'object', |
| 476 |
default: null, |
| 477 |
}, |
| 478 |
customText: { |
| 479 |
type: 'string', |
| 480 |
default: '', |
| 481 |
}, |
| 482 |
version: { |
| 483 |
type: 'string', |
| 484 |
default: null, |
| 485 |
}, |
| 486 |
}, |
| 487 |
|
| 488 |
edit: EditBlockContent, |
| 489 |
|
| 490 |
save: function( props ) { |
| 491 |
const { |
| 492 |
setAttributes, |
| 493 |
attributes: { |
| 494 |
customText, |
| 495 |
} |
| 496 |
} = props; |
| 497 |
return ( // return if link behavior normal |
| 498 |
<div |
| 499 |
data-id='codoc-tag' |
| 500 |
className={ CODOC_ENTRIES_CLASS } |
| 501 |
> |
| 502 |
{ customText && !! customText.length && ( |
| 503 |
<RichText.Content |
| 504 |
value={ customText } |
| 505 |
/> |
| 506 |
)} |
| 507 |
</div> |
| 508 |
) |
| 509 |
}, |
| 510 |
} ); |
| 511 |
|