当前位置:网站首页>Analyse du principe de mise en œuvre d'un éditeur de texte open source markdown - to - rich
Analyse du principe de mise en œuvre d'un éditeur de texte open source markdown - to - rich
2022-06-13 04:21:00 【Xiaolin au coin de la rue】
L'auteur écrit habituellement des articles en utilisantMarkdown,Mais il y a des plateformes qui ne supportent pasMarkdownSituation,Le réarrangement n'est pas possible,Donc ils vont tous utiliserMarkdownOutils pour enrichir le texte,Par exemple,markdown-nice,Plus vous en utilisez, plus vous vous demandez comment,Et c'est là qu'il y a cet article..
markdown-niceEst basé surReactProjets construits,Regardez d'abord la page entière:

Une barre d'outils en haut,Trois zones parallèles au milieu,La zone d'édition、Zone d'aperçu、Personnaliser la zone du sujet,La zone de thème personnalisée est cachée par défaut.
En gros, c'est unMarkdownÉditeur,J'ai ajouté quelques adaptations pour chaque plate - forme.
Éditeur
L'éditeur utiliseCodeMirror,Plus précisément, un composant encapsulé secondaireReact-CodeMirror:
import CodeMirror from "@uiw/react-codemirror";
class App extends Component {
render() {
return (
<CodeMirror
value={this.props.content.content}
options={
{
theme: "md-mirror",// Sujet
keyMap: "sublime",// Raccourcis clavier
mode: "markdown",// Mode, C'est le type de langue
lineWrapping: true,// Activer le saut de ligne super long
lineNumbers: false,// Ne pas afficher le numéro de ligne
extraKeys: {// Configurer les raccourcis clavier
...bindHotkeys(this.props.content, this.props.dialog),
Tab: betterTab,
RightClick: rightClick,
},
}}
onChange={this.handleThrottleChange}
onScroll={this.handleScroll}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onDrop={this.handleDrop}
onPaste={this.handlePaste}
ref={this.getInstance}
/>
)
}
}
Raccourcis clavier、Les ordres
markdown-niceAdoptionextraKeys Options définir quelques raccourcis clavier , Des raccourcis ont également été ajoutés à la barre d'outils :

La logique de ces raccourcis clavier ou boutons de commande pour manipuler le contenu du texte est essentiellement cohérente , Obtenir d'abord le contenu de la circonscription actuelle :
const selected = editor.getSelection()
Ensuite, la modification de l'usinage est effectuée :
`**${
selected}**`
Remplacer le contenu de la dernière circonscription :
editor.replaceSelection(`**${
selected}**`)
Vous pouvez également modifier la position du curseur pour améliorer l'expérience , Par exemple, la position du curseur sera derrière le texte après l'opération de gras ,Au lieu de* C'est parce que markdown-nice La position du curseur a également été modifiée après le remplacement de la sélection :

export const bold = (editor, selection) => {
editor.replaceSelection(`**${
selection}**`);
const cursor = editor.getCursor();
cursor.ch -= 2;// Position du curseur deux caractères en avant
editor.setCursor(cursor);
};
Tableaux
Markdown La syntaxe de la table est difficile à écrire ,markdown-nice Pour les tables, seules les fonctions qui vous aident à insérer des symboles de syntaxe de table sont disponibles , Vous pouvez entrer le nombre de lignes et de lignes de tableau à insérer :

Insérer automatiquement un symbole après confirmation :

