1
1
import { Plugin } from 'obsidian' ;
2
+ import { nanoid } from 'nanoid' ;
2
3
3
4
type GistJSON = {
4
5
description : string ,
@@ -10,8 +11,13 @@ type GistJSON = {
10
11
stylesheet : string
11
12
}
12
13
14
+ const pluginName = "obsidian-gist"
15
+ const obsidianAppOrigin = 'app://obsidian.md'
16
+
13
17
export default class GistPlugin extends Plugin {
14
18
async onload ( ) {
19
+ this . _injectContainerHeightAdjustmentScript ( )
20
+
15
21
this . registerMarkdownCodeBlockProcessor ( "gist" , async ( sourceString : string , el , ctx ) => {
16
22
const gists = sourceString . trim ( ) . split ( "\n" )
17
23
@@ -23,21 +29,20 @@ export default class GistPlugin extends Plugin {
23
29
} ) ;
24
30
}
25
31
26
- onunload ( ) {
27
- }
28
-
29
32
// private
30
33
31
- async _processGist ( el : HTMLElement , gist : string ) {
34
+ async _processGist ( el : HTMLElement , gistString : string ) {
32
35
const pattern = / (?< protocol > h t t p s ? : \/ \/ ) ? (?< host > g i s t \. g i t h u b \. c o m \/ ) ? ( (?< username > \w + ) \/ ) ? (?< gistID > \w + ) ( \# (?< filename > .+ ) ) ? /
33
36
34
- const matchResult = gist . match ( pattern ) . groups
37
+ const matchResult = gistString . match ( pattern ) . groups
35
38
36
- if ( matchResult . gistID === undefined ) {
37
- return this . _showError ( el , gist )
39
+ const gistID = matchResult . gistID
40
+
41
+ if ( gistID === undefined ) {
42
+ return this . _showError ( el , gistString , `Could not found a valid Gist ID, please make sure your content and format is correct.` )
38
43
}
39
44
40
- let gistURL = `https://gist.github.com/${ matchResult . gistID } .json`
45
+ let gistURL = `https://gist.github.com/${ gistID } .json`
41
46
42
47
if ( matchResult . filename !== undefined ) {
43
48
gistURL = `${ gistURL } ?file=${ matchResult . filename } `
@@ -47,22 +52,29 @@ export default class GistPlugin extends Plugin {
47
52
const response = await fetch ( gistURL )
48
53
49
54
if ( response . ok ) {
50
- const gistJSON = await response . json ( )
51
- return this . _insertGistElement ( el , gistJSON as GistJSON )
55
+ const gistJSON = await response . json ( ) as GistJSON
56
+ return this . _insertGistElement ( el , gistID , gistJSON )
52
57
} else {
53
- return this . _showError ( el , gist )
58
+ return this . _showError ( el , gistString , `Could not fetch the Gist info from GitHub server. (Code: ${ response . status } )` )
54
59
}
55
60
} catch ( error ) {
56
- return this . _showError ( el , gist )
61
+ return this . _showError ( el , gistString , `Could not fetch the Gist from GitHub server. (Error: ${ error } )` )
57
62
}
58
63
}
59
64
60
- async _insertGistElement ( el : HTMLElement , gistJSON : GistJSON ) {
65
+ async _insertGistElement ( el : HTMLElement , gistID : string , gistJSON : GistJSON ) {
66
+ // generate an uuid for each gist element
67
+ const gistUUID = `${ pluginName } -${ gistID } -${ nanoid ( ) } `
68
+
61
69
// container
62
70
const container = document . createElement ( 'iframe' ) ;
71
+ container . id = gistUUID
72
+ container . classList . add ( `${ pluginName } -container` )
73
+ container . setAttribute ( 'sandbox' , 'allow-scripts allow-top-navigation-by-user-activation' )
74
+ container . setAttribute ( 'loading' , 'lazy' )
63
75
64
- // auto adjust container height
65
- const innerStyle = `
76
+ // reset the default things on HTML
77
+ const resetStylesheet = `
66
78
<style>
67
79
html, body {
68
80
margin: 0;
@@ -72,36 +84,92 @@ export default class GistPlugin extends Plugin {
72
84
</style>
73
85
`
74
86
87
+ // height adjustment script
88
+ const heightAdjustmentScript = `
89
+ <script>
90
+ deliverHeightMessage = () => {
91
+ const contentHeight = document.body.scrollHeight;
92
+
93
+ top.postMessage({
94
+ sender: '${ pluginName } ',
95
+ gistUUID: '${ gistUUID } ',
96
+ contentHeight: contentHeight
97
+ }, '${ obsidianAppOrigin } ');
98
+ }
99
+
100
+ window.addEventListener('load', () => {
101
+ deliverHeightMessage();
102
+ })
103
+ </script>
104
+ `
105
+
75
106
// build stylesheet link
76
107
const stylesheetLink = document . createElement ( 'link' ) ;
77
108
stylesheetLink . rel = "stylesheet" ;
78
109
stylesheetLink . href = gistJSON . stylesheet
79
110
80
- // build link hacker
111
+ // hack to make links open in the parent
81
112
const parentLinkHack = document . createElement ( 'base' )
82
113
parentLinkHack . target = "_parent"
83
114
84
115
// Inject content into the iframe
85
116
container . srcdoc = `
86
- <head>
87
- ${ stylesheetLink . outerHTML }
88
- ${ parentLinkHack . outerHTML }
89
-
90
- ${ innerStyle }
91
- </head>
92
-
93
117
<html>
94
- ${ gistJSON . div }
118
+ <head>
119
+ <!-- hack -->
120
+ ${ resetStylesheet }
121
+ ${ parentLinkHack . outerHTML }
122
+ ${ heightAdjustmentScript }
123
+
124
+ <!-- gist style -->
125
+ ${ stylesheetLink . outerHTML }
126
+ </head>
127
+
128
+ <body>
129
+ ${ gistJSON . div }
130
+ </body>
95
131
</html>
96
132
`
97
- container . setAttribute ( 'sandbox' , 'allow-same-origin allow-top-navigation-by-user-activation' )
98
- container . setAttribute ( 'onload' , 'this.height=this.contentDocument.body.scrollHeight;' )
99
133
100
- // insert into the DOM
134
+ // insert container into the DOM
101
135
el . appendChild ( container )
102
136
}
103
137
104
- async _showError ( el : HTMLElement , gistIDAndFilename : String ) {
105
- el . createEl ( 'pre' , { text : `Failed to load the Gist (${ gistIDAndFilename } ).` } )
138
+ async _showError ( el : HTMLElement , gistIDAndFilename : String , errorMessage : String = '' ) {
139
+ const errorText = `
140
+ Failed to load the Gist (${ gistIDAndFilename } ).
141
+
142
+ Error:
143
+
144
+ ${ errorMessage }
145
+ ` . trim ( )
146
+
147
+ el . createEl ( 'pre' , { text : errorText } )
148
+ }
149
+
150
+ _injectContainerHeightAdjustmentScript ( ) {
151
+ const containerHeightAdjustmentScript = document . createElement ( 'script' )
152
+ containerHeightAdjustmentScript . id = `${ pluginName } -container-height-adjustment`
153
+ containerHeightAdjustmentScript . textContent = `
154
+ window.addEventListener("message", (messageEvent) => {
155
+ const sender = messageEvent.data.sender
156
+
157
+ if (messageEvent.origin !== 'null') {
158
+ // a message received from the iFrame with \`srcdoc\` attribute, the \`origin\` will be \`null\`.
159
+ return;
160
+ }
161
+
162
+ // only process message coming from this plugin
163
+ if (sender === '${ pluginName } ') {
164
+ const gistUUID = messageEvent.data.gistUUID
165
+ const contentHeight = messageEvent.data.contentHeight
166
+
167
+ const gistContainer = document.querySelector('iframe#' + gistUUID)
168
+ gistContainer.height = contentHeight
169
+ }
170
+ }, false)
171
+ `
172
+
173
+ document . head . appendChild ( containerHeightAdjustmentScript )
106
174
}
107
175
}
0 commit comments