@bigbluebutton/tlschema 2.0.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/LICENSE +190 -0
  2. package/dist-cjs/TLStore.js +133 -0
  3. package/dist-cjs/TLStore.js.map +7 -0
  4. package/dist-cjs/assets/TLBaseAsset.js +37 -0
  5. package/dist-cjs/assets/TLBaseAsset.js.map +7 -0
  6. package/dist-cjs/assets/TLBookmarkAsset.js +38 -0
  7. package/dist-cjs/assets/TLBookmarkAsset.js.map +7 -0
  8. package/dist-cjs/assets/TLImageAsset.js +76 -0
  9. package/dist-cjs/assets/TLImageAsset.js.map +7 -0
  10. package/dist-cjs/assets/TLVideoAsset.js +76 -0
  11. package/dist-cjs/assets/TLVideoAsset.js.map +7 -0
  12. package/dist-cjs/createPresenceStateDerivation.js +68 -0
  13. package/dist-cjs/createPresenceStateDerivation.js.map +7 -0
  14. package/dist-cjs/createTLSchema.js +68 -0
  15. package/dist-cjs/createTLSchema.js.map +7 -0
  16. package/dist-cjs/index.d.ts +1414 -0
  17. package/dist-cjs/index.js +142 -0
  18. package/dist-cjs/index.js.map +7 -0
  19. package/dist-cjs/misc/TLColor.js +36 -0
  20. package/dist-cjs/misc/TLColor.js.map +7 -0
  21. package/dist-cjs/misc/TLCursor.js +55 -0
  22. package/dist-cjs/misc/TLCursor.js.map +7 -0
  23. package/dist-cjs/misc/TLHandle.js +36 -0
  24. package/dist-cjs/misc/TLHandle.js.map +7 -0
  25. package/dist-cjs/misc/TLOpacity.js +30 -0
  26. package/dist-cjs/misc/TLOpacity.js.map +7 -0
  27. package/dist-cjs/misc/TLScribble.js +40 -0
  28. package/dist-cjs/misc/TLScribble.js.map +7 -0
  29. package/dist-cjs/misc/geometry-types.js +37 -0
  30. package/dist-cjs/misc/geometry-types.js.map +7 -0
  31. package/dist-cjs/misc/id-validator.js +33 -0
  32. package/dist-cjs/misc/id-validator.js.map +7 -0
  33. package/dist-cjs/records/TLAsset.js +74 -0
  34. package/dist-cjs/records/TLAsset.js.map +7 -0
  35. package/dist-cjs/records/TLCamera.js +74 -0
  36. package/dist-cjs/records/TLCamera.js.map +7 -0
  37. package/dist-cjs/records/TLDocument.js +82 -0
  38. package/dist-cjs/records/TLDocument.js.map +7 -0
  39. package/dist-cjs/records/TLInstance.js +452 -0
  40. package/dist-cjs/records/TLInstance.js.map +7 -0
  41. package/dist-cjs/records/TLPage.js +74 -0
  42. package/dist-cjs/records/TLPage.js.map +7 -0
  43. package/dist-cjs/records/TLPageState.js +209 -0
  44. package/dist-cjs/records/TLPageState.js.map +7 -0
  45. package/dist-cjs/records/TLPointer.js +76 -0
  46. package/dist-cjs/records/TLPointer.js.map +7 -0
  47. package/dist-cjs/records/TLPresence.js +170 -0
  48. package/dist-cjs/records/TLPresence.js.map +7 -0
  49. package/dist-cjs/records/TLRecord.js +17 -0
  50. package/dist-cjs/records/TLRecord.js.map +7 -0
  51. package/dist-cjs/records/TLShape.js +147 -0
  52. package/dist-cjs/records/TLShape.js.map +7 -0
  53. package/dist-cjs/shapes/TLArrowShape.js +156 -0
  54. package/dist-cjs/shapes/TLArrowShape.js.map +7 -0
  55. package/dist-cjs/shapes/TLBaseShape.js +52 -0
  56. package/dist-cjs/shapes/TLBaseShape.js.map +7 -0
  57. package/dist-cjs/shapes/TLBookmarkShape.js +57 -0
  58. package/dist-cjs/shapes/TLBookmarkShape.js.map +7 -0
  59. package/dist-cjs/shapes/TLDrawShape.js +89 -0
  60. package/dist-cjs/shapes/TLDrawShape.js.map +7 -0
  61. package/dist-cjs/shapes/TLEmbedShape.js +709 -0
  62. package/dist-cjs/shapes/TLEmbedShape.js.map +7 -0
  63. package/dist-cjs/shapes/TLFrameShape.js +33 -0
  64. package/dist-cjs/shapes/TLFrameShape.js.map +7 -0
  65. package/dist-cjs/shapes/TLGeoShape.js +230 -0
  66. package/dist-cjs/shapes/TLGeoShape.js.map +7 -0
  67. package/dist-cjs/shapes/TLGroupShape.js +28 -0
  68. package/dist-cjs/shapes/TLGroupShape.js.map +7 -0
  69. package/dist-cjs/shapes/TLHighlightShape.js +38 -0
  70. package/dist-cjs/shapes/TLHighlightShape.js.map +7 -0
  71. package/dist-cjs/shapes/TLImageShape.js +69 -0
  72. package/dist-cjs/shapes/TLImageShape.js.map +7 -0
  73. package/dist-cjs/shapes/TLLineShape.js +70 -0
  74. package/dist-cjs/shapes/TLLineShape.js.map +7 -0
  75. package/dist-cjs/shapes/TLNoteShape.js +144 -0
  76. package/dist-cjs/shapes/TLNoteShape.js.map +7 -0
  77. package/dist-cjs/shapes/TLTextShape.js +67 -0
  78. package/dist-cjs/shapes/TLTextShape.js.map +7 -0
  79. package/dist-cjs/shapes/TLVideoShape.js +53 -0
  80. package/dist-cjs/shapes/TLVideoShape.js.map +7 -0
  81. package/dist-cjs/store-migrations.js +81 -0
  82. package/dist-cjs/store-migrations.js.map +7 -0
  83. package/dist-cjs/styles/StyleProp.js +94 -0
  84. package/dist-cjs/styles/StyleProp.js.map +7 -0
  85. package/dist-cjs/styles/TLColorStyle.js +283 -0
  86. package/dist-cjs/styles/TLColorStyle.js.map +7 -0
  87. package/dist-cjs/styles/TLDashStyle.js +29 -0
  88. package/dist-cjs/styles/TLDashStyle.js.map +7 -0
  89. package/dist-cjs/styles/TLFillStyle.js +29 -0
  90. package/dist-cjs/styles/TLFillStyle.js.map +7 -0
  91. package/dist-cjs/styles/TLFontStyle.js +36 -0
  92. package/dist-cjs/styles/TLFontStyle.js.map +7 -0
  93. package/dist-cjs/styles/TLHorizontalAlignStyle.js +29 -0
  94. package/dist-cjs/styles/TLHorizontalAlignStyle.js.map +7 -0
  95. package/dist-cjs/styles/TLSizeStyle.js +29 -0
  96. package/dist-cjs/styles/TLSizeStyle.js.map +7 -0
  97. package/dist-cjs/styles/TLVerticalAlignStyle.js +29 -0
  98. package/dist-cjs/styles/TLVerticalAlignStyle.js.map +7 -0
  99. package/dist-cjs/translations/languages.js +59 -0
  100. package/dist-cjs/translations/languages.js.map +7 -0
  101. package/dist-cjs/translations/translations.js +63 -0
  102. package/dist-cjs/translations/translations.js.map +7 -0
  103. package/dist-cjs/util-types.js +17 -0
  104. package/dist-cjs/util-types.js.map +7 -0
  105. package/dist-esm/TLStore.mjs +113 -0
  106. package/dist-esm/TLStore.mjs.map +7 -0
  107. package/dist-esm/assets/TLBaseAsset.mjs +17 -0
  108. package/dist-esm/assets/TLBaseAsset.mjs.map +7 -0
  109. package/dist-esm/assets/TLBookmarkAsset.mjs +18 -0
  110. package/dist-esm/assets/TLBookmarkAsset.mjs.map +7 -0
  111. package/dist-esm/assets/TLImageAsset.mjs +56 -0
  112. package/dist-esm/assets/TLImageAsset.mjs.map +7 -0
  113. package/dist-esm/assets/TLVideoAsset.mjs +56 -0
  114. package/dist-esm/assets/TLVideoAsset.mjs.map +7 -0
  115. package/dist-esm/createPresenceStateDerivation.mjs +48 -0
  116. package/dist-esm/createPresenceStateDerivation.mjs.map +7 -0
  117. package/dist-esm/createTLSchema.mjs +48 -0
  118. package/dist-esm/createTLSchema.mjs.map +7 -0
  119. package/dist-esm/index.d.mts +1414 -0
  120. package/dist-esm/index.mjs +193 -0
  121. package/dist-esm/index.mjs.map +7 -0
  122. package/dist-esm/misc/TLColor.mjs +16 -0
  123. package/dist-esm/misc/TLColor.mjs.map +7 -0
  124. package/dist-esm/misc/TLCursor.mjs +35 -0
  125. package/dist-esm/misc/TLCursor.mjs.map +7 -0
  126. package/dist-esm/misc/TLHandle.mjs +16 -0
  127. package/dist-esm/misc/TLHandle.mjs.map +7 -0
  128. package/dist-esm/misc/TLOpacity.mjs +10 -0
  129. package/dist-esm/misc/TLOpacity.mjs.map +7 -0
  130. package/dist-esm/misc/TLScribble.mjs +20 -0
  131. package/dist-esm/misc/TLScribble.mjs.map +7 -0
  132. package/dist-esm/misc/geometry-types.mjs +17 -0
  133. package/dist-esm/misc/geometry-types.mjs.map +7 -0
  134. package/dist-esm/misc/id-validator.mjs +13 -0
  135. package/dist-esm/misc/id-validator.mjs.map +7 -0
  136. package/dist-esm/records/TLAsset.mjs +57 -0
  137. package/dist-esm/records/TLAsset.mjs.map +7 -0
  138. package/dist-esm/records/TLCamera.mjs +54 -0
  139. package/dist-esm/records/TLCamera.mjs.map +7 -0
  140. package/dist-esm/records/TLDocument.mjs +62 -0
  141. package/dist-esm/records/TLDocument.mjs.map +7 -0
  142. package/dist-esm/records/TLInstance.mjs +432 -0
  143. package/dist-esm/records/TLInstance.mjs.map +7 -0
  144. package/dist-esm/records/TLPage.mjs +54 -0
  145. package/dist-esm/records/TLPage.mjs.map +7 -0
  146. package/dist-esm/records/TLPageState.mjs +189 -0
  147. package/dist-esm/records/TLPageState.mjs.map +7 -0
  148. package/dist-esm/records/TLPointer.mjs +56 -0
  149. package/dist-esm/records/TLPointer.mjs.map +7 -0
  150. package/dist-esm/records/TLPresence.mjs +150 -0
  151. package/dist-esm/records/TLPresence.mjs.map +7 -0
  152. package/dist-esm/records/TLRecord.mjs +1 -0
  153. package/dist-esm/records/TLRecord.mjs.map +7 -0
  154. package/dist-esm/records/TLShape.mjs +127 -0
  155. package/dist-esm/records/TLShape.mjs.map +7 -0
  156. package/dist-esm/shapes/TLArrowShape.mjs +136 -0
  157. package/dist-esm/shapes/TLArrowShape.mjs.map +7 -0
  158. package/dist-esm/shapes/TLBaseShape.mjs +32 -0
  159. package/dist-esm/shapes/TLBaseShape.mjs.map +7 -0
  160. package/dist-esm/shapes/TLBookmarkShape.mjs +37 -0
  161. package/dist-esm/shapes/TLBookmarkShape.mjs.map +7 -0
  162. package/dist-esm/shapes/TLDrawShape.mjs +69 -0
  163. package/dist-esm/shapes/TLDrawShape.mjs.map +7 -0
  164. package/dist-esm/shapes/TLEmbedShape.mjs +689 -0
  165. package/dist-esm/shapes/TLEmbedShape.mjs.map +7 -0
  166. package/dist-esm/shapes/TLFrameShape.mjs +13 -0
  167. package/dist-esm/shapes/TLFrameShape.mjs.map +7 -0
  168. package/dist-esm/shapes/TLGeoShape.mjs +212 -0
  169. package/dist-esm/shapes/TLGeoShape.mjs.map +7 -0
  170. package/dist-esm/shapes/TLGroupShape.mjs +8 -0
  171. package/dist-esm/shapes/TLGroupShape.mjs.map +7 -0
  172. package/dist-esm/shapes/TLHighlightShape.mjs +18 -0
  173. package/dist-esm/shapes/TLHighlightShape.mjs.map +7 -0
  174. package/dist-esm/shapes/TLImageShape.mjs +49 -0
  175. package/dist-esm/shapes/TLImageShape.mjs.map +7 -0
  176. package/dist-esm/shapes/TLLineShape.mjs +50 -0
  177. package/dist-esm/shapes/TLLineShape.mjs.map +7 -0
  178. package/dist-esm/shapes/TLNoteShape.mjs +126 -0
  179. package/dist-esm/shapes/TLNoteShape.mjs.map +7 -0
  180. package/dist-esm/shapes/TLTextShape.mjs +47 -0
  181. package/dist-esm/shapes/TLTextShape.mjs.map +7 -0
  182. package/dist-esm/shapes/TLVideoShape.mjs +33 -0
  183. package/dist-esm/shapes/TLVideoShape.mjs.map +7 -0
  184. package/dist-esm/store-migrations.mjs +61 -0
  185. package/dist-esm/store-migrations.mjs.map +7 -0
  186. package/dist-esm/styles/StyleProp.mjs +74 -0
  187. package/dist-esm/styles/StyleProp.mjs.map +7 -0
  188. package/dist-esm/styles/TLColorStyle.mjs +263 -0
  189. package/dist-esm/styles/TLColorStyle.mjs.map +7 -0
  190. package/dist-esm/styles/TLDashStyle.mjs +9 -0
  191. package/dist-esm/styles/TLDashStyle.mjs.map +7 -0
  192. package/dist-esm/styles/TLFillStyle.mjs +9 -0
  193. package/dist-esm/styles/TLFillStyle.mjs.map +7 -0
  194. package/dist-esm/styles/TLFontStyle.mjs +16 -0
  195. package/dist-esm/styles/TLFontStyle.mjs.map +7 -0
  196. package/dist-esm/styles/TLHorizontalAlignStyle.mjs +9 -0
  197. package/dist-esm/styles/TLHorizontalAlignStyle.mjs.map +7 -0
  198. package/dist-esm/styles/TLSizeStyle.mjs +9 -0
  199. package/dist-esm/styles/TLSizeStyle.mjs.map +7 -0
  200. package/dist-esm/styles/TLVerticalAlignStyle.mjs +9 -0
  201. package/dist-esm/styles/TLVerticalAlignStyle.mjs.map +7 -0
  202. package/dist-esm/translations/languages.mjs +39 -0
  203. package/dist-esm/translations/languages.mjs.map +7 -0
  204. package/dist-esm/translations/translations.mjs +43 -0
  205. package/dist-esm/translations/translations.mjs.map +7 -0
  206. package/dist-esm/util-types.mjs +1 -0
  207. package/dist-esm/util-types.mjs.map +7 -0
  208. package/package.json +64 -0
  209. package/src/TLStore.ts +160 -0
  210. package/src/assets/TLBaseAsset.ts +44 -0
  211. package/src/assets/TLBookmarkAsset.ts +31 -0
  212. package/src/assets/TLImageAsset.ts +73 -0
  213. package/src/assets/TLVideoAsset.ts +73 -0
  214. package/src/createPresenceStateDerivation.ts +53 -0
  215. package/src/createTLSchema.ts +65 -0
  216. package/src/index.ts +168 -0
  217. package/src/migrations.test.ts +1768 -0
  218. package/src/misc/TLColor.ts +28 -0
  219. package/src/misc/TLCursor.ts +54 -0
  220. package/src/misc/TLHandle.ts +41 -0
  221. package/src/misc/TLOpacity.ts +11 -0
  222. package/src/misc/TLScribble.ts +39 -0
  223. package/src/misc/geometry-types.ts +37 -0
  224. package/src/misc/id-validator.ts +14 -0
  225. package/src/records/TLAsset.ts +80 -0
  226. package/src/records/TLCamera.ts +74 -0
  227. package/src/records/TLDocument.ts +77 -0
  228. package/src/records/TLInstance.ts +493 -0
  229. package/src/records/TLPage.ts +72 -0
  230. package/src/records/TLPageState.ts +216 -0
  231. package/src/records/TLPointer.ts +74 -0
  232. package/src/records/TLPresence.ts +179 -0
  233. package/src/records/TLRecord.ts +21 -0
  234. package/src/records/TLShape.ts +214 -0
  235. package/src/shapes/TLArrowShape.ts +160 -0
  236. package/src/shapes/TLBaseShape.ts +67 -0
  237. package/src/shapes/TLBookmarkShape.ts +44 -0
  238. package/src/shapes/TLDrawShape.ts +89 -0
  239. package/src/shapes/TLEmbedShape.ts +754 -0
  240. package/src/shapes/TLFrameShape.ts +18 -0
  241. package/src/shapes/TLGeoShape.ts +226 -0
  242. package/src/shapes/TLGroupShape.ts +14 -0
  243. package/src/shapes/TLHighlightShape.ts +24 -0
  244. package/src/shapes/TLImageShape.ts +59 -0
  245. package/src/shapes/TLLineShape.ts +61 -0
  246. package/src/shapes/TLNoteShape.ts +137 -0
  247. package/src/shapes/TLTextShape.ts +55 -0
  248. package/src/shapes/TLVideoShape.ts +40 -0
  249. package/src/store-migrations.ts +63 -0
  250. package/src/styles/StyleProp.ts +105 -0
  251. package/src/styles/TLColorStyle.ts +296 -0
  252. package/src/styles/TLDashStyle.ts +11 -0
  253. package/src/styles/TLFillStyle.ts +11 -0
  254. package/src/styles/TLFontStyle.ts +19 -0
  255. package/src/styles/TLHorizontalAlignStyle.ts +11 -0
  256. package/src/styles/TLSizeStyle.ts +11 -0
  257. package/src/styles/TLVerticalAlignStyle.ts +11 -0
  258. package/src/translations/languages.ts +39 -0
  259. package/src/translations/translations.test.ts +43 -0
  260. package/src/translations/translations.ts +73 -0
  261. package/src/util-types.ts +2 -0