L'implémentation est en fait une logique d'épissage de chaîne :
const text = this.buildFormFormat(this.state.rowNum, this.state.columnNum);
buildFormFormat = (rowNum, columnNum) => {
let formFormat = "";
// Au moins trois lignes seront créées
for (let i = 0; i < 3; i++) {
formFormat += this.buildRow(i, columnNum);
}
// Plus de trois lignes
for (let i = 3; i <= rowNum; i++) {
formFormat += this.buildRow(i, columnNum);
}
return formFormat;
};
buildRow = (rowNum, columnNum) => {
let appendText = "|";
// Première ligne séparation de l'en - tête et du contenu
if (rowNum === 1) {
appendText += " --- |";
for (let i = 0; i < columnNum - 1; i++) {
appendText += " --- |";
}
} else {
appendText += " |";
for (let i = 0; i < columnNum - 1; i++) {
appendText += " |";
}
}
return appendText + (/windows|win32/i.test(navigator.userAgent) ? "\r\n" : "\n");
};
Remplacer le contenu actuel de la sélection après la génération des caractères de table :
handleOk = () => {
const {
markdownEditor} = this.props.content;
const cursor = markdownEditor.getCursor();
const text = this.buildFormFormat(this.state.rowNum, this.state.columnNum);
markdownEditor.replaceSelection(text);
cursor.ch += 2;
markdownEditor.setCursor(cursor);
markdownEditor.focus();
};
La position du curseur a également été modifiée et l'éditeur a été recentré .
Téléchargement d'images
markdown-nice Prise en charge du téléchargement direct d'images en faisant glisser et coller des images dans la zone d'édition , C'est en écoutant. CodeMirrorDe l'éditeurdropEtpasteÉvénement réalisé:
<CodeMirror
onDrop={this.handleDrop}
onPaste={this.handlePaste}
/>
handleDrop = (instance, e) => {
if (!(e.dataTransfer && e.dataTransfer.files)) {
return;
}
for (let i = 0; i < e.dataTransfer.files.length; i++) {
uploadAdaptor({
file: e.dataTransfer.files[i], content: this.props.content});
}
};
handlePaste = (instance, e) => {
if (e.clipboardData && e.clipboardData.files) {
for (let i = 0; i < e.clipboardData.files.length; i++) {
uploadAdaptor({
file: e.clipboardData.files[i], content: this.props.content});
}
}
}
Détermine si un fichier existe dans les données traînées ou collées et appelle uploadAdaptorMéthodes:
export const uploadAdaptor = (...args) => {
const type = localStorage.getItem(IMAGE_HOSTING_TYPE);
if (type === IMAGE_HOSTING_NAMES.aliyun) {
const config = JSON.parse(window.localStorage.getItem(ALIOSS_IMAGE_HOSTING));
if (
!config.region.length ||
!config.accessKeyId.length ||
!config.accessKeySecret.length ||
!config.bucket.length
) {
message.error(" Veuillez d'abord configurer Alibaba Cloud Drawing Bed ");
return false;
}
return aliOSSUpload(...args);
}
}
Autres types de lits omis ,Alibaba CloudOSSPar exemple, Vérifiez d'abord si la configuration pertinente existe , S'il existe, il appelle aliOSSUploadMéthodes:
import OSS from "ali-oss";
export const aliOSSUpload = ({
file = {
},
onSuccess = () => {
},
onError = () => {
},
images = [],
content = null, // store content
}) => {
const config = JSON.parse(window.localStorage.getItem(ALIOSS_IMAGE_HOSTING));
// Convertir le type de fichier en base64Type
const base64Reader = new FileReader();
base64Reader.readAsDataURL(file);
base64Reader.onload = (e) => {
const urlData = e.target.result;
const base64 = urlData.split(",").pop();
// Obtenir le type de fichier
const fileType = urlData
.split(";")
.shift()
.split(":")
.pop();
// base64Tourne.blob
const blob = toBlob(base64, fileType);
// blobTourne.arrayBuffer
const bufferReader = new FileReader();
bufferReader.readAsArrayBuffer(blob);
bufferReader.onload = (event) => {
const buffer = new OSS.Buffer(event.target.result);
aliOSSPutObject({
config, file, buffer, onSuccess, onError, images, content});
};
};
};
Cette étape consiste principalement à convertir les types de fichiers en arrayBufferType,Enfin, il appellealiOSSPutObject Effectuer une opération de téléchargement de fichiers :
const aliOSSPutObject = ({
config, file, buffer, onSuccess, onError, images, content}) => {
let client = new OSS(config);
// Télécharger le nom du fichier à l'heure actuelle
const OSSName = getOSSName(file.name);
// Effectuer une opération de téléchargement
client
.put(OSSName, buffer)
.then((response) => {
const names = file.name.split(".");
names.pop();
const filename = names.join(".");
const image = {
filename, // Nom inchangé et suffixe supprimé
url: response.url,
};
// Insérer dans le document
if (content) {
writeToEditor({
content, image});
}
})
.catch((error) => {
console.log(error);
});
};
Une fois téléchargé avec succès, l'image sera insérée dans le document :
function writeToEditor({
content, image}) {
const isContainImgName = window.localStorage.getItem(IS_CONTAIN_IMG_NAME) === "true";
let text = "";
// Avec ou sans nom de fichier
if (isContainImgName) {
text = `\n\n`;
} else {
text = `\n\n`;
}
const {
markdownEditor} = content;
// Remplacer la circonscription actuelle
const cursor = markdownEditor.getCursor();
markdownEditor.replaceSelection(text, cursor);
content.setContent(markdownEditor.getValue());
}
La logique de téléchargement spécifique d'autres grandes plateformes peut se référer au code source :imageHosting.js.
FormatageMarkdown
markdown-nice Support de formatage MarkdownLa fonction de, C'est - à - dire la fonction d'embellissement ,Par exemple,:

Après embellissement:

