1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20/** 21 * @fileOverview 22 * ViewModel template parser & data-binding process 23 */ 24 25import { 26 hasOwn, 27 Log, 28 removeItem 29} from '../../utils/index'; 30import { 31 initData, 32 initComputed 33} from '../reactivity/state'; 34import { 35 bindElement, 36 setClass, 37 setIdStyle, 38 setTagStyle, 39 setId, 40 bindSubVm, 41 bindSubVmAfterInitialized, 42 newWatch 43} from './directive'; 44import { 45 createBlock, 46 createBody, 47 createElement, 48 attachTarget, 49 moveTarget, 50 removeTarget 51} from './domHelper'; 52import { 53 bindPageLifeCycle 54} from './pageLife'; 55import Vm from './index'; 56import Element from '../../vdom/Element'; 57import Comment from '../../vdom/Comment'; 58import Node from '../../vdom/Node'; 59import { VmOptions } from './vmOptions'; 60 61export interface FragBlockInterface { 62 start: Comment; 63 end: Comment; 64 element?: Element; 65 blockId: number; 66 children?: any[]; 67 data?: any[]; 68 vms?: Vm[]; 69 updateMark?: Node; 70 display?: boolean; 71 type?: string; 72 vm?: Vm; 73} 74 75export interface AttrInterface { 76 type: string; 77 value: () => void | string; 78 tid: number; 79 append: string; 80 slot: string; 81 name: string; 82 data: () => any | string; 83} 84 85export interface TemplateInterface { 86 type: string; 87 attr: Partial<AttrInterface>; 88 classList?: () => any | string[]; 89 children?: TemplateInterface[]; 90 events?: object; 91 repeat?: () => any | RepeatInterface; 92 shown?: () => any; 93 style?: Record<string, string>; 94 id?: () => any | string; 95 append?: string; 96 onBubbleEvents?: object; 97 onCaptureEvents?: object; 98 catchBubbleEvents?: object; 99 catchCaptureEvents?: object; 100} 101 102interface RepeatInterface { 103 exp: () => any; 104 key?: string; 105 value?: string; 106 tid?: number; 107} 108 109interface MetaInterface { 110 repeat: object; 111 shown: boolean; 112 type: string; 113} 114 115interface ConfigInterface { 116 latestValue: undefined | string | number; 117 recorded: boolean; 118} 119 120export function build(vm: Vm) { 121 const opt: any = vm.vmOptions || {}; 122 const template: any = opt.template || {}; 123 compile(vm, template, vm.parentEl); 124 Log.debug(`"OnReady" lifecycle in Vm(${vm.type}).`); 125 vm.$emit('hook:onReady'); 126 if (vm.parent) { 127 vm.$emit('hook:onAttached'); 128 } 129 vm.ready = true; 130} 131 132/** 133 * Compile the Virtual Dom. 134 * @param {Vm} vm - Vm object needs to be compiled. 135 * @param {TemplateInterface} target - Node need to be compiled. Structure of the label in the template. 136 * @param {FragBlockInterface | Element} dest - Parent Node's VM of current. 137 * @param {MetaInterface} [meta] - To transfer data. 138 */ 139function compile(vm: Vm, target: TemplateInterface, dest: FragBlockInterface | Element, meta?: Partial<MetaInterface>): void { 140 const app: any = vm.app || {}; 141 if (app.lastSignal === -1) { 142 return; 143 } 144 meta = meta || {}; 145 if (targetIsSlot(target)) { 146 compileSlot(vm, target, dest as Element); 147 return; 148 } 149 150 if (targetNeedCheckRepeat(target, meta)) { 151 if (dest.type === 'document') { 152 Log.warn('The root element does\'t support `repeat` directive!'); 153 } else { 154 compileRepeat(vm, target, dest as Element); 155 } 156 return; 157 } 158 if (targetNeedCheckShown(target, meta)) { 159 if (dest.type === 'document') { 160 Log.warn('The root element does\'t support `if` directive!'); 161 } else { 162 compileShown(vm, target, dest, meta); 163 } 164 return; 165 } 166 const type = meta.type || target.type; 167 const component: VmOptions | null = targetIsComposed(vm, type); 168 if (component) { 169 compileCustomComponent(vm, component, target, dest, type, meta); 170 return; 171 } 172 if (targetIsBlock(target)) { 173 compileBlock(vm, target, dest); 174 return; 175 } 176 compileNativeComponent(vm, target, dest, type); 177} 178 179/** 180 * Check if target type is slot. 181 * 182 * @param {object} target 183 * @return {boolean} 184 */ 185function targetIsSlot(target: TemplateInterface) { 186 return target.type === 'slot'; 187} 188 189/** 190 * Check if target needs to compile by a list. 191 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 192 * @param {MetaInterface} meta - To transfer data. 193 * @return {boolean} - True if target needs repeat. Otherwise return false. 194 */ 195function targetNeedCheckRepeat(target: TemplateInterface, meta: Partial<MetaInterface>) { 196 return !hasOwn(meta, 'repeat') && target.repeat; 197} 198 199/** 200 * Check if target needs to compile by a 'if' or 'shown' value. 201 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 202 * @param {MetaInterface} meta - To transfer data. 203 * @return {boolean} - True if target needs a 'shown' value. Otherwise return false. 204 */ 205function targetNeedCheckShown(target: TemplateInterface, meta: Partial<MetaInterface>) { 206 return !hasOwn(meta, 'shown') && target.shown; 207} 208 209/** 210 * Check if this kind of component is composed. 211 * @param {Vm} vm - Vm object needs to be compiled. 212 * @param {string} type - Component type. 213 * @return {VmOptions} Component. 214 */ 215function targetIsComposed(vm: Vm, type: string): VmOptions { 216 let component; 217 if (vm.app && vm.app.customComponentMap) { 218 component = vm.app.customComponentMap[type]; 219 } 220 if (component) { 221 if (component.data && typeof component.data === 'object') { 222 if (!component.initObjectData) { 223 component.initObjectData = component.data; 224 } 225 component.data = Object.assign({}, component.initObjectData); 226 } 227 } 228 return component; 229} 230 231/** 232 * Compile a target with repeat directive. 233 * @param {Vm} vm - Vm object needs to be compiled. 234 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 235 * @param {dest} dest - Node need to be appended. 236 */ 237function compileSlot(vm: Vm, target: TemplateInterface, dest: Element): Element { 238 if (!vm.slotContext) { 239 // slot in root vm 240 return; 241 } 242 243 const slotDest = createBlock(vm, dest); 244 245 // reslove slot contentext 246 const namedContents = vm.slotContext.content; 247 const parentVm = vm.slotContext.parentVm; 248 const slotItem = { target, dest: slotDest }; 249 const slotName = target.attr.name || 'default'; 250 251 // acquire content by name 252 const namedContent = namedContents[slotName]; 253 if (!namedContent) { 254 compileChildren(vm, slotItem.target, slotItem.dest); 255 } else { 256 compileChildren(parentVm, { children: namedContent }, slotItem.dest); 257 } 258} 259 260/** 261 * Compile a target with repeat directive. 262 * @param {Vm} vm - Vm object needs to be compiled. 263 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 264 * @param {Element} dest - Parent Node's VM of current. 265 */ 266function compileRepeat(vm: Vm, target: TemplateInterface, dest: Element): void { 267 const repeat = target.repeat; 268 let getter: any; 269 let key: any; 270 let value: any; 271 let trackBy: any; 272 273 if (isRepeat(repeat)) { 274 getter = repeat.exp; 275 key = repeat.key || '$idx'; 276 value = repeat.value; 277 trackBy = repeat.tid; 278 } else { 279 getter = repeat; 280 key = '$idx'; 281 value = '$item'; 282 trackBy = target.attr && target.attr.tid; 283 } 284 if (typeof getter !== 'function') { 285 getter = function() { 286 return []; 287 }; 288 } 289 const fragBlock: FragBlockInterface = createBlock(vm, dest); 290 fragBlock.children = []; 291 fragBlock.data = []; 292 fragBlock.vms = []; 293 bindRepeat(vm, target, fragBlock, { getter, key, value, trackBy }); 294} 295 296/** 297 * Compile a target with 'if' directive. 298 * @param {Vm} vm - Vm object needs to be compiled. 299 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 300 * @param {FragBlockInterface | Element} dest - Parent Node's VM of current. 301 * @param {MetaInterface} meta - To transfer data. 302 */ 303function compileShown( 304 vm: Vm, 305 target: TemplateInterface, 306 dest: Element | FragBlockInterface, 307 meta: Partial<MetaInterface> 308): void { 309 const newMeta: Partial<MetaInterface> = { shown: true }; 310 const fragBlock = createBlock(vm, dest); 311 if (isBlock(dest) && dest.children) { 312 dest.children.push(fragBlock); 313 } 314 if (hasOwn(meta, 'repeat')) { 315 newMeta.repeat = meta.repeat; 316 } 317 bindShown(vm, target, fragBlock, newMeta); 318} 319 320/** 321 * Support <block>. 322 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 323 * @return {boolean} True if target supports bolck. Otherwise return false. 324 */ 325function targetIsBlock(target: TemplateInterface): boolean { 326 return target.type === 'block'; 327} 328 329/** 330 * If <block> create block and compile the children node. 331 * @param {Vm} vm - Vm object needs to be compiled. 332 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 333 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current. 334 */ 335function compileBlock(vm: Vm, target: TemplateInterface, dest: Element | FragBlockInterface): void { 336 const block = createBlock(vm, dest); 337 if (isBlock(dest) && dest.children) { 338 dest.children.push(block); 339 } 340 const app: any = vm.app || {}; 341 const children = target.children; 342 if (children && children.length) { 343 children.every((child) => { 344 compile(vm, child, block); 345 return app.lastSignal !== -1; 346 }); 347 } 348} 349 350/** 351 * Compile a composed component. 352 * @param {Vm} vm - Vm object needs to be compiled. 353 * @param {VmOptions} component - Composed component. 354 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 355 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current. 356 * @param {string} type - Component Type. 357 * @param {MetaInterface} meta - To transfer data. 358 */ 359function compileCustomComponent( 360 vm: Vm, 361 component: VmOptions, 362 target: TemplateInterface, 363 dest: Element | FragBlockInterface, 364 type: string, 365 meta: Partial<MetaInterface> 366): void { 367 const subVm = new Vm( 368 type, 369 component, 370 vm, 371 dest, 372 undefined, 373 { 374 'hook:_innerInit': function() { 375 // acquire slot content of context 376 const namedContents = {}; 377 if (target.children) { 378 target.children.forEach(item => { 379 const slotName = item.attr.slot || 'default'; 380 if (namedContents[slotName]) { 381 namedContents[slotName].push(item); 382 } else { 383 namedContents[slotName] = [item]; 384 } 385 }); 386 } 387 this.slotContext = { content: namedContents, parentVm: vm }; 388 setId(vm, null, target.id, this); 389 390 // Bind template earlier because of lifecycle issues. 391 this.externalBinding = { 392 parent: vm, 393 template: target 394 }; 395 396 // Bind props before init data. 397 bindSubVm(vm, this, target, meta.repeat); 398 } 399 }); 400 bindSubVmAfterInitialized(vm, subVm, target, dest); 401} 402 403/** 404 * Reset the element style. 405 * @param {Vm} vm - Vm object needs to be compiled. 406 * @param {Element} element - To be reset. 407 */ 408function resetElementStyle(vm: Vm, element: Element): void { 409 // Add judgment statements to avoid repeatedly calling 'setClass' function. 410 let len = 0; 411 if (element.children !== undefined) { 412 len = element.children.length; 413 } 414 const css = vm.css || {}; 415 const mqArr = css['@MEDIA']; 416 for (let ii = 0; ii < len; ii++) { 417 const el = element.children[ii] as Element; 418 if (!el.isCustomComponent) { 419 resetElementStyle(vm, el); 420 } 421 } 422 if (element.type) { 423 setTagStyle(vm, element, element.type); 424 } 425 if (element.id) { 426 setIdStyle(vm, element, element.id); 427 } 428 if (element.classList && mqArr) { 429 for (let i = 0; i < element.classList.length; i++) { 430 for (let m = 0; m < mqArr.length; m++) { 431 const clsKey = '.' + element.classList[i]; 432 if (hasOwn(mqArr[m], clsKey)) { 433 setClass(vm, element, element.classList); 434 } 435 } 436 } 437 } 438} 439 440/** 441 * <p>Generate element from template and attach to the dest if needed.</p> 442 * <p>The time to attach depends on whether the mode status is node or tree.</p> 443 * @param {Vm} vm - Vm object needs to be compiled. 444 * @param {TemplateInterface} template - Generate element from template. 445 * @param {FragBlockInterface | Element} dest - Parent Node's VM of current. 446 * @param {string} type - Vm type. 447 */ 448function compileNativeComponent(vm: Vm, template: TemplateInterface, dest: FragBlockInterface | Element, type: string): void { 449 function handleViewSizeChanged(e) { 450 if (!vm.mediaStatus) { 451 vm.mediaStatus = {}; 452 } 453 vm.mediaStatus.orientation = e.orientation; 454 vm.mediaStatus.width = e.width; 455 vm.mediaStatus.height = e.height; 456 vm.mediaStatus.resolution = e.resolution; 457 vm.mediaStatus['device-type'] = e.deviceType; 458 vm.mediaStatus['aspect-ratio'] = e.aspectRatio; 459 vm.mediaStatus['device-width'] = e.deviceWidth; 460 vm.mediaStatus['device-height'] = e.deviceHeight; 461 vm.mediaStatus['round-screen'] = e.roundScreen; 462 vm.mediaStatus['dark-mode'] = e.darkMode; 463 const css = vm.vmOptions && vm.vmOptions.style || {}; 464 const mqArr = css['@MEDIA']; 465 if (!mqArr) { 466 return; 467 } 468 if (e.isInit && vm.init) { 469 return; 470 } 471 vm.init = true; 472 resetElementStyle(vm, e.currentTarget); 473 e.currentTarget.addEvent('show'); 474 } 475 476 let element; 477 if (!isBlock(dest) && dest.ref === '_documentElement') { 478 // If its parent is documentElement then it's a body. 479 element = createBody(vm, type); 480 } else { 481 element = createElement(vm, type); 482 element.destroyHook = function() { 483 if (element.block !== undefined) { 484 removeTarget(element.block); 485 } 486 if (element.watchers !== undefined) { 487 element.watchers.forEach(function(watcher) { 488 watcher.teardown(); 489 }); 490 element.watchers = []; 491 } 492 }; 493 } 494 495 if (!vm.rootEl) { 496 vm.rootEl = element; 497 498 // Bind event earlier because of lifecycle issues. 499 const binding: any = vm.externalBinding || {}; 500 const target = binding.template; 501 const parentVm = binding.parent; 502 if (target && target.events && parentVm && element) { 503 for (const type in target.events) { 504 const handler = parentVm[target.events[type]]; 505 if (handler) { 506 element.addEvent(type, handler.bind(parentVm)); 507 } 508 } 509 } 510 // Page show hide life circle hook function. 511 bindPageLifeCycle(vm, element); 512 element.setCustomFlag(); 513 element.customFlag = true; 514 vm.init = true; 515 element.addEvent('viewsizechanged', handleViewSizeChanged); 516 } 517 518 // Dest is parent element. 519 bindElement(vm, element, template, dest); 520 if (element.event && element.event['attached']) { 521 element.fireEvent('attached', {}); 522 } 523 524 if (template.attr && template.attr.append) { 525 template.append = template.attr.append; 526 } 527 if (template.append) { 528 element.attr = element.attr || {}; 529 element.attr.append = template.append; 530 } 531 let treeMode = template.append === 'tree'; 532 const app: any = vm.app || {}; 533 534 // Record the parent node of treeMode, used by class selector. 535 if (treeMode) { 536 if (!global.treeModeParentNode) { 537 global.treeModeParentNode = dest; 538 } else { 539 treeMode = false; 540 } 541 } 542 if (app.lastSignal !== -1 && !treeMode) { 543 app.lastSignal = attachTarget(element, dest); 544 } 545 if (app.lastSignal !== -1) { 546 compileChildren(vm, template, element); 547 } 548 if (app.lastSignal !== -1 && treeMode) { 549 delete global.treeModeParentNode; 550 app.lastSignal = attachTarget(element, dest); 551 } 552} 553 554/** 555 * Set all children to a certain parent element. 556 * @param {Vm} vm - Vm object needs to be compiled. 557 * @param {any} template - Generate element from template. 558 * @param {Element | FragBlockInterface} dest - Parent Node's VM of current. 559 * @return {void | boolean} If there is no children, return null. Return true if has node. 560 */ 561function compileChildren(vm: Vm, template: any, dest: Element | FragBlockInterface): void | boolean { 562 const app: any = vm.app || {}; 563 const children = template.children; 564 if (children && children.length) { 565 children.every((child) => { 566 compile(vm, child, dest); 567 return app.lastSignal !== -1; 568 }); 569 } 570} 571 572/** 573 * Watch the list update and refresh the changes. 574 * @param {Vm} vm - Vm object need to be compiled. 575 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 576 * @param {FragBlockInterface} fragBlock - {vms, data, children} 577 * @param {*} info - {getter, key, value, trackBy, oldStyle} 578 */ 579function bindRepeat(vm: Vm, target: TemplateInterface, fragBlock: FragBlockInterface, info: any): void { 580 const vms = fragBlock.vms; 581 const children = fragBlock.children; 582 const { getter, trackBy } = info; 583 const keyName = info.key; 584 const valueName = info.value; 585 586 function compileItem(item: any, index: number, context: Vm) { 587 const mergedData = {}; 588 mergedData[keyName] = index; 589 mergedData[valueName] = item; 590 const newContext = mergeContext(context, mergedData); 591 vms.push(newContext); 592 compile(newContext, target, fragBlock, { repeat: item }); 593 } 594 const list = watchBlock(vm, fragBlock, getter, 'repeat', 595 (data) => { 596 Log.debug(`The 'repeat' item has changed ${data}.`); 597 if (!fragBlock || !data) { 598 return; 599 } 600 const oldChildren = children.slice(); 601 const oldVms = vms.slice(); 602 const oldData = fragBlock.data.slice(); 603 604 // Collect all new refs track by. 605 const trackMap = {}; 606 const reusedMap = {}; 607 data.forEach((item, index) => { 608 const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index; 609 if (key === null || key === '') { 610 return; 611 } 612 trackMap[key] = item; 613 }); 614 615 // Remove unused element foreach old item. 616 const reusedList: any[] = []; 617 oldData.forEach((item, index) => { 618 const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index; 619 if (hasOwn(trackMap, key)) { 620 reusedMap[key] = { 621 item, index, key, 622 target: oldChildren[index], 623 vm: oldVms[index] 624 }; 625 reusedList.push(item); 626 } else { 627 removeTarget(oldChildren[index]); 628 } 629 }); 630 631 // Create new element for each new item. 632 children.length = 0; 633 vms.length = 0; 634 fragBlock.data = data.slice(); 635 fragBlock.updateMark = fragBlock.start; 636 637 data.forEach((item, index) => { 638 const key = trackBy && item[trackBy] !== undefined ? item[trackBy] : index; 639 const reused = reusedMap[key]; 640 if (reused) { 641 if (reused.item === reusedList[0]) { 642 reusedList.shift(); 643 } else { 644 removeItem(reusedList, reused.item); 645 moveTarget(reused.target, fragBlock.updateMark); 646 } 647 children.push(reused.target); 648 vms.push(reused.vm); 649 reused.vm[valueName] = item; 650 651 reused.vm[keyName] = index; 652 fragBlock.updateMark = reused.target; 653 } else { 654 compileItem(item, index, vm); 655 } 656 }); 657 delete fragBlock.updateMark; 658 } 659 ); 660 if (list && Array.isArray(list)) { 661 fragBlock.data = list.slice(0); 662 list.forEach((item, index) => { 663 compileItem(item, index, vm); 664 }); 665 } 666} 667 668/** 669 * Watch the display update and add/remove the element. 670 * @param {Vm} vm - Vm object needs to be compiled. 671 * @param {TemplateInterface} target - Node needs to be compiled. Structure of the label in the template. 672 * @param {FragBlockInterface} fragBlock - {vms, data, children} 673 * @param {MetaInterface} meta - To transfer data. 674 */ 675function bindShown( 676 vm: Vm, 677 target: TemplateInterface, 678 fragBlock: FragBlockInterface, 679 meta: Partial<MetaInterface> 680): void { 681 const display = watchBlock(vm, fragBlock, target.shown, 'shown', 682 (display) => { 683 Log.debug(`The 'if' item was changed ${display}.`); 684 if (!fragBlock || !!fragBlock.display === !!display) { 685 return; 686 } 687 fragBlock.display = !!display; 688 if (display) { 689 compile(vm, target, fragBlock, meta); 690 } else { 691 removeTarget(fragBlock, true); 692 } 693 } 694 ); 695 696 fragBlock.display = !!display; 697 if (display) { 698 compile(vm, target, fragBlock, meta); 699 } 700} 701 702/** 703 * Watch calc changes and append certain type action to differ. 704 * @param {Vm} vm - Vm object needs to be compiled. 705 * @param {FragBlockInterface} fragBlock - {vms, data, children} 706 * @param {Function} calc - Function. 707 * @param {string} type - Vm type. 708 * @param {Function} handler - Function. 709 * @return {*} Init value of calc. 710 */ 711function watchBlock(vm: Vm, fragBlock: FragBlockInterface, calc: Function, type: string, handler: Function): any { 712 const differ = vm && vm.app && vm.app.differ; 713 const config: Partial<ConfigInterface> = {}; 714 const newWatcher = newWatch(vm, calc, (value) => { 715 config.latestValue = value; 716 if (differ && !config.recorded) { 717 differ.append(type, fragBlock.blockId.toString(), () => { 718 const latestValue = config.latestValue; 719 handler(latestValue); 720 config.recorded = false; 721 config.latestValue = undefined; 722 }); 723 } 724 config.recorded = true; 725 }); 726 fragBlock.end.watchers.push(newWatcher); 727 return newWatcher.value; 728} 729 730/** 731 * Clone a context and merge certain data. 732 * @param {Vm} context - Context value. 733 * @param {Object} mergedData - Certain data. 734 * @return {*} The new context. 735 */ 736function mergeContext(context: Vm, mergedData: object): any { 737 const newContext = Object.create(context); 738 newContext._data = mergedData; 739 newContext._shareData = {}; 740 initData(newContext); 741 initComputed(newContext); 742 newContext._realParent = context; 743 return newContext; 744} 745 746/** 747 * Check if it needs repeat. 748 * @param {Function | RepeatInterface} repeat - Repeat value. 749 * @return {boolean} - True if it needs repeat. Otherwise return false. 750 */ 751function isRepeat(repeat: Function | RepeatInterface): repeat is RepeatInterface { 752 const newRepeat = <RepeatInterface>repeat; 753 return newRepeat.exp !== undefined; 754} 755 756/** 757 * Check if it is a block. 758 * @param {FragBlockInterface | Node} node - Node value. 759 * @return {boolean} - True if it is a block. Otherwise return false. 760 */ 761export function isBlock(node: FragBlockInterface | Node): node is FragBlockInterface { 762 const newNode = <FragBlockInterface>node; 763 return newNode.blockId !== undefined; 764} 765