Skip to content

Commit 622505a

Browse files
committed
refactor css typescript humanreadable
1 parent 24ddc04 commit 622505a

2 files changed

Lines changed: 198 additions & 33 deletions

File tree

src/humanReadablePane.css

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,129 @@
1-
.humanReadableFrame {
1+
.human-readable-pane {
2+
display: block;
3+
min-width: 0;
4+
width: 100%;
5+
max-width: 100%;
6+
box-sizing: border-box;
7+
}
8+
9+
.human-readable-pane__container {
10+
display: block;
11+
min-width: 0;
12+
width: 100%;
13+
max-width: 100%;
14+
box-sizing: border-box;
15+
overflow-x: hidden;
16+
}
17+
18+
.human-readable-pane__frame {
19+
display: block;
220
border: 1px solid;
3-
padding: var(--spacing-md, 1em);
4-
height: var(--human-readable-height, 30em);
5-
width: 800px;
21+
padding: 1rem;
22+
height: var(--human-readable-pane-height, 30rem);
23+
min-width: 0;
24+
max-width: 100%;
25+
width: 100%;
26+
box-sizing: border-box;
627
resize: both;
728
overflow: auto;
829
}
30+
31+
.human-readable-pane__frame--iframe {
32+
padding: 0;
33+
overflow: hidden;
34+
width: 100%;
35+
max-width: 100%;
36+
min-height: 18rem;
37+
background: var(--color-background, white);
38+
}
39+
40+
.human-readable-pane__frame--markdown {
41+
overflow-wrap: anywhere;
42+
word-break: break-word;
43+
}
44+
45+
.human-readable-pane__frame--plain-text {
46+
font-family: monospace;
47+
white-space: pre-wrap;
48+
overflow-wrap: anywhere;
49+
word-break: break-word;
50+
}
51+
52+
.human-readable-pane__frame--markdown > * {
53+
max-width: 100%;
54+
box-sizing: border-box;
55+
}
56+
57+
.human-readable-pane__frame--markdown img,
58+
.human-readable-pane__frame--markdown video,
59+
.human-readable-pane__frame--markdown canvas,
60+
.human-readable-pane__frame--markdown iframe {
61+
max-width: 100%;
62+
height: auto;
63+
}
64+
65+
.human-readable-pane__frame--markdown pre,
66+
.human-readable-pane__frame--markdown code {
67+
max-width: 100%;
68+
overflow-wrap: anywhere;
69+
word-break: break-word;
70+
}
71+
72+
.human-readable-pane__frame--markdown pre {
73+
white-space: pre-wrap;
74+
}
75+
76+
.human-readable-pane__frame--markdown table {
77+
display: block;
78+
width: 100%;
79+
max-width: 100%;
80+
overflow-x: auto;
81+
box-sizing: border-box;
82+
}
83+
84+
.human-readable-pane__frame--markdown th,
85+
.human-readable-pane__frame--markdown td {
86+
white-space: normal;
87+
overflow-wrap: anywhere;
88+
word-break: break-word;
89+
}
90+
91+
.human-readable-pane[data-layout='mobile'] .human-readable-pane__frame {
92+
padding: 0.75rem;
93+
resize: none;
94+
min-width: 0;
95+
}
96+
97+
.human-readable-pane[data-layout='mobile'] .human-readable-pane__frame--markdown,
98+
.human-readable-pane[data-layout='mobile'] .human-readable-pane__frame--plain-text {
99+
overflow-wrap: anywhere;
100+
word-break: break-word;
101+
}
102+
103+
.human-readable-pane[data-layout='mobile'] .human-readable-pane__frame--iframe {
104+
width: 100%;
105+
max-width: 100%;
106+
min-height: 16rem;
107+
height: min(var(--human-readable-pane-height, 30rem), 75vh);
108+
}
109+
110+
@media (max-width: 576px) {
111+
.human-readable-pane__frame {
112+
padding: 0.75rem;
113+
resize: none;
114+
min-width: 0;
115+
}
116+
117+
.human-readable-pane__frame--markdown,
118+
.human-readable-pane__frame--plain-text {
119+
overflow-wrap: anywhere;
120+
word-break: break-word;
121+
}
122+
123+
.human-readable-pane__frame--iframe {
124+
width: 100%;
125+
max-width: 100%;
126+
min-height: 16rem;
127+
height: min(var(--human-readable-pane-height, 30rem), 75vh);
128+
}
129+
}
Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,48 @@ import { icons, ns } from 'solid-ui'
77
import { Util } from 'rdflib'
88
import { marked } from 'marked'
99
import DOMPurify from 'dompurify'
10+
import type { DataBrowserContext } from 'pane-registry'
11+
import type { NamedNode } from 'rdflib'
1012
import './humanReadablePane.css'
1113

1214
// Helper function to check if a URI has a markdown file extension
13-
const isMarkdownFile = (uri) => {
15+
type DokieliCacheValue = 'dokieli' | 'html'
16+
17+
type HumanReadableIcon = string | Promise<string>
18+
19+
type HumanReadablePaneDefinition = {
20+
icon: (subject: NamedNode, context: DataBrowserContext) => HumanReadableIcon
21+
name: string
22+
label: (subject: NamedNode, context: DataBrowserContext) => 'view' | 'View' | null
23+
render: (subject: NamedNode, context: DataBrowserContext) => HTMLDivElement
24+
}
25+
26+
type RenderEnvironmentLike = {
27+
layout?: 'mobile' | 'desktop' | string
28+
}
29+
30+
const isMarkdownFile = (uri?: string | null): boolean => {
1431
if (!uri) return false
1532
const path = uri.split('?')[0].split('#')[0] // Remove query string and fragment
1633
return /\.(md|markdown|mdown|mkd|mkdn)$/i.test(path)
1734
}
1835

1936
// Cache for dokieli detection results (keyed by subject URI)
20-
const dokieliCache = new Map()
37+
const dokieliCache = new Map<string, DokieliCacheValue>()
38+
39+
function applyFrameClasses (
40+
frame: HTMLElement,
41+
modifier: 'markdown' | 'plain-text' | 'iframe',
42+
lines: number
43+
): void {
44+
frame.className = ''
45+
frame.classList.add('human-readable-pane__frame')
46+
frame.classList.add(`human-readable-pane__frame--${modifier}`)
47+
frame.style.setProperty('--human-readable-pane-height', `${lines}em`)
48+
}
2149

22-
const humanReadablePane = {
23-
icon: function (subject, context) {
50+
const humanReadablePane: HumanReadablePaneDefinition = {
51+
icon: function (subject: NamedNode, context: DataBrowserContext): HumanReadableIcon {
2452
// Markdown files detected by extension
2553
if (subject && isMarkdownFile(subject.uri)) {
2654
return icons.iconBase + 'markdown.svg'
@@ -78,7 +106,10 @@ const humanReadablePane = {
78106

79107
name: 'humanReadable',
80108

81-
label: function (subject, context) {
109+
label: function (
110+
subject: NamedNode,
111+
context: DataBrowserContext
112+
): 'view' | 'View' | null {
82113
const kb = context.session.store
83114

84115
// See also the source pane, which has lower precedence.
@@ -94,7 +125,11 @@ const humanReadablePane = {
94125
'video/mp4'
95126
]
96127

97-
const hasContentTypeIn = function (kb, x, displayables) {
128+
const hasContentTypeIn = function (
129+
kb: typeof context.session.store,
130+
x: NamedNode,
131+
displayables: string[]
132+
): boolean {
98133
const cts = kb.fetcher.getHeader(x, 'content-type')
99134
if (cts) {
100135
for (let j = 0; j < cts.length; j++) {
@@ -109,7 +144,11 @@ const humanReadablePane = {
109144
}
110145

111146
// This data could come from a fetch OR from ldp container
112-
const hasContentTypeIn2 = function (kb, x, displayables) {
147+
const hasContentTypeIn2 = function (
148+
kb: typeof context.session.store,
149+
x: NamedNode,
150+
displayables: string[]
151+
): boolean {
113152
const t = kb.findTypeURIs(x)
114153
for (let k = 0; k < displayables.length; k++) {
115154
if (Util.mediaTypeClass(displayables[k]).uri in t) {
@@ -157,11 +196,19 @@ const humanReadablePane = {
157196
return null
158197
},
159198

160-
render: function (subject, context) {
199+
render: function (
200+
subject: NamedNode,
201+
context: DataBrowserContext
202+
): HTMLDivElement {
161203
const myDocument = context.dom
162204
const div = myDocument.createElement('div')
163205
const kb = context.session.store
164206

207+
function applyEnvironmentAttributes (element: HTMLDivElement): void {
208+
const environment = (context.environment ?? {}) as RenderEnvironmentLike
209+
element.dataset.layout = environment.layout ?? 'desktop'
210+
}
211+
165212
const cts = kb.fetcher.getHeader(subject.doc(), 'content-type')
166213
const ct = cts ? cts[0].split(';', 1)[0].trim() : null // remove content-type parameters
167214

@@ -176,64 +223,61 @@ const humanReadablePane = {
176223
}
177224

178225
// @@ When we can, use CSP to turn off scripts within the iframe
179-
div.setAttribute('class', 'docView')
180-
div.setAttribute('style', 'display: block; width: 100%; max-width: 100%; box-sizing: border-box;')
226+
div.classList.add('human-readable-pane')
227+
applyEnvironmentAttributes(div)
181228

182229
// render markdown to html in a DIV element
183-
const renderMarkdownContent = function (frame) {
230+
const renderMarkdownContent = function (frame: HTMLDivElement) {
184231
kb.fetcher.webOperation('GET', subject.uri).then(response => {
185-
const markdownText = response.responseText
232+
const markdownText = response.responseText ?? ''
186233
const lines = Math.min(30, markdownText.split(/\n/).length + 5)
187-
const res = marked.parse(markdownText)
234+
const res = marked.parse(markdownText, { async: false })
188235
const clean = DOMPurify.sanitize(res)
189236
frame.innerHTML = clean
190-
frame.setAttribute('class', 'doc')
191-
frame.setAttribute('style', `display: block; border: 1px solid; padding: 1em; height: ${lines}em; max-width: 100%; width: 100%; box-sizing: border-box; resize: both; overflow: auto;`)
237+
applyFrameClasses(frame, 'markdown', lines)
192238
}).catch(error => {
193239
console.error('Error fetching markdown content:', error)
194240
frame.innerHTML = '<p>Error loading content</p>'
195241
})
196242
}
197243

198244
// render plain text in a PRE element
199-
const renderPlainTextContent = function (frame) {
245+
const renderPlainTextContent = function (frame: HTMLPreElement) {
200246
kb.fetcher.webOperation('GET', subject.uri).then(response => {
201-
const plainText = response.responseText
247+
const plainText = response.responseText ?? ''
202248
const lines = Math.min(30, plainText.split(/\n/).length + 5)
203249
frame.textContent = plainText
204-
frame.setAttribute('class', 'doc')
205-
frame.setAttribute('style', `display: block; border: 1px solid; padding: 1em; height: ${lines}em; max-width: 100%; width: 100%; box-sizing: border-box; resize: both; overflow: auto; font-family: monospace; white-space: pre-wrap; word-wrap: break-word;`)
250+
applyFrameClasses(frame, 'plain-text', lines)
206251
}).catch(error => {
207252
console.error('Error fetching plain text content:', error)
208253
frame.textContent = 'Error loading content'
209254
})
210255
}
211256

212-
const setIframeAttributes = (frame, lines) => {
257+
const setIframeAttributes = (frame: HTMLIFrameElement, lines: number) => {
213258
frame.setAttribute('src', subject.uri)
214-
frame.setAttribute('class', 'doc')
215-
frame.setAttribute('style', `display: block; border: 1px solid; padding: 1em; height: ${lines}em; max-width: 100%; width: 100%; box-sizing: border-box; resize: both; overflow: auto;`)
259+
applyFrameClasses(frame, 'iframe', lines)
216260
}
217261

218262
if (isMarkdown) {
219263
// For markdown, use a DIV element and render the content
220-
const frame = myDocument.createElement('DIV')
264+
const frame = myDocument.createElement('div')
221265
renderMarkdownContent(frame)
222266
const frameContainer = myDocument.createElement('div')
223-
frameContainer.setAttribute('style', 'display: block; width: 100%; max-width: 100%; box-sizing: border-box;')
267+
frameContainer.classList.add('human-readable-pane__container')
224268
frameContainer.appendChild(frame)
225269
div.appendChild(frameContainer)
226270
} else if (isPlainText) {
227271
// For plain text, use a PRE element and render the content
228-
const frame = myDocument.createElement('PRE')
272+
const frame = myDocument.createElement('pre')
229273
renderPlainTextContent(frame)
230274
const frameContainer = myDocument.createElement('div')
231-
frameContainer.setAttribute('style', 'display: block; width: 100%; max-width: 100%; box-sizing: border-box;')
275+
frameContainer.classList.add('human-readable-pane__container')
232276
frameContainer.appendChild(frame)
233277
div.appendChild(frameContainer)
234278
} else {
235279
// For other content types, use IFRAME
236-
const frame = myDocument.createElement('IFRAME')
280+
const frame = myDocument.createElement('iframe')
237281

238282
// Apply sandbox for HTML/XHTML
239283
if (ct === 'text/html' || ct === 'application/xhtml+xml') {
@@ -242,7 +286,7 @@ const humanReadablePane = {
242286

243287
// Fetch content to calculate lines dynamically
244288
kb.fetcher.webOperation('GET', subject.uri).then(response => {
245-
const blobText = response.responseText
289+
const blobText = response.responseText ?? ''
246290
const newLines = blobText.includes('<script src="https://dokie.li/scripts/dokieli.js">') ? -10 : 5
247291
const lines = Math.min(30, blobText.split(/\n/).length + newLines)
248292

@@ -259,7 +303,7 @@ const humanReadablePane = {
259303
})
260304

261305
const frameContainer = myDocument.createElement('div')
262-
frameContainer.setAttribute('style', 'display: block; width: 100%; max-width: 100%; box-sizing: border-box;')
306+
frameContainer.classList.add('human-readable-pane__container')
263307
frameContainer.appendChild(frame)
264308
div.appendChild(frameContainer)
265309
}

0 commit comments

Comments
 (0)