Skip to content

Commit f9137f9

Browse files
committed
💡 refactor(presenter): altera forma com que metadados dos slides são lidos
1 parent c66049f commit f9137f9

37 files changed

+1256
-148
lines changed

Diff for: ‎apps/presenter/src/app/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
export * from './slide-loader'
22
export * from './data-binding'
3+
export * from './key-handler'
34
export * from './observable'
45
export * from './navigator'
6+
export * from './controls'
7+
export * from './metadata'
58
export * from './animator'
9+
export * from './toolbar'
610
export * from './router'
711
export * from './slide'

Diff for: ‎apps/presenter/src/app/metadata.ts

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import * as detectNewline from 'detect-newline'
2+
import { safeLoad } from 'js-yaml'
3+
4+
/**
5+
* Configuration that can be supplied by the user
6+
*/
7+
export interface MarkdownParserConfig {
8+
windows: boolean /** Specify whether to treat the string as though from a windows platform */
9+
}
10+
11+
export interface AnyMap {
12+
[x: string]: any
13+
}
14+
15+
export interface MarkdownData {
16+
metadata: AnyMap
17+
content: string
18+
}
19+
20+
/**
21+
* Internal Operation Configurations
22+
*/
23+
interface InternalConfig extends MarkdownParserConfig {
24+
source: string /** A string containing the markdown */
25+
isWin(): boolean
26+
}
27+
28+
/**
29+
* The configuration to be used for operation
30+
*/
31+
const config: InternalConfig = {
32+
source: '',
33+
windows: false,
34+
// infer the platform type by the eol present in the source string
35+
isWin: () => {
36+
const newLine = detectNewline(config.source)
37+
return config.windows || (!!newLine && newLine.match(/\r\n/) !== null)
38+
},
39+
}
40+
41+
Object.seal(config)
42+
43+
const METADATA_START = () => (config.isWin() ? /^---\r\n/ : /^---\n/)
44+
const METADATA_END = () => (config.isWin() ? /\r\n---\r\n/ : /\n---\n/)
45+
const METADATA_FILE_END = () => (config.isWin() ? /\r\n---$/ : /\n---$/)
46+
const JOIN_SEPARATOR = () => (config.isWin() ? '\r\n---\r\n' : '\n---\n')
47+
48+
/**
49+
* Check if the provided array has only one element that ends with METADATA_FILE_END.
50+
* If so, the source is metadata only with no content. The function cleans the metadata and adds an empty content element to the array.
51+
*
52+
* @param {Array}
53+
* @returns {Array}
54+
*/
55+
const checkMetadataOnly = (src: string[]) => {
56+
if (src.length === 1 && src[0].match(METADATA_FILE_END()) !== null) {
57+
return [src[0].replace(METADATA_FILE_END(), ''), '']
58+
}
59+
return src
60+
}
61+
62+
/**
63+
* Split a string with the METADATA_END separator if it starts with METADATA_START.
64+
* Otherwise it creates an array containing the source string provided.
65+
*
66+
* @param {string} Source string to split.
67+
* @returns {Array}
68+
*/
69+
const splitSource = (src: string) => {
70+
if (src.match(METADATA_START()) !== null) {
71+
return checkMetadataOnly(src.split(METADATA_END()))
72+
}
73+
return [src]
74+
}
75+
76+
/**
77+
* If source array has more than one value, it cleans (remove METADATA_START() and trim) and returns the first one.
78+
* Otherwise it returns null.
79+
*
80+
* @param {string[]} src
81+
* @returns {string|null}
82+
*/
83+
const cleanMetadata = (src: string[]) => {
84+
if (src.length >= 1) {
85+
return src[0].replace(METADATA_START(), '').trim()
86+
}
87+
return ''
88+
}
89+
90+
/**
91+
* If the supplied value is nil, it returns an empty object, otherwise it returns the value itself.
92+
*
93+
* @param {string} src
94+
* @returns {string | object}
95+
*/
96+
const emptyObjectIfNil = (src: string) => (src.length === 0 ? {} : src)
97+
98+
/**
99+
* Join the elements of the array except the first one (metadata).
100+
* If there's only one element (no metadata), it returns it.
101+
*
102+
* @param {string[]} srcLines
103+
* @returns {string}
104+
*/
105+
const joinContent = (srcLines: string[]) => {
106+
if (srcLines.length > 1) {
107+
return srcLines.slice(1, srcLines.length).join(JOIN_SEPARATOR())
108+
}
109+
return srcLines.join('')
110+
}
111+
112+
/**
113+
* Validate incoming input.
114+
*
115+
* @param {string} src Document source to parse.
116+
* @param {MarkdownParserConfig} config Operation configuration.
117+
*/
118+
119+
const validateInput = (src: string, config: MarkdownParserConfig) => {
120+
if (typeof src !== 'string') {
121+
throw new TypeError('Source parameter (src) must be a string.')
122+
}
123+
124+
if (Object.keys(config).length > 0 && typeof config.windows !== 'boolean') {
125+
throw new TypeError('Configuration property (windows) must be a boolean.')
126+
}
127+
}
128+
129+
/**
130+
* Parse a markdown document (src) looking for metadata in YAML format.
131+
* In order to be parsed, metadata must be placed at the beginning of the document between two triple dashes.
132+
* Example:
133+
* ---
134+
* title: Lorem ipsum
135+
* author: Marcus Antonius
136+
* keywords: latin, ipsum
137+
* ---
138+
*
139+
* NB: setting windows to true in configuration prop will override the ability
140+
* to infer the type from the document (src)
141+
*
142+
* @param {string} src Document source to parse.
143+
* @param {MarkdownParserConfig} config Operation configuration.
144+
* @returns {MarkdownData}
145+
* @throws {TypeError} src must be a string.
146+
* @throws {YAMLException} Error on YAML metadata parsing.
147+
*/
148+
export const parse = (
149+
src: string,
150+
userConfig: MarkdownParserConfig = config
151+
): MarkdownData => {
152+
validateInput(src, userConfig)
153+
154+
config.source = src.trim()
155+
156+
config.windows = userConfig.windows
157+
158+
const splittedSource = splitSource(config.source)
159+
160+
const cleanedMetadata = cleanMetadata(splittedSource)
161+
const parsedYaml = safeLoad(cleanedMetadata)
162+
const metaData = emptyObjectIfNil(parsedYaml)
163+
164+
const content = joinContent(splittedSource)
165+
166+
return {
167+
metadata: metaData,
168+
content: content,
169+
}
170+
}
171+
172+
export default parse