Le format utilise prettier:
import prettier from "prettier/standalone";
import prettierMarkdown from "prettier/parser-markdown";
export const formatDoc = (content, store) => {
content = handlePrettierDoc(content);
// Pour ceux enveloppés en chinois `$` Ajouter des espaces avant et après le symbole
content = content.replace(/([\u4e00-\u9fa5])\$/g, "$1 $");
content = content.replace(/\$([\u4e00-\u9fa5])/g, "$ $1");
store.setContent(content);
message.success(" Formatage du document terminé !");
};
// AppelezprettierFormatage
const handlePrettierDoc = (content) => {
const prettierRes = prettier.format(content, {
parser: "markdown",
plugins: [prettierMarkdown],
});
return prettierRes;
};
Aperçu
L'aperçu est MarkdownConvertir enhtmlAfficher, La zone d'aperçu n'a besoin que d'un seul élément conteneur ,Par exemple,div,Ensuite, vous convertissezhtmlUtilisation du contenudiv.innerHTML = html La méthode peut être ajoutée .
ActuellementMarkdownConvertir enhtml Il y a beaucoup de bibliothèques open source pour ,Par exemple,markdown-it、marked、showdown,markdown-niceUtilisémarkdown-it.
Code de base:
const parseHtml = markdownParser.render(this.props.content.content);
return (
<section
dangerouslySetInnerHTML={
{
__html: parseHtml,
}}
/>
)
markdownParserC'est - à - dire:markdown-itExemple:
import MarkdownIt from "markdown-it";
export const markdownParser = new MarkdownIt({
html: true,// Autoriser la présence dans le code source HTMLÉtiquettes
highlight: (str, lang) => {
// Logique de mise en évidence du Code ,Regarde derrière.
},
});
Plug - in
Création terminéeMarkdownItAprès l'exemple de, Et beaucoup de plugins ont été enregistrés :
markdownParser
.use(markdownItSpan) // Ajouter à l'étiquette de titre span
.use(markdownItTableContainer) // Ajouter un conteneur en dehors du tableau
.use(markdownItMath) // Formule mathématique
.use(markdownItLinkfoot) // Modifier la note de bas de page
.use(markdownItTableOfContents, {
transformLink: () => "",
includeLevel: [2, 3],
markerPattern: /^\[toc\]/im,
}) // TOC Seuls les titres des niveaux 2 et 3 sont pris en charge
.use(markdownItRuby) // Symbole phonétique
.use(markdownItImplicitFigures, {
figcaption: true}) // Illustration
.use(markdownItDeflist) // Liste des définitions
.use(markdownItLiReplacer) // li Ajouter p Étiquettes
.use(markdownItImageFlow) // Module de déplacement de l'écran horizontal
.use(markdownItMultiquote) // Ajouter une référence à plusieurs niveaux class
.use(markdownItImsize);
Les notes fonctionnelles du plug - in reflètent également .
markdown-it Ça va entrer. Markdown Chaîne à chaîne token,Et puis selontokenGénérerhtmlString,Par exemple,# Xiaolin au coin de la rue Il en résulte ce qui suit: tokenListe( Supprimer certains champs ):
[
{
"type": "heading_open",
"tag": "h1",
"nesting": 1,
"level": 0,
"children": null,
"content": "",
"markup": "#",
"info": "",
"block": true,
},
{
"type": "inline",
"tag": "",
"nesting": 0,
"level": 1,
"children": [
{
"type": "text",
"tag": "",
"nesting": 0,
"level": 0,
"children": null,
"content": "Xiaolin au coin de la rue",
"markup": "",
"info": "",
"block": false,
}
],
"content": "Xiaolin au coin de la rue",
"markup": "",
"info": "",
"block": true,
},
{
"type": "heading_close",
"tag": "h1",
"nesting": -1,
"level": 0,
"children": null,
"content": "",
"markup": "#",
"info": "",
"block": true
}
]
Inmarkdown-itInterne, Chaque travail est fait individuellement rules, C'est une fonction après l'autre. ,AnalytiquerulesDivisé en trois catégories:core、block、inline.
coreContientnormalize、block、inline、linkify、replacements、smartquotesCes règles, Ça va nous arriver markdown Les chaînes exécutent les règles ci - dessus dans l'ordre , Qui contient blockEtinlnie Procédure d'exécution des règles de type ,blockEtinline Les règles pertinentes sont utilisées pour générer un tokenDe,Comme son nom l'indique, Un token,Comme le titre、Bloc de code、Tableaux、 Liste des articles, etc , Un qui est responsable de la génération de types inline après la génération d'éléments au niveau du bloc token,Comme le texte、Liens、Photos, etc..
block Le temps d'exécution est scanné ligne par ligne markdownString, Tous les niveaux de bloc sont exécutés à tour de rôle pour chaque ligne de chaîne ruleFonctions, Résoudre le niveau du bloc de construction token,IntégréblockIl y a des règles.table、code、fence、blockquote、hr、list、heading、paragraphAttendez..
In block Après le traitement des règles de type , Il pourrait en résulter un type Pour inline De token,Ce genre de token Appartient à un token,Il faut donc passer parinlineTypetokenEncore une fois., C'est - à - dire au niveau du bloc tokenDecontent Champs les caractères enregistrés sont analysés pour générer en ligne token,IntégréinlineIl y a des règles.text、link、imageAttendez..
Une fois ces règles d'analyse terminées, un tokenTableau,Encore une fois.render Génération de règles connexes htmlString,Donc unmarkdown-it Si le plug - in veut intervenir token, Alors, mettez - le à jour. 、Extension、 Ajouter différents types de résolution rule, Si vous souhaitez intervenir sur la base de tokenProduithtml, Alors, mettez - le à jour. 、Extension、Ajouter un rendurule.
Ce qui précède n'est qu'une brève introduction , Pour en savoir plus, lisez markdown-it Code source ou deux séries d'articles ci - dessous :
markdown-itAnalyse des sources1-Processus global、markdown-itSérie d'articles
markdown-nice Tant de plugins utilisés , Certains sont communautaires , Certains ont été écrits par eux - mêmes , Ensuite, regardons deux des plus simples .
1.markdownItMultiquote
function makeRule() {
return function addTableContainer(state) {
let count = 0;
let outerQuoteToekn;
for (var i = 0; i < state.tokens.length; i++) {
// Traverser touttoken
const curToken = state.tokens[i];
// Rencontreblockquote_openTypetoken
if (curToken.type === "blockquote_open") {
if (count === 0) {
// Couche externe blockquote De token
outerQuoteToekn = curToken;
}
count++;
continue;
}
if (count > 0) {
// Ajouter un nom de classe à la couche externe
outerQuoteToekn.attrs = [["class", "multiquote-" + count]];
count = 0;
}
}
};
}
export default (md) => {
// Ajouter une règle personnalisée sous la règle de base
md.core.ruler.push("blockquote-class", makeRule(md));
};
Ce plugin est simple, C'est quand il y a plusieurs niveaux de nidification blockquote Pour la couche externe blockquote tokenAjouter un nom de classe,Les effets sont les suivants:

