LSPlugin.shadow.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import EventEmitter from 'eventemitter3'
  2. import { PluginLocal } from './LSPlugin.core'
  3. import { LSPluginUser } from './LSPlugin.user'
  4. // @ts-ignore
  5. const { importHTML, createSandboxContainer } = window.QSandbox || {}
  6. function userFetch (url, opts) {
  7. if (!url.startsWith('http')) {
  8. url = url.replace('file://', '')
  9. return new Promise(async (resolve, reject) => {
  10. try {
  11. const content = await window.apis.doAction(['readFile', url])
  12. resolve({
  13. text () {
  14. return content
  15. }
  16. })
  17. } catch (e) {
  18. console.error(e)
  19. reject(e)
  20. }
  21. })
  22. }
  23. return fetch(url, opts)
  24. }
  25. class LSPluginShadowFrame extends EventEmitter<'mounted' | 'unmounted'> {
  26. private _frame?: HTMLElement
  27. private _root?: ShadowRoot
  28. private _loaded = false
  29. private _unmountFns: Array<() => Promise<void>> = []
  30. constructor (
  31. private _pluginLocal: PluginLocal
  32. ) {
  33. super()
  34. _pluginLocal._dispose(() => {
  35. this._unmount()
  36. })
  37. }
  38. async load () {
  39. const { name, entry } = this._pluginLocal.options
  40. if (this.loaded || !entry) return
  41. const { template, execScripts } = await importHTML(entry, { fetch: userFetch })
  42. this._mount(template, document.body)
  43. const sandbox = createSandboxContainer(
  44. name, {
  45. elementGetter: () => this._root?.firstChild,
  46. }
  47. )
  48. const global = sandbox.instance.proxy as any
  49. global.__shadow_mode__ = true
  50. global.LSPluginLocal = this._pluginLocal
  51. global.LSPluginShadow = this
  52. global.LSPluginUser = global.logseq = new LSPluginUser(
  53. this._pluginLocal.toJSON() as any,
  54. this._pluginLocal.caller!
  55. )
  56. // TODO: {mount, unmount}
  57. const execResult: any = await execScripts(global, true)
  58. this._unmountFns.push(execResult.unmount)
  59. this._loaded = true
  60. }
  61. _mount (content: string, container: HTMLElement) {
  62. const frame = this._frame = document.createElement('div')
  63. frame.classList.add('lsp-shadow-sandbox')
  64. frame.id = this._pluginLocal.id
  65. this._root = frame.attachShadow({ mode: 'open' })
  66. this._root.innerHTML = `<div>${content}</div>`
  67. container.appendChild(frame)
  68. this.emit('mounted')
  69. }
  70. _unmount () {
  71. for (const fn of this._unmountFns) {
  72. fn && fn.call(null)
  73. }
  74. }
  75. destroy () {
  76. this.frame?.parentNode?.removeChild(this.frame)
  77. }
  78. get loaded (): boolean {
  79. return this._loaded
  80. }
  81. get document () {
  82. return this._root?.firstChild as HTMLElement
  83. }
  84. get frame (): HTMLElement {
  85. return this._frame!
  86. }
  87. }
  88. export {
  89. LSPluginShadowFrame
  90. }