Diff for: ‎apps/presenter/src/app/slide-loader.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
1-
import { setOptions, Renderer, parse } from 'marked'
2-
import * as hljs from 'highlight.js'
31
import { Slide } from './slide'
42

5-
/**
6-
* Configura syntax highlight com
7-
* a biblioteca marked
8-
*/
9-
setOptions({
10-
renderer: new Renderer(),
11-
highlight: (code, lang) => {
12-
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
13-
return hljs.highlight(code, { language }).value
14-
},
15-
})
16-
173
/**
184
* Carrega os slides pouco a pouco (lazy load)
195
* @param {string} start nome do slide inicial
@@ -48,7 +34,8 @@ export async function loadSlides(start: string) {
4834
export async function load(name: string) {
4935
const response = await fetch(`assets/slides/${name}.md`)
5036
const slide = await response.text()
51-
return new Slide(parse(slide))
37+
return new Slide(slide)
38+
// return new Slide(parse(slide))
5239
}
5340
/**
5441
* Carrega slide em HTML, porém ainda

Diff for: ‎apps/presenter/src/app/slide.ts

+23-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
import { DataBinding } from './data-binding'
2+
import * as hljs from 'highlight.js'
3+
import { parse } from './metadata'
4+
import * as marked from 'marked'
5+
6+
/**
7+
* Configura syntax highlight com
8+
* a biblioteca marked
9+
*/
10+
marked.setOptions({
11+
renderer: new marked.Renderer(),
12+
highlight: (code, lang) => {
13+
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
14+
return hljs.highlight(code, { language }).value
15+
},
16+
})
217

318
export class Slide {
419
public _text: string
@@ -12,16 +27,19 @@ export class Slide {
1227
private _title: any
1328

1429
constructor(text: string) {
15-
this._text = text
30+
const { content, metadata } = parse(text)
31+
32+
this._text = content
1633

1734
this._context = {}
1835

1936
this._dataBinding = new DataBinding()
2037

2138
this._html = document.createElement('div')
22-
this._html.innerHTML = text
39+
this._html.innerHTML = marked.parse(content)
2340

24-
this._title = this._html.querySelector('h1')?.innerText
41+
this._title = metadata.title
42+
// this._html.querySelector('h1')?.innerText
2543
const transition = this._html.querySelectorAll<HTMLElement>('transition')
2644

2745
if (transition.length) {
@@ -30,10 +48,8 @@ export class Slide {
3048
this._transition = null
3149
}
3250

33-
const selector = 'a[title="next-slide"]'
34-
const hasNext = this._html.querySelector<HTMLAnchorElement>(selector)
35-
if (hasNext) {
36-
this._nextSlideName = this.getSlideByLink(hasNext)
51+
if (metadata.nextSlide) {
52+
this._nextSlideName = metadata.nextSlide
3753
} else {
3854
this._nextSlideName = null
3955
}

0 commit comments

Comments
 (0)