2.markdownItLiReplacer
function makeRule(md) {
return function replaceListItem() {
// Deux règles de rendu sont écrasées
md.renderer.rules.list_item_open = function replaceOpen() {
return "<li><section>";
};
md.renderer.rules.list_item_close = function replaceClose() {
return "</section></li>";
};
};
}
export default (md) => {
md.core.ruler.push("replace-li", makeRule(md));
};
Ce plugin est plus simple , Overrided Built - in list_itemLes règles, L'effet est li Il y en a un dans l'étiquette. sectionÉtiquettes.
Note de bas de page
Nous savons tous que la plus grande limite pour les numéros publics est que les hyperliens ne sont autorisés que sur la liste blanche. , Tout le reste sera filtré. , Donc si vous ne faites rien , On n'a plus d'hyperliens. , La solution est généralement de passer à une note de bas de page , À la fin de l'article ,markdown-nice La logique est compliquée. , Changez d'abord MarkdownContenu,Oui.:
[Laboratoire idéal pour les jeunes](http://lxqnsys.com/)
Formaté comme:
[Laboratoire idéal pour les jeunes](http://lxqnsys.com/ "Laboratoire idéal pour les jeunes")
C'est - à - dire ajouter le titre ,Et ensuite passermarkdown-itTraitement des plug - instoken, Générer une note de bas de page :
markdownParser
.use(markdownItLinkfoot) // Modifier la note de bas de page
L'implémentation de ce plugin est également assez complexe , Les sources intéressantes peuvent être lues :markdown-it-linkfoot.js.
En fait, nous pouvons choisir une autre façon de penser plus simple , On peut écraser markdown-it Liens internes tokenRègles de rendu, Recueillir toutes les données liées en même temps , Enfin, nous le faisons nous - mêmes. htmlLa chaîne est attelée àmarkdown-itSortiehtmlSur la chaîne.
Par exemple, nous créons unmarkdownItLinkfoot2Plug - in,Inscription:
// Utilisé pour collecter tous les liens
export const linkList = []
markdownParser
.use(markdownItLinkfoot2, linkList)
Passez le tableau des liens de collection au plug - in via l'option , Ensuite, le Code du plug - in :
function makeRule(md, linkList) {
return function() {
// Videz les tableaux et les compteurs avant chaque resolve
linkList.splice(0, linkList.length)
let index = 0
let isWeChatLink = false
// Écrasera Ouverture de l'étiquette tokenRègles de rendu
md.renderer.rules.link_open = function(tokens, idx) {
// Obtenir le couranttoken
let token = tokens[idx]
// Obtenir le lien url
let href = token.attrs[0] ? token.attrs[0][1] : ''
// Dans le cas d’un nom de domaine Wechat, aucune conversion n’est nécessaire
if (/^https:\/\/mp.weixin.qq.com\//.test(href)) {
isWeChatLink = true
return `<a href="${
href}">`
}
// Suivi d'autres dans le lien token, Nous pouvons parcourir pour trouver le type de texte token Comme titre de lien
token = tokens[++idx]
let title = ''
while(token.type !== 'link_close') {
if (token.type === 'text') {
title = token.content
break
}
token = tokens[++idx]
}
// Ajouter un lien au tableau
linkList.push({
href,
title
})
// En même temps, nous mettonsaRemplacer l'étiquette parspanÉtiquettes
return "<span>";
};
// Écrasera Étiquette fermée de l'étiquette tokenRègles de rendu
md.renderer.rules.link_close = function() {
if (isWeChatLink) {
return "</a>"
}
// Nous ajouterons un superscript après le nom du lien , Note de bas de page en son nom , L'exposant est l'index
index++
return `<sup>[${
index}]</sup></span>`;
};
};
}
export default (md, linkList) => {
// Ajouter nos règles personnalisées à la chaîne de règles de base
md.core.ruler.push("change-link", makeRule(md, linkList));
};
Ensuite, nous générons nos propres notes de bas de page htmlString,Et le raccorder àmarkdown-it Sortie après analyse htmlSur la chaîne :
let parseHtml = markdownParser.render(this.props.content.content);
if (linkList.length > 0) {
let linkFootStr = '<div>Liens de référence:</div>'
linkList.forEach((item, index) => {
linkFootStr += `<div>[${
index + 1}] ${
item.title}:${
item.href}</div>`
})
parseHtml += linkFootStr
}
Les effets sont les suivants:

Affinez le style. .
Défilement synchrone
Le défilement synchrone de la zone d'édition et de la zone d'aperçu est une fonctionnalité de base , Lier d'abord la souris à l'événement , Cela permet de déterminer dans quelle zone la souris a déclenché le défilement :
// Éditeur
<div id="nice-md-editor" onMouseOver={(e) => this.setCurrentIndex(1, e)}></div>
// Zone d'aperçu
<div id="nice-rich-text" onMouseOver={(e) => this.setCurrentIndex(2, e)}></div>
setCurrentIndex(index) {
this.index = index;
}
Puis lier l'événement de défilement :
// Éditeur
<CodeMirror onScroll={this.handleScroll}></CodeMirror>
// Conteneur de zone d'aperçu
<div
id={BOX_ID}
onScroll={this.handleScroll}
ref={(node) => {
this.previewContainer = node;
}}
>
// Zone d'aperçu
<section
id={LAYOUT_ID}
dangerouslySetInnerHTML={
{
__html: parseHtml,
}}
ref={(node) => {
this.previewWrap = node;
}}
</section>
</div>
handleScroll = () => {
if (this.props.navbar.isSyncScroll) {
const {
markdownEditor} = this.props.content;
const cmData = markdownEditor.getScrollInfo();
// Distance de défilement de l'éditeur
const editorToTop = cmData.top;
// Hauteur de défilement de l'éditeur
const editorScrollHeight = cmData.height - cmData.clientHeight;
// scale = Hauteur de défilement de la zone d'aperçu / Hauteur de défilement de l'éditeur
this.scale = (this.previewWrap.offsetHeight - this.previewContainer.offsetHeight + 55) / editorScrollHeight;
// scale = Distance de défilement de la zone d'aperçu / Distance de défilement de l'éditeur = this.previewContainer.scrollTop / editorToTop
if (this.index === 1) {
// La souris déclenche le défilement sur l'éditeur , La zone d'aperçu suit le défilement
this.previewContainer.scrollTop = editorToTop * this.scale;
} else {
// La souris déclenche le défilement dans la zone d'aperçu , L'éditeur suit le défilement
this.editorTop = this.previewContainer.scrollTop / this.scale;
markdownEditor.scrollTo(null, this.editorTop);
}
}
};
Le calcul est simple., Le rapport de la distance de roulis entre les deux zones est égal au rapport de la distance de roulis entre les deux zones. , Calculer la distance de roulement pour l'une de ces zones , Mais ce calcul n'est pas très précis , Surtout quand il y a beaucoup d'images :

Comme vous pouvez le voir dans l'image ci - dessus, les éditeurs défilent vers 4.2Sous - section, Et la zone d'aperçu 4.2 Je ne vois même pas les barres .
Pour résoudre ce problème, il ne suffit pas de calculer la hauteur. , Il faut pouvoir faire correspondre les éléments des deux côtés ,Détails prévus, Voir un autre article de l'auteur : Comment réaliser un défilement synchrone précis MarkdownÉditeur.
Sujet
Le sujet est essentiellement cssStyles,markdownConvertir enhtml Il n'y a pas beaucoup d'étiquettes impliquées , Il suffit d'énumérer tous les styles personnalisés .
markdown-nice D'abord créé quatre styleÉtiquettes:

1.basic-theme
Thème de base , Définit un ensemble de styles par défaut , Le contenu du style peut être trouvé à basic.jsAffichage des fichiers.
2.markdown-theme
Utilisé pour insérer le style de thème sélectionné , C'est pour couvrir basic-themeStyles pour, Les styles de thème personnalisés sont également insérés dans cette étiquette :

3.font-theme
Utilisé pour insérer spécifiquement des styles de police , Ça correspond à cette fonction :

// Police de serif Et Polices sans serif Basculer
toggleFont = () => {
const {
isSerif} = this.state;
const serif = `#nice { font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; }`;
const sansSerif = `#nice { font-family: Roboto, Oxygen, Ubuntu, Cantarell, PingFangSC-light, PingFangTC-light, 'Open Sans', 'Helvetica Neue', sans-serif; }`;
const choosen = isSerif ? serif : sansSerif;
replaceStyle(FONT_THEME_ID, choosen);
message.success(" Police commutée avec succès !");
this.setState({
isSerif: !isSerif});
};
4.code-theme
Comme son nom l'indique, Correspond au style utilisé pour insérer le bloc de code ,markdown-itOffre unhighlight Options pour configurer la mise en évidence du bloc de code ,Fournit une fonction, Recevoir les caractères du Code et le type de langue ,Retour à unhtmlFragment, Peut également être emballé pre Retour après l'étiquette ,Voilà.markdown-it Il n'y aura plus de traitement interne. .
markdown-niceUtiliséhighlight.js Pour mettre en évidence le Code :
export const markdownParser = new MarkdownIt({
html: true,
highlight: (str, lang) => {
if (lang === undefined || lang === "") {
lang = "bash";
}
// Pluscustom Représente un style personnalisé , Pas exclusivement Wechat ,Évitez d'êtreremove pre
if (lang && highlightjs.getLanguage(lang)) {
try {
const formatted = highlightjs
.highlight(lang, str, true)
.value.replace(/\n/g, "<br/>") // Nouvelle lignebrReprésentation
.replace(/\s/g, " ") // AvecnbspRemplacer les espaces
.replace(/span /g, "span "); // span Réparation de l'étiquette
return '<pre class="custom"><code class="hljs">' + formatted + "</code></pre>";
} catch (e) {
console.log(e);
}
}
// escapeHtml La méthode s'échappe htmlPlanté &<>" Les caractères
return '<pre class="custom"><code class="hljs">' + markdownParser.utils.escapeHtml(str) + "</code></pre>";
},
});

highlight.jsBeaucoup de sujets sont intégrés:styles,markdown-nice J'ai choisi 6Espèce:

Et il soutientmacStyle,La différence, c'est quemac Styles ajoutés :

Copie en un clic
markdown-nice Trois boutons copiés en une seule touche ,Respectivement.Numéro public、Oui.、Nuggets, Nugget est maintenant l'éditeur lui - même markdownDe, Donc, nous ignorons directement .
Numéro public:
copyWechat = () => {
const layout = document.getElementById(LAYOUT_ID); // Protection du site
const html = layout.innerHTML;
solveWeChatMath();
this.html = solveHtml();
copySafari(this.html);
message.success("Copié, Veuillez coller sur la plateforme publique Wechat ");
layout.innerHTML = html; // Restauration du site
};
Oui.:
copyZhihu = () => {
const layout = document.getElementById(LAYOUT_ID); // Protection du site
const html = layout.innerHTML;
solveZhihuMath();
this.html = solveHtml();
copySafari(this.html);
message.success("Copié, Veuillez coller ");
layout.innerHTML = html; // Restauration du site
};
La principale différence est que solveWeChatMathEtsolveZhihuMathMéthodes, Ces deux méthodes sont utilisées pour résoudre le problème de la formule .markdown-niceUtiliserMathJax Pour rendre une formule (Regardez par vous - mêmes,L'auteur a raison.MathJaxPas familier,Je ne comprends pas~):
try {
window.MathJax = {
tex: {
inlineMath: [["\$", "\$"]],// Début de la formule en ligne /Séparateur d'extrémité
displayMath: [["\$\$", "\$\$"]],// Début de la formule au niveau du bloc /Séparateur d'extrémité
tags: "ams",
},
svg: {
fontCache: "none",// Ne pas mettre en cachesvgChemin, Ne pas réutiliser
},
options: {
renderActions: {
addMenu: [0, "", ""],
addContainer: [
190,
(doc) => {
for (const math of doc.math) {
this.addContainer(math, doc);
}
},
this.addContainer,
],
},
},
};
require("mathjax/es5/tex-svg-full");
} catch (e) {
console.log(e);
}
addContainer(math, doc) {
const tag = "span";
const spanClass = math.display ? "span-block-equation" : "span-inline-equation";
const cls = math.display ? "block-equation" : "inline-equation";
math.typesetRoot.className = cls;
math.typesetRoot.setAttribute(MJX_DATA_FORMULA, math.math);
math.typesetRoot.setAttribute(MJX_DATA_FORMULA_TYPE, cls);
math.typesetRoot = doc.adaptor.node(tag, {
class: spanClass, style: "cursor:pointer"}, [math.typesetRoot]);
}
// Après la mise à jour du contenu, appelez la méthode suivante pour rendre à nouveau la formule
export const updateMathjax = () => {
window.MathJax.texReset();
window.MathJax.typesetClear();
window.MathJax.typesetPromise();
};
Formule convertie htmlLa structure est la suivante::

L'éditeur de numéro public ne supporte pas les formules , Donc C'est par insertion directe svg:
export const solveWeChatMath = () => {
const layout = document.getElementById(LAYOUT_ID);
// Obtenir toutes les étiquettes de formule
const mjxs = layout.getElementsByTagName("mjx-container");
for (let i = 0; i < mjxs.length; i++) {
const mjx = mjxs[i];
if (!mjx.hasAttribute("jax")) {
break;
}
// Supprimermjx-container Quelques propriétés sur l'étiquette
mjx.removeAttribute("jax");
mjx.removeAttribute("display");
mjx.removeAttribute("tabindex");
mjx.removeAttribute("ctxtmenu_counter");
// Le premier noeud est svgNoeud
const svg = mjx.firstChild;
// Oui.svg La largeur et la hauteur définies par l'attribut sont changées en styles
const width = svg.getAttribute("width");
const height = svg.getAttribute("height");
svg.removeAttribute("width");
svg.removeAttribute("height");
svg.style.width = width;
svg.style.height = height;
}
};
L'éditeur supporte les formules , Donc, il va directement corréler la formule htmlRemplacer par:imgÉtiquettes:
export const solveZhihuMath = () => {
const layout = document.getElementById(LAYOUT_ID);
const mjxs = layout.getElementsByTagName("mjx-container");
while (mjxs.length > 0) {
const mjx = mjxs[0];
let data = mjx.getAttribute(MJX_DATA_FORMULA);
if (!data) {
continue;
}
if (mjx.hasAttribute("display") && data.indexOf("\\tag") === -1) {
data += "\\\\";
}
// Remplacer l'étiquette entière de la formule
mjx.outerHTML = '<img class="Formula-image" data-eeimg="true" src="" alt="' + data + '">';
}
};
Après le traitement de la formule, l'exécution suivante se produit solveHtmlMéthodes:
import juice from "juice";
export const solveHtml = () => {
const element = document.getElementById(BOX_ID);
let html = element.innerHTML;
// Remplacer l'étiquette du contenant de la formule par span
html = html.replace(/<mjx-container (class="inline.+?)<\/mjx-container>/g, "<span $1</span>");
// Remplacer les espaces par
html = html.replace(/\s<span class="inline/g, ' <span class="inline');
// Ibid.
html = html.replace(/svg><\/span>\s/g, "svg></span> ");
// Cette étiquette a été remplacée. , Pourquoi le remplacer ici?
html = html.replace(/mjx-container/g, "section");
html = html.replace(/class="mjx-solid"/g, 'fill="none" stroke-width="70"');
// Supprimer la formule mjx-assistive-mmlÉtiquettes
html = html.replace(/<mjx-assistive-mml.+?<\/mjx-assistive-mml>/g, "");
// Obtient les styles à l'intérieur des quatre étiquettes de style
const basicStyle = document.getElementById(BASIC_THEME_ID).innerText;
const markdownStyle = document.getElementById(MARKDOWN_THEME_ID).innerText;
const codeStyle = document.getElementById(CODE_THEME_ID).innerText;
const fontStyle = document.getElementById(FONT_THEME_ID).innerText;
let res = "";
try {
// Utiliserjuice La Bibliothèque insère les styles dans htmlSur l'étiquette
res = juice.inlineContent(html, basicStyle + markdownStyle + codeStyle + fontStyle, {
inlinePseudoElements: true,// Insérer un pseudo - élément , La méthode est de convertir spanÉtiquettes
preserveImportant: true,// Tiens bon.!import
});
} catch (e) {
message.error("Veuillez vérifier. CSS Si le document a été écrit correctement !");
}
return res;
};
Cette étape consiste principalement à remplacer l'étiquette associée à la formule , Puis j'ai pris les styles dans les quatre étiquettes de style , L'étape la plus critique est l'utilisation finale juice Le style a été incrusté dans htmlDans l'étiquette, Donc les styles sont séparés lors de l'aperçu , Mais finalement, les données que nous avons copiées étaient stylisées :


htmlTerminé, L'opération copier dans le presse - papiers se termine copySafari:
export const copySafari = (text) => {
// Accès input
let input = document.getElementById("copy-input");
if (!input) {
// input Ça ne marche pas CSS Cacher, Doit exister dans la page .
input = document.createElement("input");
input.id = "copy-input";
input.style.position = "absolute";
input.style.left = "-1000px";
input.style.zIndex = "-1000";
document.body.appendChild(input);
}
// Jean input Sélectionner un caractère , Peu importe ce caractère
input.value = "NOTHING";
input.setSelectionRange(0, 1);
input.focus();
// Copie déclenchée
document.addEventListener("copy", function copyCall(e) {
e.preventDefault();
e.clipboardData.setData("text/html", text);
e.clipboardData.setData("text/plain", text);
document.removeEventListener("copy", copyCall);
});
document.execCommand("copy");
};
Exporter sousPDF
Exporter sousPDF La fonction est en fait réalisée par la fonction d'impression ,C'est - à - dire l'appel:
window.print();

Vous pouvez voir que le contenu imprimé n'est que la zone d'aperçu ,Comment est - ce possible?,C'est simple.,Accès aux médias, Masquer d'autres éléments qui n'ont pas besoin d'être imprimés en mode impression :
@media print {
.nice-md-editing {
display: none;
}
.nice-navbar {
display: none;
}
.nice-sidebar {
display: none;
}
.nice-wx-box {
overflow: visible;
box-shadow: none;
width: 100%;
}
.nice-style-editing {
display: none;
}
#nice-rich-text {
padding: 0 !important;
}
.nice-footer-container {
display: none;
}
}
C'est comme ça que ça marche:

Résumé
Cet article a une compréhension simple du point de vue du code source markdown-nicePrincipe de réalisation, La logique globale est relativement simple , Certaines implémentations détaillées sont encore un peu gênantes ,Comme l'extensionmarkdown-it、 Soutien aux formules mathématiques, etc. .Extensionmarkdown-it Il y a beaucoup de scènes ,Par exemple,VuePress Beaucoup de fonctions sont écrites markdown-itPlug - in pour implémenter, Il y a donc des exigences de développement connexes qui peuvent se référer à la mise en œuvre de ces excellents projets open source. .
边栏推荐
- Differences and relations between three-tier architecture and MVC
- 在线音频调节技术汇总
- How to use debounce in lodash to realize anti shake
- 【剑指Offer】面试题25.合并两个有序的链表
- 建模杂谈系列143 数据处理、分析与决策系统开发的梳理
- Redis数据持久化
- The WebView case of flutter
- Common ways to traverse map sets
- The could not find com scwang. smart:refresh-layout-kernel:2.0.3. Required by: project: the app cannot load the third-party package
- 建模雜談系列143 數據處理、分析與决策系統開發的梳理
猜你喜欢

EMC rectification outline

SCM signal generator program

VGA display based on de2-115 platform

Unity shader learning 004 shader debugging platform difference third-party debugging tools
![[note]vs2015 compilation of masm32 using MASM32 Library](/img/f5/b47336af248d1b485208ec3f9ca12b.png)
[note]vs2015 compilation of masm32 using MASM32 Library

环评图件制作-数据处理+图件制作

出现Could not find com.scwang.smart:refresh-layout-kernel:2.0.3.Required by: project :app 无法加载第三方包情况
![[MySQL] index and transaction](/img/19/f87fee3749690902c349c42673f148.png)
[MySQL] index and transaction

Google Chrome browser reports an error: net:: err_ BLOCKED_ BY_ CLIENT

Single chip microcomputer: MODBUS multi computer communication program design
随机推荐
LVS four layer load balancing cluster (3) cluster function classification - HPC
Introduction to RFM analysis
Lambda end operation find and match allmatch
Mongodb compass connects to the Alibaba cloud remote server database or reports an error occurred while loading instance info: command hostinfo req
建模杂谈系列143 数据处理、分析与决策系统开发的梳理
在线音频调节技术汇总
7-289 tag count (300 points)
十亿数据量 判断元素是否存在
Single chip microcomputer: a/d differential input signal
Redis
Advanced Mathematics (Seventh Edition) Tongji University exercises 1-3 personal solutions
Interpretation of mobile phone private charging protocol
Lambda end operation count
SCM: introduction and operation of EEPROM
MCU: NEC protocol infrared remote controller
Redis persistence mode AOF and RDB
PAT 1054 The Dominant Color
Lambda end operation reduce merge
February 25, 2021 (Archaeology 12 year Landbridge provincial competition)
Lambda end operation collect