@@ -0,0 +1,754 @@
1
+ import { defineMigrations } from '@bigbluebutton/store'
2
+ import { T } from '@bigbluebutton/validate'
3
+ import { ShapePropsType, TLBaseShape } from './TLBaseShape'
4
+
5
+ // Only allow multiplayer embeds. If we add additional routes later for example '/help' this won't match
6
+ const TLDRAW_APP_RE = /(^\/r\/[^/]+\/?$)/
7
+
8
+ const safeParseUrl = (url: string) => {
9
+ try {
10
+ return new URL(url)
11
+ } catch (err) {
12
+ return
13
+ }
14
+ }
15
+
16
+ /** @public */
17
+ export const EMBED_DEFINITIONS = [
18
+ {
19
+ type: 'tldraw',
20
+ title: 'tldraw',
21
+ hostnames: ['beta.tldraw.com', 'tldraw.com'],
22
+ minWidth: 300,
23
+ minHeight: 300,
24
+ width: 720,
25
+ height: 500,
26
+ doesResize: true,
27
+ canUnmount: true,
28
+ toEmbedUrl: (url) => {
29
+ const urlObj = safeParseUrl(url)
30
+ if (urlObj && urlObj.pathname.match(TLDRAW_APP_RE)) {
31
+ return url
32
+ }
33
+ return
34
+ },
35
+ fromEmbedUrl: (url) => {
36
+ const urlObj = safeParseUrl(url)
37
+ if (urlObj && urlObj.pathname.match(TLDRAW_APP_RE)) {
38
+ return url
39
+ }
40
+ return
41
+ },
42
+ },
43
+ {
44
+ type: 'figma',
45
+ title: 'Figma',
46
+ hostnames: ['figma.com'],
47
+ width: 720,
48
+ height: 500,
49
+ doesResize: true,
50
+ canUnmount: true,
51
+ toEmbedUrl: (url) => {
52
+ if (
53
+ !!url.match(
54
+ // eslint-disable-next-line no-useless-escape
55
+ /https:\/\/([\w\.-]+\.)?figma.com\/(file|proto)\/([0-9a-zA-Z]{22,128})(?:\/.*)?$/
56
+ ) &&
57
+ !url.includes('figma.com/embed')
58
+ ) {
59
+ return `https://www.figma.com/embed?embed_host=share&url=${url}`
60
+ }
61
+ return
62
+ },
63
+ fromEmbedUrl: (url) => {
64
+ const urlObj = safeParseUrl(url)
65
+ if (urlObj && urlObj.pathname.match(/^\/embed\/?$/)) {
66
+ const outUrl = urlObj.searchParams.get('url')
67
+ if (outUrl) {
68
+ return outUrl
69
+ }
70
+ }
71
+ return
72
+ },
73
+ },
74
+ {
75
+ type: 'google_maps',
76
+ title: 'Google Maps',
77
+ hostnames: ['google.*'],
78
+ width: 720,
79
+ height: 500,
80
+ doesResize: true,
81
+ canUnmount: false,
82
+ toEmbedUrl: (url) => {
83
+ if (url.includes('/maps/')) {
84
+ const match = url.match(/@(.*),(.*),(.*)z/)
85
+ let result: string
86
+ if (match) {
87
+ const [, lat, lng, z] = match
88
+ const host = new URL(url).host.replace('www.', '')
89
+ result = `https://${host}/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=${lat},${lng}&zoom=${z}`
90
+ } else {
91
+ result = ''
92
+ }
93
+
94
+ return result
95
+ }
96
+ return
97
+ },
98
+ fromEmbedUrl: (url) => {
99
+ const urlObj = safeParseUrl(url)
100
+ if (!urlObj) return
101
+
102
+ const matches = urlObj.pathname.match(/^\/maps\/embed\/v1\/view\/?$/)
103
+ if (matches && urlObj.searchParams.has('center') && urlObj.searchParams.get('zoom')) {
104
+ const zoom = urlObj.searchParams.get('zoom')
105
+ const [lat, lon] = urlObj.searchParams.get('center')!.split(',')
106
+ return `https://www.google.com/maps/@${lat},${lon},${zoom}z`
107
+ }
108
+ return
109
+ },
110
+ },
111
+ {
112
+ type: 'val_town',
113
+ title: 'Val Town',
114
+ hostnames: ['val.town'],
115
+ minWidth: 260,
116
+ minHeight: 100,
117
+ width: 720,
118
+ height: 500,
119
+ doesResize: true,
120
+ canUnmount: false,
121
+ toEmbedUrl: (url) => {
122
+ const urlObj = safeParseUrl(url)
123
+ // e.g. extract "steveruizok.mathFact" from https://www.val.town/v/steveruizok.mathFact
124
+ const matches = urlObj && urlObj.pathname.match(/\/v\/([^/]+)\/?/)
125
+ if (matches) {
126
+ return `https://www.val.town/embed/${matches[1]}`
127
+ }
128
+ return
129
+ },
130
+ fromEmbedUrl: (url) => {
131
+ const urlObj = safeParseUrl(url)
132
+ // e.g. extract "steveruizok.mathFact" from https://www.val.town/v/steveruizok.mathFact
133
+ const matches = urlObj && urlObj.pathname.match(/\/embed\/([^/]+)\/?/)
134
+ if (matches) {
135
+ return `https://www.val.town/v/${matches[1]}`
136
+ }
137
+ return
138
+ },
139
+ },
140
+ {
141
+ type: 'codesandbox',
142
+ title: 'CodeSandbox',
143
+ hostnames: ['codesandbox.io'],
144
+ minWidth: 300,
145
+ minHeight: 300,
146
+ width: 720,
147
+ height: 500,
148
+ doesResize: true,
149
+ canUnmount: false,
150
+ toEmbedUrl: (url) => {
151
+ const urlObj = safeParseUrl(url)
152
+ const matches = urlObj && urlObj.pathname.match(/\/s\/([^/]+)\/?/)
153
+ if (matches) {
154
+ return `https://codesandbox.io/embed/${matches[1]}`
155
+ }
156
+ return
157
+ },
158
+ fromEmbedUrl: (url) => {
159
+ const urlObj = safeParseUrl(url)
160
+ const matches = urlObj && urlObj.pathname.match(/\/embed\/([^/]+)\/?/)
161
+ if (matches) {
162
+ return `https://codesandbox.io/s/${matches[1]}`
163
+ }
164
+ return
165
+ },
166
+ },
167
+ {
168
+ type: 'codepen',
169
+ title: 'Codepen',
170
+ hostnames: ['codepen.io'],
171
+ minWidth: 300,
172
+ minHeight: 300,
173
+ width: 520,
174
+ height: 400,
175
+ doesResize: true,
176
+ canUnmount: false,
177
+ toEmbedUrl: (url) => {
178
+ const CODEPEN_URL_REGEXP = /https:\/\/codepen.io\/([^/]+)\/pen\/([^/]+)/
179
+ const matches = url.match(CODEPEN_URL_REGEXP)
180
+ if (matches) {
181
+ const [_, user, id] = matches
182
+ return `https://codepen.io/${user}/embed/${id}`
183
+ }
184
+ return
185
+ },
186
+ fromEmbedUrl: (url) => {
187
+ const CODEPEN_EMBED_REGEXP = /https:\/\/codepen.io\/([^/]+)\/embed\/([^/]+)/
188
+ const matches = url.match(CODEPEN_EMBED_REGEXP)
189
+ if (matches) {
190
+ const [_, user, id] = matches
191
+ return `https://codepen.io/${user}/pen/${id}`
192
+ }
193
+ return
194
+ },
195
+ },
196
+ {
197
+ type: 'scratch',
198
+ title: 'Scratch',
199
+ hostnames: ['scratch.mit.edu'],
200
+ width: 520,
201
+ height: 400,
202
+ doesResize: false,
203
+ canUnmount: false,
204
+ toEmbedUrl: (url) => {
205
+ const SCRATCH_URL_REGEXP = /https?:\/\/scratch.mit.edu\/projects\/([^/]+)/
206
+ const matches = url.match(SCRATCH_URL_REGEXP)
207
+ if (matches) {
208
+ const [_, id] = matches
209
+ return `https://scratch.mit.edu/projects/embed/${id}`
210
+ }
211
+ return
212
+ },
213
+ fromEmbedUrl: (url) => {
214
+ const SCRATCH_EMBED_REGEXP = /https:\/\/scratch.mit.edu\/projects\/embed\/([^/]+)/
215
+ const matches = url.match(SCRATCH_EMBED_REGEXP)
216
+ if (matches) {
217
+ const [_, id] = matches
218
+ return `https://scratch.mit.edu/projects/${id}`
219
+ }
220
+ return
221
+ },
222
+ },
223
+ {
224
+ type: 'youtube',
225
+ title: 'YouTube',
226
+ hostnames: ['*.youtube.com', 'youtube.com', 'youtu.be'],
227
+ width: 800,
228
+ height: 450,
229
+ doesResize: true,
230
+ canUnmount: false,
231
+ overridePermissions: {
232
+ 'allow-presentation': true,
233
+ },
234
+ isAspectRatioLocked: true,
235
+ toEmbedUrl: (url) => {
236
+ const urlObj = safeParseUrl(url)
237
+ if (!urlObj) return
238
+
239
+ const hostname = urlObj.hostname.replace(/^www./, '')
240
+ if (hostname === 'youtu.be') {
241
+ const videoId = urlObj.pathname.split('/').filter(Boolean)[0]
242
+ return `https://www.youtube.com/embed/${videoId}`
243
+ } else if (
244
+ (hostname === 'youtube.com' || hostname === 'm.youtube.com') &&
245
+ urlObj.pathname.match(/^\/watch/)
246
+ ) {
247
+ const videoId = urlObj.searchParams.get('v')
248
+ return `https://www.youtube.com/embed/${videoId}`
249
+ }
250
+ return
251
+ },
252
+ fromEmbedUrl: (url) => {
253
+ const urlObj = safeParseUrl(url)
254
+ if (!urlObj) return
255
+
256
+ const hostname = urlObj.hostname.replace(/^www./, '')
257
+ if (hostname === 'youtube.com') {
258
+ const matches = urlObj.pathname.match(/^\/embed\/([^/]+)\/?/)
259
+ if (matches) {
260
+ return `https://www.youtube.com/watch?v=${matches[1]}`
261
+ }
262
+ }
263
+ return
264
+ },
265
+ },
266
+ {
267
+ type: 'google_calendar',
268
+ title: 'Google Calendar',
269
+ hostnames: ['calendar.google.*'],
270
+ width: 720,
271
+ height: 500,
272
+ minWidth: 460,
273
+ minHeight: 360,
274
+ doesResize: true,
275
+ canUnmount: false,
276
+ instructionLink: 'https://support.google.com/calendar/answer/41207?hl=en',
277
+ toEmbedUrl: (url) => {
278
+ const urlObj = safeParseUrl(url)
279
+ const cidQs = urlObj?.searchParams.get('cid')
280
+
281
+ if (urlObj?.pathname.match(/\/calendar\/u\/0/) && cidQs) {
282
+ urlObj.pathname = '/calendar/embed'
283
+
284
+ const keys = Array.from(urlObj.searchParams.keys())
285
+ for (const key of keys) {
286
+ urlObj.searchParams.delete(key)
287
+ }
288
+ urlObj.searchParams.set('src', cidQs)
289
+ return urlObj.href
290
+ }
291
+ return
292
+ },
293
+ fromEmbedUrl: (url) => {
294
+ const urlObj = safeParseUrl(url)
295
+ const srcQs = urlObj?.searchParams.get('src')
296
+
297
+ if (urlObj?.pathname.match(/\/calendar\/embed/) && srcQs) {
298
+ urlObj.pathname = '/calendar/u/0'
299
+ const keys = Array.from(urlObj.searchParams.keys())
300
+ for (const key of keys) {
301
+ urlObj.searchParams.delete(key)
302
+ }
303
+ urlObj.searchParams.set('cid', srcQs)
304
+ return urlObj.href
305
+ }
306
+ return
307
+ },
308
+ },
309
+ {
310
+ type: 'google_slides',
311
+ title: 'Google Slides',
312
+ hostnames: ['docs.google.*'],
313
+ width: 720,
314
+ height: 500,
315
+ minWidth: 460,
316
+ minHeight: 360,
317
+ doesResize: true,
318
+ canUnmount: false,
319
+ toEmbedUrl: (url) => {
320
+ const urlObj = safeParseUrl(url)
321
+
322
+ if (urlObj?.pathname.match(/^\/presentation/) && urlObj?.pathname.match(/\/pub\/?$/)) {
323
+ urlObj.pathname = urlObj.pathname.replace(/\/pub$/, '/embed')
324
+ const keys = Array.from(urlObj.searchParams.keys())
325
+ for (const key of keys) {
326
+ urlObj.searchParams.delete(key)
327
+ }
328
+ return urlObj.href
329
+ }
330
+ return
331
+ },
332
+ fromEmbedUrl: (url) => {
333
+ const urlObj = safeParseUrl(url)
334
+
335
+ if (urlObj?.pathname.match(/^\/presentation/) && urlObj?.pathname.match(/\/embed\/?$/)) {
336
+ urlObj.pathname = urlObj.pathname.replace(/\/embed$/, '/pub')
337
+ const keys = Array.from(urlObj.searchParams.keys())
338
+ for (const key of keys) {
339
+ urlObj.searchParams.delete(key)
340
+ }
341
+ return urlObj.href
342
+ }
343
+ return
344
+ },
345
+ },
346
+ {
347
+ type: 'github_gist',
348
+ title: 'GitHub Gist',
349
+ hostnames: ['gist.github.com'],
350
+ width: 720,
351
+ height: 500,
352
+ doesResize: true,
353
+ canUnmount: true,
354
+ toEmbedUrl: (url) => {
355
+ const urlObj = safeParseUrl(url)
356
+ if (urlObj && urlObj.pathname.match(/\/([^/]+)\/([^/]+)/)) {
357
+ if (!url.split('/').pop()) return
358
+ return url
359
+ }
360
+ return
361
+ },
362
+ fromEmbedUrl: (url) => {
363
+ const urlObj = safeParseUrl(url)
364
+ if (urlObj && urlObj.pathname.match(/\/([^/]+)\/([^/]+)/)) {
365
+ if (!url.split('/').pop()) return
366
+ return url
367
+ }
368
+ return
369
+ },
370
+ },
371
+ {
372
+ type: 'replit',
373
+ title: 'Replit',
374
+ hostnames: ['replit.com'],
375
+ width: 720,
376
+ height: 500,
377
+ doesResize: true,
378
+ canUnmount: false,
379
+ toEmbedUrl: (url) => {
380
+ const urlObj = safeParseUrl(url)
381
+ if (urlObj && urlObj.pathname.match(/\/@([^/]+)\/([^/]+)/)) {
382
+ return `${url}?embed=true`
383
+ }
384
+ return
385
+ },
386
+ fromEmbedUrl: (url) => {
387
+ const urlObj = safeParseUrl(url)
388
+ if (
389
+ urlObj &&
390
+ urlObj.pathname.match(/\/@([^/]+)\/([^/]+)/) &&
391
+ urlObj.searchParams.has('embed')
392
+ ) {
393
+ urlObj.searchParams.delete('embed')
394
+ return urlObj.href
395
+ }
396
+ return
397
+ },
398
+ },
399
+ {
400
+ type: 'felt',
401
+ title: 'Felt',
402
+ hostnames: ['felt.com'],
403
+ width: 720,
404
+ height: 500,
405
+ doesResize: true,
406
+ canUnmount: false,
407
+ toEmbedUrl: (url) => {
408
+ const urlObj = safeParseUrl(url)
409
+ if (urlObj && urlObj.pathname.match(/^\/map\//)) {
410
+ return urlObj.origin + '/embed' + urlObj.pathname
411
+ }
412
+ return
413
+ },
414
+ fromEmbedUrl: (url) => {
415
+ const urlObj = safeParseUrl(url)
416
+ if (urlObj && urlObj.pathname.match(/^\/embed\/map\//)) {
417
+ urlObj.pathname = urlObj.pathname.replace(/^\/embed/, '')
418
+ return urlObj.href
419
+ }
420
+ return
421
+ },
422
+ },
423
+ {
424
+ type: 'spotify',
425
+ title: 'Spotify',
426
+ hostnames: ['open.spotify.com'],
427
+ width: 720,
428
+ height: 500,
429
+ minHeight: 500,
430
+ overrideOutlineRadius: 12,
431
+ doesResize: true,
432
+ canUnmount: false,
433
+ toEmbedUrl: (url) => {
434
+ const urlObj = safeParseUrl(url)
435
+ if (urlObj && urlObj.pathname.match(/^\/(artist|album)\//)) {
436
+ return urlObj.origin + '/embed' + urlObj.pathname
437
+ }
438
+ return
439
+ },
440
+ fromEmbedUrl: (url) => {
441
+ const urlObj = safeParseUrl(url)
442
+ if (urlObj && urlObj.pathname.match(/^\/embed\/(artist|album)\//)) {
443
+ return urlObj.origin + urlObj.pathname.replace(/^\/embed/, '')
444
+ }
445
+ return
446
+ },
447
+ },
448
+ {
449
+ type: 'vimeo',
450
+ title: 'Vimeo',
451
+ hostnames: ['vimeo.com', 'player.vimeo.com'],
452
+ width: 640,
453
+ height: 360,
454
+ doesResize: true,
455
+ canUnmount: false,
456
+ isAspectRatioLocked: true,
457
+ toEmbedUrl: (url) => {
458
+ const urlObj = safeParseUrl(url)
459
+ if (urlObj && urlObj.hostname === 'vimeo.com') {
460
+ if (urlObj.pathname.match(/^\/[0-9]+/)) {
461
+ return (
462
+ 'https://player.vimeo.com/video/' + urlObj.pathname.split('/')[1] + '?title=0&byline=0'
463
+ )
464
+ }
465
+ }
466
+ return
467
+ },
468
+ fromEmbedUrl: (url) => {
469
+ const urlObj = safeParseUrl(url)
470
+ if (urlObj && urlObj.hostname === 'player.vimeo.com') {
471
+ const matches = urlObj.pathname.match(/^\/video\/([^/]+)\/?$/)
472
+ if (matches) {
473
+ return 'https://vimeo.com/' + matches[1]
474
+ }
475
+ }
476
+ return
477
+ },
478
+ },
479
+ {
480
+ type: 'excalidraw',
481
+ title: 'Excalidraw',
482
+ hostnames: ['excalidraw.com'],
483
+ width: 720,
484
+ height: 500,
485
+ doesResize: true,
486
+ canUnmount: false,
487
+ isAspectRatioLocked: true,
488
+ toEmbedUrl: (url) => {
489
+ const urlObj = safeParseUrl(url)
490
+ if (urlObj && urlObj.hash.match(/#room=/)) {
491
+ return url
492
+ }
493
+ return
494
+ },
495
+ fromEmbedUrl: (url) => {
496
+ const urlObj = safeParseUrl(url)
497
+ if (urlObj && urlObj.hash.match(/#room=/)) {
498
+ return url
499
+ }
500
+ return
501
+ },
502
+ },
503
+ {
504
+ type: 'observable',
505
+ title: 'Observable',
506
+ hostnames: ['observablehq.com'],
507
+ width: 720,
508
+ height: 500,
509
+ doesResize: true,
510
+ canUnmount: false,
511
+ isAspectRatioLocked: false,
512
+ backgroundColor: '#fff',
513
+ toEmbedUrl: (url) => {
514
+ const urlObj = safeParseUrl(url)
515
+ if (urlObj && urlObj.pathname.match(/^\/@([^/]+)\/([^/]+)\/?$/)) {
516
+ return `${urlObj.origin}/embed${urlObj.pathname}?cell=*`
517
+ }
518
+ if (urlObj && urlObj.pathname.match(/^\/d\/([^/]+)\/?$/)) {
519
+ const pathName = urlObj.pathname.replace(/^\/d/, '')
520
+ return `${urlObj.origin}/embed${pathName}?cell=*`
521
+ }
522
+
523
+ return
524
+ },
525
+ fromEmbedUrl: (url) => {
526
+ const urlObj = safeParseUrl(url)
527
+ if (urlObj && urlObj.pathname.match(/^\/embed\/@([^/]+)\/([^/]+)\/?$/)) {
528
+ return `${urlObj.origin}${urlObj.pathname.replace('/embed', '')}#cell-*`
529
+ }
530
+ if (urlObj && urlObj.pathname.match(/^\/embed\/([^/]+)\/?$/)) {
531
+ return `${urlObj.origin}${urlObj.pathname.replace('/embed', '/d')}#cell-*`
532
+ }
533
+
534
+ return
535
+ },
536
+ },
537
+ ] as const satisfies readonly EmbedDefinition[]
538
+
539
+ /**
540
+ * Permissions with note inline from
541
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
542
+ *
543
+ * @public
544
+ */
545
+ export const embedShapePermissionDefaults = {
546
+ // ========================================================================================
547
+ // Disabled permissions
548
+ // ========================================================================================
549
+ // [MDN] Experimental: Allows for downloads to occur without a gesture from the user.
550
+ // [REASON] Disabled because otherwise the <iframe/> trick the user on behalf of us to performing an action
551
+ 'allow-downloads-without-user-activation': false,
552
+ // [MDN] Allows for downloads to occur with a gesture from the user.
553
+ // [REASON] Disabled because otherwise the <iframe/> trick the user on behalf of us to performing an action
554
+ 'allow-downloads': false,
555
+ // [MDN] Lets the resource open modal windows.
556
+ // [REASON] The <iframe/> could 'window.prompt("Enter your tldraw password")'
557
+ 'allow-modals': false,
558
+ // [MDN] Lets the resource lock the screen orientation.
559
+ // [REASON] Would interfer with tldraw interface
560
+ 'allow-orientation-lock': false,
561
+ // [MDN] Lets the resource use the Pointer Lock API.
562
+ // [REASON] Maybe we should allow this for games embeds (scratch/codepen/codesandbox)
563
+ 'allow-pointer-lock': false,
564
+ // [MDN] Allows popups (such as window.open(), target="_blank", or showModalDialog()). If this keyword is not used, the popup will silently fail to open.
565
+ // [REASON] We shouldn't allow popups as a embed could pretend to be us by opening a mocked version of tldraw. This is very unobvious when it is performed as an action within out app
566
+ 'allow-popups': true,
567
+ // [MDN] Lets the sandboxed document open new windows without those windows inheriting the sandboxing. For example, this can safely sandbox an advertisement without forcing the same restrictions upon the page the ad links to.
568
+ // [REASON] We're alread disabling popups.
569
+ 'allow-popups-to-escape-sandbox': false,
570
+ // [MDN] Lets the resource start a presentation session.
571
+ // [REASON] Prevents embed from navigating away from tldraw and pretending to be us
572
+ 'allow-presentation': false,
573
+ // [MDN] Experimental: Lets the resource request access to the parent's storage capabilities with the Storage Access API.
574
+ // [REASON] We don't want anyone else to access our storage
575
+ 'allow-storage-access-by-user-activation': false,
576
+ // [MDN] Lets the resource navigate the top-level browsing context (the one named _top).
577
+ // [REASON] Prevents embed from navigating away from tldraw and pretending to be us
578
+ 'allow-top-navigation': false,
579
+ // [MDN] Lets the resource navigate the top-level browsing context, but only if initiated by a user gesture.
580
+ // [REASON] Prevents embed from navigating away from tldraw and pretending to be us
581
+ 'allow-top-navigation-by-user-activation': false,
582
+ // ========================================================================================
583
+ // Enabled permissions
584
+ // ========================================================================================
585
+ // [MDN] Lets the resource run scripts (but not create popup windows).
586
+ 'allow-scripts': true,
587
+ // [MDN] If this token is not used, the resource is treated as being from a special origin that always fails the same-origin policy (potentially preventing access to data storage/cookies and some JavaScript APIs).
588
+ 'allow-same-origin': true,
589
+ // [MDN] Allows the resource to submit forms. If this keyword is not used, form submission is blocked.
590
+ 'allow-forms': true,
591
+ } as const
592
+
593
+ /** @public */
594
+ export type TLEmbedShapePermissions = { [K in keyof typeof embedShapePermissionDefaults]?: boolean }
595
+
596
+ /** @public */
597
+ export const embedShapeProps = {
598
+ w: T.nonZeroNumber,
599
+ h: T.nonZeroNumber,
600
+ url: T.string,
601
+ }
602
+
603
+ /** @public */
604
+ export type TLEmbedShapeProps = ShapePropsType<typeof embedShapeProps>
605
+
606
+ /** @public */
607
+ export type TLEmbedShape = TLBaseShape<'embed', TLEmbedShapeProps>
608
+
609
+ /** @public */
610
+ export type EmbedDefinition = {
611
+ readonly type: string
612
+ readonly title: string
613
+ readonly hostnames: readonly string[]
614
+ readonly minWidth?: number
615
+ readonly minHeight?: number
616
+ readonly width: number
617
+ readonly height: number
618
+ readonly doesResize: boolean
619
+ readonly canUnmount: boolean
620
+ readonly isAspectRatioLocked?: boolean
621
+ readonly overridePermissions?: TLEmbedShapePermissions
622
+ readonly instructionLink?: string
623
+ readonly backgroundColor?: string
624
+ // TODO: FIXME this is ugly be required because some embeds have their own border radius for example spotify embeds
625
+ readonly overrideOutlineRadius?: number
626
+ readonly toEmbedUrl: (url: string) => string | undefined
627
+ readonly fromEmbedUrl: (url: string) => string | undefined
628
+ }
629
+
630
+ const Versions = {
631
+ GenOriginalUrlInEmbed: 1,
632
+ RemoveDoesResize: 2,
633
+ RemoveTmpOldUrl: 3,
634
+ RemovePermissionOverrides: 4,
635
+ } as const
636
+
637
+ /** @internal */
638
+ export const embedShapeMigrations = defineMigrations({
639
+ currentVersion: Versions.RemovePermissionOverrides,
640
+ migrators: {
641
+ [Versions.GenOriginalUrlInEmbed]: {
642
+ // add tmpOldUrl property
643
+ up: (shape) => {
644
+ const url = shape.props.url
645
+ const host = new URL(url).host.replace('www.', '')
646
+ let originalUrl
647
+ for (const localEmbedDef of EMBED_DEFINITIONS) {
648
+ if ((localEmbedDef as EmbedDefinition).hostnames.includes(host)) {
649
+ try {
650
+ originalUrl = localEmbedDef.fromEmbedUrl(url)
651
+ } catch (err) {
652
+ console.warn(err)
653
+ }
654
+ }
655
+ }
656
+
657
+ return {
658
+ ...shape,
659
+ props: {
660
+ ...shape.props,
661
+ tmpOldUrl: shape.props.url,
662
+ url: originalUrl ?? '',
663
+ },
664
+ }
665
+ },
666
+ // remove tmpOldUrl property
667
+ down: (shape) => {
668
+ let newUrl = shape.props.tmpOldUrl
669
+ if (!newUrl || newUrl === '') {
670
+ const url = shape.props.url
671
+ const host = new URL(url).host.replace('www.', '')
672
+
673
+ for (const localEmbedDef of EMBED_DEFINITIONS) {
674
+ if ((localEmbedDef as EmbedDefinition).hostnames.includes(host)) {
675
+ try {
676
+ newUrl = localEmbedDef.toEmbedUrl(url)
677
+ } catch (err) {
678
+ console.warn(err)
679
+ }
680
+ }
681
+ }
682
+ }
683
+
684
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
685
+ const { tmpOldUrl, ...props } = shape.props
686
+ return {
687
+ ...shape,
688
+ props: {
689
+ ...props,
690
+ url: newUrl ?? '',
691
+ },
692
+ }
693
+ },
694
+ },
695
+ [Versions.RemoveDoesResize]: {
696
+ up: (shape) => {
697
+ const { doesResize: _, ...props } = shape.props
698
+ return {
699
+ ...shape,
700
+ props: {
701
+ ...props,
702
+ },
703
+ }
704
+ },
705
+ down: (shape) => {
706
+ return {
707
+ ...shape,
708
+ props: {
709
+ ...shape.props,
710
+ doesResize: true,
711
+ },
712
+ }
713
+ },
714
+ },
715
+ [Versions.RemoveTmpOldUrl]: {
716
+ up: (shape) => {
717
+ const { tmpOldUrl: _, ...props } = shape.props
718
+ return {
719
+ ...shape,
720
+ props: {
721
+ ...props,
722
+ },
723
+ }
724
+ },
725
+ down: (shape) => {
726
+ return {
727
+ ...shape,
728
+ props: {
729
+ ...shape.props,
730
+ },
731
+ }
732
+ },
733
+ },
734
+ [Versions.RemovePermissionOverrides]: {
735
+ up: (shape) => {
736
+ const { overridePermissions: _, ...props } = shape.props
737
+ return {
738
+ ...shape,
739
+ props: {
740
+ ...props,
741
+ },
742
+ }
743
+ },
744
+ down: (shape) => {
745
+ return {
746
+ ...shape,
747
+ props: {
748
+ ...shape.props,
749
+ },
750
+ }
751
+ },
752
+ },
753
+ },
754
+ })