Source code

Revision control

Copy as Markdown

Other Tools

import { k as keyName, b as base, t as text } from "./index.es-02a92ebc.js";
const longtext = ' <section class="pg-boilerplate pgheader" id="pg-header" lang="en">\r\n <h2 id="pg-header-heading">The Project Gutenberg eBook of <span lang="fr">Du côté de chez Swann</span></h2>\r\n <div>\r\n This ebook is for the use of anyone anywhere in the United States and most other parts of the world at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project\r\n Gutenberg License included with this ebook or online at <a class="reference external" href="https://www.gutenberg.org">www.gutenberg.org</a>. If you are not located in the United States, you’ll have to check the laws of the country\r\n where you are located before using this eBook.\r\n </div>\r\n\r\n <div class="container" id="pg-machine-header">\r\n <p><strong>Title</strong>: Du côté de chez Swann<br /></p>\r\n <div id="pg-header-authlist">\r\n <p><strong>Author</strong>: Marcel Proust</p>\r\n </div>\r\n <p><strong>Release date</strong>: May 1, 2001 [eBook #2650]<br />Most recently updated: August 12, 2021</p>\r\n <p><strong>Language</strong>: French</p>\r\n </div>\r\n <div id="pg-start-separator">\r\n <span>*** START OF THE PROJECT GUTENBERG EBOOK DU CÔTÉ DE CHEZ SWANN ***</span>\r\n </div>\r\n </section>\r\n <div style="margin-top: 2em; margin-bottom: 4em"></div>\r\n <h2 class="no-break">MARCEL PROUST</h2>\r\n <h2>A LA RECHERCHE DU TEMPS PERDU</h2>\r\n <h3>TOME I</h3>\r\n <h1>Du Côté de Chez Swann</h1>\r\n <p class="center">À Monsieur Gaston Calmette</p>\r\n <p class="letter">\r\n <i\r\n >Comme un témoignage de profonde<br />\r\n et affectueuse reconnaissance</i\r\n >,\r\n </p>\r\n <p class="letter">Marcel Proust.</p>\r\n <div class="chapter">\r\n <h2>\r\n PREMIÈRE PARTIE<br />\r\n COMBRAY\r\n </h2>\r\n <h3>I.</h3>\r\n <p>\r\n Longtemps, je me suis couché de bonne heure. Parfois, à peine ma bougie éteinte, mes yeux se fermaient si vite que je n’avais pas le temps de me dire: «Je m’endors.» Et, une demi-heure après, la pensée qu’il était temps de chercher le\r\n sommeil m’éveillait; je voulais poser le volume que je croyais avoir encore dans les mains et souffler ma lumière; je n’avais pas cessé en dormant de faire des réflexions sur ce que je venais de lire, mais ces réflexions avaient pris\r\n un tour un peu particulier; il me semblait que j’étais moi-même ce dont parlait l’ouvrage: une église, un quatuor, la rivalité de François I<sup>er</sup> et de Charles Quint. Cette croyance survivait pendant quelques secondes à mon\r\n réveil; elle ne choquait pas ma raison mais pesait comme des écailles sur mes yeux et les empêchait de se rendre compte que le bougeoir n’était plus allumé. Puis elle commençait à me devenir inintelligible, comme après la métempsycose\r\n les pensées d’une existence antérieure; le sujet du livre se détachait de moi, j’étais libre de m’y appliquer ou non; aussitôt je recouvrais la vue et j’étais bien étonné de trouver autour de moi une obscurité, douce et reposante pour\r\n mes yeux, mais peut-être plus encore pour mon esprit, à qui elle apparaissait comme une chose sans cause, incompréhensible, comme une chose vraiment obscure. Je me demandais quelle heure il pouvait être; j’entendais le sifflement des\r\n trains qui, plus ou moins éloigné, comme le chant d’un oiseau dans une forêt, relevant les distances, me décrivait l’étendue de la campagne déserte où le voyageur se hâte vers la station prochaine; et le petit chemin qu’il suit va\r\n être gravé dans son souvenir par l’excitation qu’il doit à des lieux nouveaux, à des actes inaccoutumés, à la causerie récente et aux adieux sous la lampe étrangère qui le suivent encore dans le silence de la nuit, à la douceur\r\n prochaine du retour.\r\n </p>\r\n <p>\r\n J’appuyais tendrement mes joues contre les belles joues de l’oreiller qui, pleines et fraîches, sont comme les joues de notre enfance. Je frottais une allumette pour regarder ma montre. Bientôt minuit. C’est l’instant où le malade,\r\n qui a été obligé de partir en voyage et a dû coucher dans un hôtel inconnu, réveillé par une crise, se réjouit en apercevant sous la porte une raie de jour. Quel bonheur, c’est déjà le matin! Dans un moment les domestiques seront\r\n levés, il pourra sonner, on viendra lui porter secours. L’espérance d’être soulagé lui donne du courage pour souffrir. Justement il a cru entendre des pas; les pas se rapprochent, puis s’éloignent. Et la raie de jour qui était sous sa\r\n porte a disparu. C’est minuit; on vient d’éteindre le gaz; le dernier domestique est parti et il faudra rester toute la nuit à souffrir sans remède.\r\n </p>\r\n <p>\r\n Je me rendormais, et parfois je n’avais plus que de courts réveils d’un instant, le temps d’entendre les craquements organiques des boiseries, d’ouvrir les yeux pour fixer le kaléidoscope de l’obscurité, de goûter grâce à une lueur\r\n momentanée de conscience le sommeil où étaient plongés les meubles, la chambre, le tout dont je n’étais qu’une petite partie et à l’insensibilité duquel je retournais vite m’unir. Ou bien en dormant j’avais rejoint sans effort un âge\r\n à jamais révolu de ma vie primitive, retrouvé telle de mes terreurs enfantines comme celle que mon grand-oncle me tirât par mes boucles et qu’avait dissipée le jour,—date pour moi d’une ère nouvelle,—où on les avait coupées. J’avais\r\n oublié cet événement pendant mon sommeil, j’en retrouvais le souvenir aussitôt que j’avais réussi à m’éveiller pour échapper aux mains de mon grand-oncle, mais par mesure de précaution j’entourais complètement ma tête de mon oreiller\r\n avant de retourner dans le monde des rêves.\r\n </p>\r\n <p>\r\n Quelquefois, comme Ève naquit d’une côte d’Adam, une femme naissait pendant mon sommeil d’une fausse position de ma cuisse. Formée du plaisir que j’étais sur le point de goûter, je m’imaginais que c’était elle qui me l’offrait. Mon\r\n corps qui sentait dans le sien ma propre chaleur voulait s’y rejoindre, je m’éveillais. Le reste des humains m’apparaissait comme bien lointain auprès de cette femme que j’avais quittée il y avait quelques moments à peine; ma joue\r\n était chaude encore de son baiser, mon corps courbaturé par le poids de sa taille. Si, comme il arrivait quelquefois, elle avait les traits d’une femme que j’avais connue dans la vie, j’allais me donner tout entier à ce but: la\r\n retrouver, comme ceux qui partent en voyage pour voir de leurs yeux une cité désirée et s’imaginent qu’on peut goûter dans une réalité le charme du songe. Peu à peu son souvenir s’évanouissait, j’avais oublié la fille de mon rêve.\r\n </p>\r\n <p>\r\n Un homme qui dort, tient en cercle autour de lui le fil des heures, l’ordre des années et des mondes. Il les consulte d’instinct en s’éveillant et y lit en une seconde le point de la terre qu’il occupe, le temps qui s’est écoulé\r\n jusqu’à son réveil; mais leurs rangs peuvent se mêler, se rompre. Que vers le matin après quelque insomnie, le sommeil le prenne en train de lire, dans une posture trop différente de celle où il dort habituellement, il suffit de son\r\n bras soulevé pour arrêter et faire reculer le soleil, et à la première minute de son réveil, il ne saura plus l’heure, il estimera qu’il vient à peine de se coucher. Que s’il s’assoupit dans une position encore plus déplacée et\r\n divergente, par exemple après dîner assis dans un fauteuil, alors le bouleversement sera complet dans les mondes désorbités, le fauteuil magique le fera voyager à toute vitesse dans le temps et dans l’espace, et au moment d’ouvrir les\r\n paupières, il se croira couché quelques mois plus tôt dans une autre contrée. Mais il suffisait que, dans mon lit même, mon sommeil fût profond et détendît entièrement mon esprit; alors celui-ci lâchait le plan du lieu où je m’étais\r\n endormi, et quand je m’éveillais au milieu de la nuit, comme j’ignorais où je me trouvais, je ne savais même pas au premier instant qui j’étais; j’avais seulement dans sa simplicité première, le sentiment de l’existence comme il peut\r\n frémir au fond d’un animal: j’étais plus dénué que l’homme des cavernes; mais alors le souvenir—non encore du lieu où j’étais, mais de quelques-uns de ceux que j’avais habités et où j’aurais pu être—venait à moi comme un secours d’en\r\n haut pour me tirer du néant d’où je n’aurais pu sortir tout seul; je passais en une seconde par-dessus des siècles de civilisation, et l’image confusément entrevue de lampes à pétrole, puis de chemises à col rabattu, recomposaient peu\r\n à peu les traits originaux de mon moi.\r\n </p>\r\n <p>\r\n Peut-être l’immobilité des choses autour de nous leur est-elle imposée par notre certitude que ce sont elles et non pas d’autres, par l’immobilité de notre pensée en face d’elles. Toujours est-il que, quand je me réveillais ainsi, mon\r\n esprit s’agitant pour chercher, sans y réussir, à savoir où j’étais, tout tournait autour de moi dans l’obscurité, les choses, les pays, les années. Mon corps, trop engourdi pour remuer, cherchait, d’après la forme de sa fatigue, à\r\n repérer la position de ses membres pour en induire la direction du mur, la place des meubles, pour reconstruire et pour nommer la demeure où il se trouvait. Sa mémoire, la mémoire de ses côtes, de ses genoux, de ses épaules, lui\r\n présentait successivement plusieurs des chambres où il avait dormi, tandis qu’autour de lui les murs invisibles, changeant de place selon la forme de la pièce imaginée, tourbillonnaient dans les ténèbres. Et avant même que ma pensée,\r\n qui hésitait au seuil des temps et des formes, eût identifié le logis en rapprochant les circonstances, lui,—mon corps,—se rappelait pour chacun le genre du lit, la place des portes, la prise de jour des fenêtres, l’existence d’un\r\n couloir, avec la pensée que j’avais en m’y endormant et que je retrouvais au réveil. Mon côté ankylosé, cherchant à deviner son orientation, s’imaginait, par exemple, allongé face au mur dans un grand lit à baldaquin et aussitôt je me\r\n disais: «Tiens, j’ai fini par m’endormir quoique maman ne soit pas venue me dire bonsoir», j’étais à la campagne chez mon grand-père, mort depuis bien des années; et mon corps, le côté sur lequel je reposais, gardiens fidèles d’un\r\n passé que mon esprit n’aurait jamais dû oublier, me rappelaient la flamme de la veilleuse de verre de Bohême, en forme d’urne, suspendue au plafond par des chaînettes, la cheminée en marbre de Sienne, dans ma chambre à coucher de\r\n Combray, chez mes grands-parents, en des jours lointains qu’en ce moment je me figurais actuels sans me les représenter exactement et que je reverrais mieux tout à l’heure quand je serais tout à fait éveillé.\r\n </p>\r\n <p>\r\n Puis renaissait le souvenir d’une nouvelle attitude; le mur filait dans une autre direction: j’étais dans ma chambre chez M<sup>me</sup> de Saint-Loup, à la campagne; mon Dieu! Il est au moins dix heures, on doit avoir fini de dîner!\r\n J’aurai trop prolongé la sieste que je fais tous les soirs en rentrant de ma promenade avec M<sup>me</sup> de Saint-Loup, avant d’endosser mon habit. Car bien des années ont passé depuis Combray, où, dans nos retours les plus tardifs,\r\n c’était les reflets rouges du couchant que je voyais sur le vitrage de ma fenêtre. C’est un autre genre de vie qu’on mène à Tansonville, chez M<sup>me</sup> de Saint-Loup, un autre genre de plaisir que je trouve à ne sortir qu’à la\r\n nuit, à suivre au clair de lune ces chemins où je jouais jadis au soleil; et la chambre où je me serai endormi au lieu de m’habiller pour le dîner, de loin je l’aperçois, quand nous rentrons, traversée par les feux de la lampe, seul\r\n phare dans la nuit.\r\n </p>\r\n <p>\r\n Ces évocations tournoyantes et confuses ne duraient jamais que quelques secondes; souvent, ma brève incertitude du lieu où je me trouvais ne distinguait pas mieux les unes des autres les diverses suppositions dont elle était faite,\r\n que nous n’isolons, en voyant un cheval courir, les positions successives que nous montre le kinétoscope. Mais j’avais revu tantôt l’une, tantôt l’autre, des chambres que j’avais habitées dans ma vie, et je finissais par me les\r\n rappeler toutes dans les longues rêveries qui suivaient mon réveil; chambres d’hiver où quand on est couché, on se blottit la tête dans un nid qu’on se tresse avec les choses les plus disparates: un coin de l’oreiller, le haut des\r\n couvertures, un bout de châle, le bord du lit, et un numéro des Débats roses, qu’on finit par cimenter ensemble selon la technique des oiseaux en s’y appuyant indéfiniment; où, par un temps glacial le plaisir qu’on goûte est de se\r\n sentir séparé du dehors (comme l’hirondelle de mer qui a son nid au fond d’un souterrain dans la chaleur de la terre), et où, le feu étant entretenu toute la nuit dans la cheminée, on dort dans un grand manteau d’air chaud et fumeux,\r\n traversé des lueurs des tisons qui se rallument, sorte d’impalpable alcôve, de chaude caverne creusée au sein de la chambre même, zone ardente et mobile en ses contours thermiques, aérée de souffles qui nous rafraîchissent la figure\r\n et viennent des angles, des parties voisines de la fenêtre ou éloignées du foyer et qui se sont refroidies;—chambres d’été où l’on aime être uni à la nuit tiède, où le clair de lune appuyé aux volets entr’ouverts, jette jusqu’au pied\r\n du lit son échelle enchantée, où on dort presque en plein air, comme la mésange balancée par la brise à la pointe d’un rayon—; parfois la chambre Louis XVI, si gaie que même le premier soir je n’y avais pas été trop malheureux et où\r\n les colonnettes qui soutenaient légèrement le plafond s’écartaient avec tant de grâce pour montrer et réserver la place du lit; parfois au contraire celle, petite et si élevée de plafond, creusée en forme de pyramide dans la hauteur\r\n de deux étages et partiellement revêtue d’acajou, où dès la première seconde j’avais été intoxiqué moralement par l’odeur inconnue du vétiver, convaincu de l’hostilité des rideaux violets et de l’insolente indifférence de la pendule\r\n qui jacassait tout haut comme si je n’eusse pas été là;—où une étrange et impitoyable glace à pieds quadrangulaires, barrant obliquement un des angles de la pièce, se creusait à vif dans la douce plénitude de mon champ visuel\r\n accoutumé un emplacement qui n’y était pas prévu;—où ma pensée, s’efforçant pendant des heures de se disloquer, de s’étirer en hauteur pour prendre exactement la forme de la chambre et arriver à remplir jusqu’en haut son gigantesque\r\n entonnoir, avait souffert bien de dures nuits, tandis que j’étais étendu dans mon lit, les yeux levés, l’oreille anxieuse, la narine rétive, le cœur battant: jusqu’à ce que l’habitude eût changé la couleur des rideaux, fait taire la\r\n pendule, enseigné la pitié à la glace oblique et cruelle, dissimulé, sinon chassé complètement, l’odeur du vétiver et notablement diminué la hauteur apparente du plafond. L’habitude! aménageuse habile mais bien lente et qui commence\r\n par laisser souffrir notre esprit pendant des semaines dans une installation provisoire; mais que malgré tout il est bien heureux de trouver, car sans l’habitude et réduit à ses seuls moyens il serait impuissant à nous rendre un logis\r\n habitable.\r\n </p>\r\n <p>\r\n Certes, j’étais bien éveillé maintenant, mon corps avait viré une dernière fois et le bon ange de la certitude avait tout arrêté autour de moi, m’avait couché sous mes couvertures, dans ma chambre, et avait mis approximativement à\r\n leur place dans l’obscurité ma commode, mon bureau, ma cheminée, la fenêtre sur la rue et les deux portes. Mais j’avais beau savoir que je n’étais pas dans les demeures dont l’ignorance du réveil m’avait en un instant sinon présenté\r\n l’image distincte, du moins fait croire la présence possible, le branle était donné à ma mémoire; généralement je ne cherchais pas à me rendormir tout de suite; je passais la plus grande partie de la nuit à me rappeler notre vie\r\n d’autrefois, à Combray chez ma grand’tante, à Balbec, à Paris, à Doncières, à Venise, ailleurs encore, à me rappeler les lieux, les personnes que j’y avais connues, ce que j’avais vu d’elles, ce qu’on m’en avait raconté.\r\n </p>\r\n <p>\r\n A Combray, tous les jours dès la fin de l’après-midi, longtemps avant le moment où il faudrait me mettre au lit et rester, sans dormir, loin de ma mère et de ma grand’mère, ma chambre à coucher redevenait le point fixe et douloureux\r\n de mes préoccupations. On avait bien inventé, pour me distraire les soirs où on me trouvait l’air trop malheureux, de me donner une lanterne magique, dont, en attendant l’heure du dîner, on coiffait ma lampe; et, à l’instar des\r\n premiers architectes et maîtres verriers de l’âge gothique, elle substituait à l’opacité des murs d’impalpables irisations, de surnaturelles apparitions multicolores, où des légendes étaient dépeintes comme dans un vitrail vacillant\r\n et momentané. Mais ma tristesse n’en était qu’accrue, parce que rien que le changement d’éclairage détruisait l’habitude que j’avais de ma chambre et grâce à quoi, sauf le supplice du coucher, elle m’était devenue supportable.\r\n Maintenant je ne la reconnaissais plus et j’y étais inquiet, comme dans une chambre d’hôtel ou de «chalet», où je fusse arrivé pour la première fois en descendant de chemin de fer.\r\n </p>\r\n <p>\r\n Au pas saccadé de son cheval, Golo, plein d’un affreux dessein, sortait de la petite forêt triangulaire qui veloutait d’un vert sombre la pente d’une colline, et s’avançait en tressautant vers le château de la pauvre Geneviève de\r\n Brabant. Ce château était coupé selon une ligne courbe qui n’était autre que la limite d’un des ovales de verre ménagés dans le châssis qu’on glissait entre les coulisses de la lanterne. Ce n’était qu’un pan de château et il avait\r\n devant lui une lande où rêvait Geneviève qui portait une ceinture bleue. Le château et la lande étaient jaunes et je n’avais pas attendu de les voir pour connaître leur couleur car, avant les verres du châssis, la sonorité mordorée du\r\n nom de Brabant me l’avait montrée avec évidence. Golo s’arrêtait un instant pour écouter avec tristesse le boniment lu à haute voix par ma grand’tante et qu’il avait l’air de comprendre parfaitement, conformant son attitude avec une\r\n docilité qui n’excluait pas une certaine majesté, aux indications du texte; puis il s’éloignait du même pas saccadé. Et rien ne pouvait arrêter sa lente chevauchée. Si on bougeait la lanterne, je distinguais le cheval de Golo qui\r\n continuait à s’avancer sur les rideaux de la fenêtre, se bombant de leurs plis, descendant dans leurs fentes. Le corps de Golo lui-même, d’une essence aussi surnaturelle que celui de sa monture, s’arrangeait de tout obstacle matériel,\r\n de tout objet gênant qu’il rencontrait en le prenant comme ossature et en se le rendant intérieur, fût-ce le bouton de la porte sur lequel s’adaptait aussitôt et surnageait invinciblement sa robe rouge ou sa figure pâle toujours aussi\r\n noble et aussi mélancolique, mais qui ne laissait paraître aucun trouble de cette transvertébration.\r\n </p>\r\n <p>\r\n Certes je leur trouvais du charme à ces brillantes projections qui semblaient émaner d’un passé mérovingien et promenaient autour de moi des reflets d’histoire si anciens. Mais je ne peux dire quel malaise me causait pourtant cette\r\n intrusion du mystère et de la beauté dans une chambre que j’avais fini par remplir de mon moi au point de ne pas faire plus attention à elle qu’à lui-même. L’influence anesthésiante de l’habitude ayant cessé, je me mettais à penser, à\r\n sentir, choses si tristes. Ce bouton de la porte de ma chambre, qui différait pour moi de tous les autres boutons de porte du monde en ceci qu’il semblait ouvrir tout seul, sans que j’eusse besoin de le tourner, tant le maniement m’en\r\n était devenu inconscient, le voilà qui servait maintenant de corps astral à Golo. Et dès qu’on sonnait le dîner, j’avais hâte de courir à la salle à manger, où la grosse lampe de la suspension, ignorante de Golo et de Barbe-Bleue, et\r\n qui connaissait mes parents et le bœuf à la casserole, donnait sa lumière de tous les soirs; et de tomber dans les bras de maman que les malheurs de Geneviève de Brabant me rendaient plus chère, tandis que les crimes de Golo me\r\n faisaient examiner ma propre conscience avec plus de scrupules.\r\n </p>\r\n <p>\r\n Après le dîner, hélas, j’étais bientôt obligé de quitter maman qui restait à causer avec les autres, au jardin s’il faisait beau, dans le petit salon où tout le monde se retirait s’il faisait mauvais. Tout le monde, sauf ma grand’mère\r\n qui trouvait que «c’est une pitié de rester enfermé à la campagne» et qui avait d’incessantes discussions avec mon père, les jours de trop grande pluie, parce qu’il m’envoyait lire dans ma chambre au lieu de rester dehors. «Ce n’est\r\n pas comme cela que vous le rendrez robuste et énergique, disait-elle tristement, surtout ce petit qui a tant besoin de prendre des forces et de la volonté.» Mon père haussait les épaules et il examinait le baromètre, car il aimait la\r\n météorologie, pendant que ma mère, évitant de faire du bruit pour ne pas le troubler, le regardait avec un respect attendri, mais pas trop fixement pour ne pas chercher à percer le mystère de ses supériorités. Mais ma grand’mère,\r\n elle, par tous les temps, même quand la pluie faisait rage et que Françoise avait précipitamment rentré les précieux fauteuils d’osier de peur qu’ils ne fussent mouillés, on la voyait dans le jardin vide et fouetté par l’averse,\r\n relevant ses mèches désordonnées et grises pour que son front s’imbibât mieux de la salubrité du vent et de la pluie. Elle disait: «Enfin, on respire!» et parcourait les allées détrempées,—trop symétriquement alignées à son gré par le\r\n nouveau jardinier dépourvu du sentiment de la nature et auquel mon père avait demandé depuis le matin si le temps s’arrangerait,—de son petit pas enthousiaste et saccadé, réglé sur les mouvements divers qu’excitaient dans son âme\r\n l’ivresse de l’orage, la puissance de l’hygiène, la stupidité de mon éducation et la symétrie des jardins, plutôt que sur le désir inconnu d’elle d’éviter à sa jupe prune les taches de boue sous lesquelles elle disparaissait jusqu’à\r\n une hauteur qui était toujours pour sa femme de chambre un désespoir et un problème.\r\n </p>\r\n <p>\r\n Quand ces tours de jardin de ma grand’mère avaient lieu après dîner, une chose avait le pouvoir de la faire rentrer: c’était, à un des moments où la révolution de sa promenade la ramenait périodiquement, comme un insecte, en face des\r\n lumières du petit salon où les liqueurs étaient servies sur la table à jeu,—si ma grand’tante lui criait: «Bathilde! viens donc empêcher ton mari de boire du cognac!» Pour la taquiner, en effet (elle avait apporté dans la famille de\r\n mon père un esprit si différent que tout le monde la plaisantait et la tourmentait), comme les liqueurs étaient défendues à mon grand-père, ma grand’tante lui en faisait boire quelques gouttes. Ma pauvre grand’mère entrait, priait\r\n ardemment son mari de ne pas goûter au cognac; il se fâchait, buvait tout de même sa gorgée, et ma grand’mère repartait, triste, découragée, souriante pourtant, car elle était si humble de cœur et si douce que sa tendresse pour les\r\n autres et le peu de cas qu’elle faisait de sa propre personne et de ses souffrances, se conciliaient dans son regard en un sourire où, contrairement à ce qu’on voit dans le visage de beaucoup d’humains, il n’y avait d’ironie que pour\r\n elle-même, et pour nous tous comme un baiser de ses yeux qui ne pouvaient voir ceux qu’elle chérissait sans les caresser passionnément du regard. Ce supplice que lui infligeait ma grand’tante, le spectacle des vaines prières de ma\r\n grand’mère et de sa faiblesse, vaincue d’avance, essayant inutilement d’ôter à mon grand-père le verre à liqueur, c’était de ces choses à la vue desquelles on s’habitue plus tard jusqu’à les considérer en riant et à prendre le parti\r\n du persécuteur assez résolument et gaiement pour se persuader à soi-même qu’il ne s’agit pas de persécution; elles me causaient alors une telle horreur, que j’aurais aimé battre ma grand’tante. Mais dès que j’entendais: «Bathilde,\r\n viens donc empêcher ton mari de boire du cognac!» déjà homme par la lâcheté, je faisais ce que nous faisons tous, une fois que nous sommes grands, quand il y a devant nous des souffrances et des injustices: je ne voulais pas les voir;\r\n je montais sangloter tout en haut de la maison à côté de la salle d’études, sous les toits, dans une petite pièce sentant l’iris, et que parfumait aussi un cassis sauvage poussé au dehors entre les pierres de la muraille et qui\r\n passait une branche de fleurs par la fenêtre entr’ouverte. Destinée à un usage plus spécial et plus vulgaire, cette pièce, d’où l’on voyait pendant le jour jusqu’au donjon de Roussainville-le-Pin, servit longtemps de refuge pour moi,\r\n sans doute parce qu’elle était la seule qu’il me fût permis de fermer à clef, à toutes celles de mes occupations qui réclamaient une inviolable solitude: la lecture, la rêverie, les larmes et la volupté. Hélas! je ne savais pas que,\r\n bien plus tristement que les petits écarts de régime de son mari, mon manque de volonté, ma santé délicate, l’incertitude qu’ils projetaient sur mon avenir, préoccupaient ma grand’mère, au cours de ces déambulations incessantes, de\r\n l’après-midi et du soir, où on voyait passer et repasser, obliquement levé vers le ciel, son beau visage aux joues brunes et sillonnées, devenues au retour de l’âge presque mauves comme les labours à l’automne, barrées, si elle\r\n sortait, par une voilette à demi relevée, et sur lesquelles, amené là par le froid ou quelque triste pensée, était toujours en train de sécher un pleur involontaire.\r\n </p>\r\n <p>\r\n Ma seule consolation, quand je montais me coucher, était que maman viendrait m’embrasser quand je serais dans mon lit. Mais ce bonsoir durait si peu de temps, elle redescendait si vite, que le moment où je l’entendais monter, puis où\r\n passait dans le couloir à double porte le bruit léger de sa robe de jardin en mousseline bleue, à laquelle pendaient de petits cordons de paille tressée, était pour moi un moment douloureux. Il annonçait celui qui allait le suivre, où\r\n elle m’aurait quitté, où elle serait redescendue. De sorte que ce bonsoir que j’aimais tant, j’en arrivais à souhaiter qu’il vînt le plus tard possible, à ce que se prolongeât le temps de répit où maman n’était pas encore venue.\r\n Quelquefois quand, après m’avoir embrassé, elle ouvrait la porte pour partir, je voulais la rappeler, lui dire «embrasse-moi une fois encore», mais je savais qu’aussitôt elle aurait son visage fâché, car la concession qu’elle faisait\r\n à ma tristesse et à mon agitation en montant m’embrasser, en m’apportant ce baiser de paix, agaçait mon père qui trouvait ces rites absurdes, et elle eût voulu tâcher de m’en faire perdre le besoin, l’habitude, bien loin de me laisser\r\n prendre celle de lui demander, quand elle était déjà sur le pas de la porte, un baiser de plus. Or la voir fâchée détruisait tout le calme qu’elle m’avait apporté un instant avant, quand elle avait penché vers mon lit sa figure\r\n aimante, et me l’avait tendue comme une hostie pour une communion de paix où mes lèvres puiseraient sa présence réelle et le pouvoir de m’endormir. Mais ces soirs-là, où maman en somme restait si peu de temps dans ma chambre, étaient\r\n doux encore en comparaison de ceux où il y avait du monde à dîner et où, à cause de cela, elle ne montait pas me dire bonsoir. Le monde se bornait habituellement à M. Swann, qui, en dehors de quelques étrangers de passage, était à peu\r\n près la seule personne qui vînt chez nous à Combray, quelquefois pour dîner en voisin (plus rarement depuis qu’il avait fait ce mauvais mariage, parce que mes parents ne voulaient pas recevoir sa femme), quelquefois après le dîner, à\r\n l’improviste. Les soirs où, assis devant la maison sous le grand marronnier, autour de la table de fer, nous entendions au bout du jardin, non pas le grelot profus et criard qui arrosait, qui étourdissait au passage de son bruit\r\n ferrugineux, intarissable et glacé, toute personne de la maison qui le déclenchait en entrant «sans sonner», mais le double tintement timide, ovale et doré de la clochette pour les étrangers, tout le monde aussitôt se demandait: «Une\r\n visite, qui cela peut-il être?» mais on savait bien que cela ne pouvait être que M. Swann; ma grand’tante parlant à haute voix, pour prêcher d’exemple, sur un ton qu’elle s’efforçait de rendre naturel, disait de ne pas chuchoter\r\n ainsi; que rien n’est plus désobligeant pour une personne qui arrive et à qui cela fait croire qu’on est en train de dire des choses qu’elle ne doit pas entendre; et on envoyait en éclaireur ma grand’mère, toujours heureuse d’avoir un\r\n prétexte pour faire un tour de jardin de plus, et qui en profitait pour arracher subrepticement au passage quelques tuteurs de rosiers afin de rendre aux roses un peu de naturel, comme une mère qui, pour les faire bouffer, passe la\r\n main dans les cheveux de son fils que le coiffeur a trop aplatis.\r\n </p>\r\n <p>\r\n Nous restions tous suspendus aux nouvelles que ma grand’mère allait nous apporter de l’ennemi, comme si on eût pu hésiter entre un grand nombre possible d’assaillants, et bientôt après mon grand-père disait: «Je reconnais la voix de\r\n Swann.» On ne le reconnaissait en effet qu’à la voix, on distinguait mal son visage au nez busqué, aux yeux verts, sous un haut front entouré de cheveux blonds presque roux, coiffés à la Bressant, parce que nous gardions le moins de\r\n lumière possible au jardin pour ne pas attirer les moustiques et j’allais, sans en avoir l’air, dire qu’on apportât les sirops; ma grand’mère attachait beaucoup d’importance, trouvant cela plus aimable, à ce qu’ils n’eussent pas l’air\r\n de figurer d’une façon exceptionnelle, et pour les visites seulement. M. Swann, quoique beaucoup plus jeune que lui, était très lié avec mon grand-père qui avait été un des meilleurs amis de son père, homme excellent mais singulier,\r\n chez qui, paraît-il, un rien suffisait parfois pour interrompre les élans du cœur, changer le cours de la pensée. J’entendais plusieurs fois par an mon grand-père raconter à table des anecdotes toujours les mêmes sur l’attitude\r\n qu’avait eue M. Swann le père, à la mort de sa femme qu’il avait veillée jour et nuit. Mon grand-père qui ne l’avait pas vu depuis longtemps était accouru auprès de lui dans la propriété que les Swann possédaient aux environs de\r\n Combray, et avait réussi, pour qu’il n’assistât pas à la mise en bière, à lui faire quitter un moment, tout en pleurs, la chambre mortuaire. Ils firent quelques pas dans le parc où il y avait un peu de soleil. Tout d’un coup, M. Swann\r\n prenant mon grand-père par le bras, s’était écrié: «Ah! mon vieil ami, quel bonheur de se promener ensemble par ce beau temps. Vous ne trouvez pas ça joli tous ces arbres, ces aubépines et mon étang dont vous ne m’avez jamais\r\n félicité? Vous avez l’air comme un bonnet de nuit. Sentez-vous ce petit vent? Ah! on a beau dire, la vie a du bon tout de même, mon cher Amédée!» Brusquement le souvenir de sa femme morte lui revint, et trouvant sans doute trop\r\n compliqué de chercher comment il avait pu à un pareil moment se laisser aller à un mouvement de joie, il se contenta, par un geste qui lui était familier chaque fois qu’une question ardue se présentait à son esprit, de passer la main\r\n sur son front, d’essuyer ses yeux et les verres de son lorgnon. Il ne put pourtant pas se consoler de la mort de sa femme, mais pendant les deux années qu’il lui survécut, il disait à mon grand-père: «C’est drôle, je pense très\r\n souvent à ma pauvre femme, mais je ne peux y penser beaucoup à la fois.» «Souvent, mais peu à la fois, comme le pauvre père Swann», était devenu une des phrases favorites de mon grand-père qui la prononçait à propos des choses les\r\n plus différentes. Il m’aurait paru que ce père de Swann était un monstre, si mon grand-père que je considérais comme meilleur juge et dont la sentence faisant jurisprudence pour moi, m’a souvent servi dans la suite à absoudre des\r\n fautes que j’aurais été enclin à condamner, ne s’était récrié: «Mais comment? c’était un cœur d’or!»\r\n </p>\r\n <p>\r\n Pendant bien des années, où pourtant, surtout avant son mariage, M. Swann, le fils, vint souvent les voir à Combray, ma grand’tante et mes grands-parents ne soupçonnèrent pas qu’il ne vivait plus du tout dans la société qu’avait\r\n fréquentée sa famille et que sous l’espèce d’incognito que lui faisait chez nous ce nom de Swann, ils hébergeaient,—avec la parfaite innocence d’honnêtes hôteliers qui ont chez eux, sans le savoir, un célèbre brigand,—un des membres\r\n les plus élégants du Jockey-Club, ami préféré du comte de Paris et du prince de Galles, un des hommes les plus choyés de la haute société du faubourg Saint-Germain.\r\n </p>\r\n <p>\r\n L’ignorance où nous étions de cette brillante vie mondaine que menait Swann tenait évidemment en partie à la réserve et à la discrétion de son caractère, mais aussi à ce que les bourgeois d’alors se faisaient de la société une idée un\r\n peu hindoue et la considéraient comme composée de castes fermées où chacun, dès sa naissance, se trouvait placé dans le rang qu’occupaient ses parents, et d’où rien, à moins des hasards d’une carrière exceptionnelle ou d’un mariage\r\n inespéré, ne pouvait vous tirer pour vous faire pénétrer dans une caste supérieure. M. Swann, le père, était agent de change; le «fils Swann» se trouvait faire partie pour toute sa vie d’une caste où les fortunes, comme dans une\r\n catégorie de contribuables, variaient entre tel et tel revenu. On savait quelles avaient été les fréquentations de son père, on savait donc quelles étaient les siennes, avec quelles personnes il était «en situation» de frayer. S’il en\r\n connaissait d’autres, c’étaient relations de jeune homme sur lesquelles des amis anciens de sa famille, comme étaient mes parents, fermaient d’autant plus bienveillamment les yeux qu’il continuait, depuis qu’il était orphelin, à venir\r\n très fidèlement nous voir; mais il y avait fort à parier que ces gens inconnus de nous qu’il voyait, étaient de ceux qu’il n’aurait pas osé saluer si, étant avec nous, il les avait rencontrés. Si l’on avait voulu à toute force\r\n appliquer à Swann un coefficient social qui lui fût personnel, entre les autres fils d’agents de situation égale à celle de ses parents, ce coefficient eût été pour lui un peu inférieur parce que, très simple de façon et ayant\r\n toujours eu une «toquade» d’objets anciens et de peinture, il demeurait maintenant dans un vieil hôtel où il entassait ses collections et que ma grand’mère rêvait de visiter, mais qui était situé quai d’Orléans, quartier que ma\r\n grand’tante trouvait infamant d’habiter. «Êtes-vous seulement connaisseur? je vous demande cela dans votre intérêt, parce que vous devez vous faire repasser des croûtes par les marchands», lui disait ma grand’tante; elle ne lui\r\n supposait en effet aucune compétence et n’avait pas haute idée même au point de vue intellectuel d’un homme qui dans la conversation évitait les sujets sérieux et montrait une précision fort prosaïque non seulement quand il nous\r\n donnait, en entrant dans les moindres détails, des recettes de cuisine, mais même quand les sœurs de ma grand’mère parlaient de sujets artistiques. Provoqué par elles à donner son avis, à exprimer son admiration pour un tableau, il\r\n gardait un silence presque désobligeant et se rattrapait en revanche s’il pouvait fournir sur le musée où il se trouvait, sur la date où il avait été peint, un renseignement matériel. Mais d’habitude il se contentait de chercher à\r\n nous amuser en racontant chaque fois une histoire nouvelle qui venait de lui arriver avec des gens choisis parmi ceux que nous connaissions, avec le pharmacien de Combray, avec notre cuisinière, avec notre cocher. Certes ces récits\r\n faisaient rire ma grand’tante, mais sans qu’elle distinguât bien si c’était à cause du rôle ridicule que s’y donnait toujours Swann ou de l’esprit qu’il mettait à les conter: «On peut dire que vous êtes un vrai type, monsieur Swann!»\r\n Comme elle était la seule personne un peu vulgaire de notre famille, elle avait soin de faire remarquer aux étrangers, quand on parlait de Swann, qu’il aurait pu, s’il avait voulu, habiter boulevard Haussmann ou avenue de l’Opéra,\r\n qu’il était le fils de M. Swann qui avait dû lui laisser quatre ou cinq millions, mais que c’était sa fantaisie. Fantaisie qu’elle jugeait du reste devoir être si divertissante pour les autres, qu’à Paris, quand M. Swann venait le\r\n 1<sup>er</sup> janvier lui apporter son sac de marrons glacés, elle ne manquait pas, s’il y avait du monde, de lui dire: «Eh bien! M. Swann, vous habitez toujours près de l’Entrepôt des vins, pour être sûr de ne pas manquer le train\r\n quand vous prenez le chemin de Lyon?» Et elle regardait du coin de l’œil, par-dessus son lorgnon, les autres visiteurs.\r\n </p>\r\n <p>\r\n Mais si l’on avait dit à ma grand’mère que ce Swann qui, en tant que fils Swann était parfaitement «qualifié» pour être reçu par toute la «belle bourgeoisie», par les notaires ou les avoués les plus estimés de Paris (privilège qu’il\r\n semblait laisser tomber un peu en quenouille), avait, comme en cachette, une vie toute différente; qu’en sortant de chez nous, à Paris, après nous avoir dit qu’il rentrait se coucher, il rebroussait chemin à peine la rue tournée et se\r\n rendait dans tel salon que jamais l’œil d’aucun agent ou associé d’agent ne contempla, cela eût paru aussi extraordinaire à ma tante qu’aurait pu l’être pour une dame plus lettrée la pensée d’être personnellement liée avec Aristée\r\n dont elle aurait compris qu’il allait, après avoir causé avec elle, plonger au sein des royaumes de Thétis, dans un empire soustrait aux yeux des mortels et où Virgile nous le montre reçu à bras ouverts; ou, pour s’en tenir à une\r\n image qui avait plus de chance de lui venir à l’esprit, car elle l’avait vue peinte sur nos assiettes à petits fours de Combray—d’avoir eu à dîner Ali-Baba, lequel quand il se saura seul, pénétrera dans la caverne, éblouissante de\r\n trésors insoupçonnés.\r\n </p>\r\n <p>\r\n Un jour qu’il était venu nous voir à Paris après dîner en s’excusant d’être en habit, Françoise ayant, après son départ, dit tenir du cocher qu’il avait dîné «chez une princesse»,—«Oui, chez une princesse du demi-monde!» avait répondu\r\n ma tante en haussant les épaules sans lever les yeux de sur son tricot, avec une ironie sereine.\r\n </p>\r\n <p>\r\n Aussi, ma grand’tante en usait-elle cavalièrement avec lui. Comme elle croyait qu’il devait être flatté par nos invitations, elle trouvait tout naturel qu’il ne vînt pas nous voir l’été sans avoir à la main un panier de pêches ou de\r\n framboises de son jardin et que de chacun de ses voyages d’Italie il m’eût rapporté des photographies de chefs-d’œuvre.\r\n </p>\r\n <p>\r\n On ne se gênait guère pour l’envoyer quérir dès qu’on avait besoin d’une recette de sauce gribiche ou de salade à l’ananas pour des grands dîners où on ne l’invitait pas, ne lui trouvant pas un prestige suffisant pour qu’on pût le\r\n servir à des étrangers qui venaient pour la première fois. Si la conversation tombait sur les princes de la Maison de France: «des gens que nous ne connaîtrons jamais ni vous ni moi et nous nous en passons, n’est-ce pas», disait ma\r\n grand’tante à Swann qui avait peut-être dans sa poche une lettre de Twickenham; elle lui faisait pousser le piano et tourner les pages les soirs où la sœur de ma grand’mère chantait, ayant pour manier cet être ailleurs si recherché,\r\n la naïve brusquerie d’un enfant qui joue avec un bibelot de collection sans plus de précautions qu’avec un objet bon marché. Sans doute le Swann que connurent à la même époque tant de clubmen était bien différent de celui que créait\r\n ma grand’tante, quand le soir, dans le petit jardin de Combray, après qu’avaient retenti les deux coups hésitants de la clochette, elle injectait et vivifiait de tout ce qu’elle savait sur la famille Swann, l’obscur et incertain\r\n personnage qui se détachait, suivi de ma grand’mère, sur un fond de ténèbres, et qu’on reconnaissait à la voix. Mais même au point de vue des plus insignifiantes choses de la vie, nous ne sommes pas un tout matériellement constitué,\r\n identique pour tout le monde et dont chacun n’a qu’à aller prendre connaissance comme d’un cahier des charges ou d’un testament; notre personnalité sociale est une création de la pensée des autres. Même l’acte si simple que nous\r\n appelons «voir une personne que nous connaissons» est en partie un acte intellectuel. Nous remplissons l’apparence physique de l’être que nous voyons, de toutes les notions que nous avons sur lui et dans l’aspect total que nous nous\r\n représentons, ces notions ont certainement la plus grande part. Elles finissent par gonfler si parfaitement les joues, par suivre en une adhérence si exacte la ligne du nez, elles se mêlent si bien de nuancer la sonorité de la voix\r\n comme si celle-ci n’était qu’une transparente enveloppe, que chaque fois que nous voyons ce visage et que nous entendons cette voix, ce sont ces notions que nous retrouvons, que nous écoutons. Sans doute, dans le Swann qu’ils\r\n s’étaient constitué, mes parents avaient omis par ignorance de faire entrer une foule de particularités de sa vie mondaine qui étaient cause que d’autres personnes, quand elles étaient en sa présence, voyaient les élégances régner\r\n dans son visage et s’arrêter à son nez busqué comme à leur frontière naturelle; mais aussi ils avaient pu entasser dans ce visage désaffecté de son prestige, vacant et spacieux, au fond de ces yeux dépréciés, le vague et doux\r\n résidu,—mi-mémoire, mi-oubli,—des heures oisives passées ensemble après nos dîners hebdomadaires, autour de la table de jeu ou au jardin, durant notre vie de bon voisinage campagnard. L’enveloppe corporelle de notre ami en avait été\r\n si bien bourrée, ainsi que de quelques souvenirs relatifs à ses parents, que ce Swann-là était devenu un être complet et vivant, et que j’ai l’impression de quitter une personne pour aller vers une autre qui en est distincte, quand,\r\n dans ma mémoire, du Swann que j’ai connu plus tard avec exactitude je passe à ce premier Swann,—à ce premier Swann dans lequel je retrouve les erreurs charmantes de ma jeunesse, et qui d’ailleurs ressemble moins à l’autre qu’aux\r\n personnes que j’ai connues à la même époque, comme s’il en était de notre vie ainsi que d’un musée où tous les portraits d’un même temps ont un air de famille, une même tonalité—à ce premier Swann rempli de loisir, parfumé par l’odeur\r\n du grand marronnier, des paniers de framboises et d’un brin d’estragon.\r\n </p>\r\n <p>\r\n Pourtant un jour que ma grand’mère était allée demander un service à une dame qu’elle avait connue au Sacré-Cœur (et avec laquelle, à cause de notre conception des castes elle n’avait pas voulu rester en relations malgré une sympathie\r\n réciproque), la marquise de Villeparisis, de la célèbre famille de Bouillon, celle-ci lui avait dit: «Je crois que vous connaissez beaucoup M. Swann qui est un grand ami de mes neveux des Laumes». Ma grand’mère était revenue de sa\r\n visite enthousiasmée par la maison qui donnait sur des jardins et où M<sup>me</sup> de Villeparisis lui conseillait de louer, et aussi par un giletier et sa fille, qui avaient leur boutique dans la cour et chez qui elle était entrée\r\n demander qu’on fît un point à sa jupe qu’elle avait déchirée dans l’escalier. Ma grand’mère avait trouvé ces gens parfaits, elle déclarait que la petite était une perle et que le giletier était l’homme le plus distingué, le mieux\r\n qu’elle eût jamais vu. Car pour elle, la distinction était quelque chose d’absolument indépendant du rang social. Elle s’extasiait sur une réponse que le giletier lui avait faite, disant à maman: «Sévigné n’aurait pas mieux dit!» et\r\n en revanche, d’un neveu de M<sup>me</sup> de Villeparisis qu’elle avait rencontré chez elle: «Ah! ma fille, comme il est commun!»\r\n </p>\r\n <p>\r\n Or le propos relatif à Swann avait eu pour effet non pas de relever celui-ci dans l’esprit de ma grand’tante, mais d’y abaisser M<sup>me</sup> de Villeparisis. Il semblait que la considération que, sur la foi de ma grand’mère, nous\r\n accordions à M<sup>me</sup> de Villeparisis, lui créât un devoir de ne rien faire qui l’en rendît moins digne et auquel elle avait manqué en apprenant l’existence de Swann, en permettant à des parents à elle de le fréquenter. «Comment\r\n elle connaît Swann? Pour une personne que tu prétendais parente du maréchal de Mac-Mahon!» Cette opinion de mes parents sur les relations de Swann leur parut ensuite confirmée par son mariage avec une femme de la pire société, presque\r\n une cocotte que, d’ailleurs, il ne chercha jamais à présenter, continuant à venir seul chez nous, quoique de moins en moins, mais d’après laquelle ils crurent pouvoir juger—supposant que c’était là qu’il l’avait prise—le milieu,\r\n inconnu d’eux, qu’il fréquentait habituellement.\r\n </p>\r\n <p>\r\n Mais une fois, mon grand-père lut dans un journal que M. Swann était un des plus fidèles habitués des déjeuners du dimanche chez le duc de X..., dont le père et l’oncle avaient été les hommes d’État les plus en vue du règne de\r\n Louis-Philippe. Or mon grand-père était curieux de tous les petits faits qui pouvaient l’aider à entrer par la pensée dans la vie privée d’hommes comme Molé, comme le duc Pasquier, comme le duc de Broglie. Il fut enchanté d’apprendre\r\n que Swann fréquentait des gens qui les avaient connus. Ma grand’tante au contraire interpréta cette nouvelle dans un sens défavorable à Swann: quelqu’un qui choisissait ses fréquentations en dehors de la caste où il était né, en\r\n dehors de sa «classe» sociale, subissait à ses yeux un fâcheux déclassement. Il lui semblait qu’on renonçât d’un coup au fruit de toutes les belles relations avec des gens bien posés, qu’avaient honorablement entretenues et engrangées\r\n pour leurs enfants les familles prévoyantes; (ma grand’tante avait même cessé de voir le fils d’un notaire de nos amis parce qu’il avait épousé une altesse et était par là descendu pour elle du rang respecté de fils de notaire à celui\r\n d’un de ces aventuriers, anciens valets de chambre ou garçons d’écurie, pour qui on raconte que les reines eurent parfois des bontés). Elle blâma le projet qu’avait mon grand-père d’interroger Swann, le soir prochain où il devait\r\n venir dîner, sur ces amis que nous lui découvrions. D’autre part les deux sœurs de ma grand’mère, vieilles filles qui avaient sa noble nature mais non son esprit, déclarèrent ne pas comprendre le plaisir que leur beau-frère pouvait\r\n trouver à parler de niaiseries pareilles. C’étaient des personnes d’aspirations élevées et qui à cause de cela même étaient incapables de s’intéresser à ce qu’on appelle un potin, eût-il même un intérêt historique, et d’une façon\r\n générale à tout ce qui ne se rattachait pas directement à un objet esthétique ou vertueux. Le désintéressement de leur pensée était tel, à l’égard de tout ce qui, de près ou de loin semblait se rattacher à la vie mondaine, que leur\r\n sens auditif,—ayant fini par comprendre son inutilité momentanée dès qu’à dîner la conversation prenait un ton frivole ou seulement terre à terre sans que ces deux vieilles demoiselles aient pu la ramener aux sujets qui leur étaient\r\n chers,—mettait alors au repos ses organes récepteurs et leur laissait subir un véritable commencement d’atrophie. Si alors mon grand-père avait besoin d’attirer l’attention des deux sœurs, il fallait qu’il eût recours à ces\r\n avertissements physiques dont usent les médecins aliénistes à l’égard de certains maniaques de la distraction: coups frappés à plusieurs reprises sur un verre avec la lame d’un couteau, coïncidant avec une brusque interpellation de la\r\n voix et du regard, moyens violents que ces psychiatres transportent souvent dans les rapports courants avec des gens bien portants, soit par habitude professionnelle, soit qu’ils croient tout le monde un peu fou.\r\n </p>\r\n <p>\r\n Elles furent plus intéressées quand la veille du jour où Swann devait venir dîner, et leur avait personnellement envoyé une caisse de vin d’Asti, ma tante, tenant un numéro du Figaro où à côté du nom d’un tableau qui était à une\r\n Exposition de Corot, il y avait ces mots: «de la collection de M. Charles Swann», nous dit: «Vous avez vu que Swann a «les honneurs» du Figaro?»—«Mais je vous ai toujours dit qu’il avait beaucoup de goût», dit ma grand’mère.\r\n «Naturellement toi, du moment qu’il s’agit d’être d’un autre avis que nous», répondit ma grand’tante qui, sachant que ma grand’mère n’était jamais du même avis qu’elle, et n’étant bien sûre que ce fût à elle-même que nous donnions\r\n toujours raison, voulait nous arracher une condamnation en bloc des opinions de ma grand’mère contre lesquelles elle tâchait de nous solidariser de force avec les siennes. Mais nous restâmes silencieux. Les sœurs de ma grand’mère\r\n ayant manifesté l’intention de parler à Swann de ce mot du Figaro, ma grand’tante le leur déconseilla. Chaque fois qu’elle voyait aux autres un avantage si petit fût-il qu’elle n’avait pas, elle se persuadait que c’était non un\r\n avantage mais un mal et elle les plaignait pour ne pas avoir à les envier. «Je crois que vous ne lui feriez pas plaisir; moi je sais bien que cela me serait très désagréable de voir mon nom imprimé tout vif comme cela dans le journal,\r\n et je ne serais pas flattée du tout qu’on m’en parlât.» Elle ne s’entêta pas d’ailleurs à persuader les sœurs de ma grand’mère; car celles-ci par horreur de la vulgarité poussaient si loin l’art de dissimuler sous des périphrases\r\n ingénieuses une allusion personnelle qu’elle passait souvent inaperçue de celui même à qui elle s’adressait. Quant à ma mère elle ne pensait qu’à tâcher d’obtenir de mon père qu’il consentît à parler à Swann non de sa femme mais de sa\r\n fille qu’il adorait et à cause de laquelle disait-on il avait fini par faire ce mariage. «Tu pourrais ne lui dire qu’un mot, lui demander comment elle va. Cela doit être si cruel pour lui.» Mais mon père se fâchait: «Mais non! tu as\r\n des idées absurdes. Ce serait ridicule.»\r\n </p>\r\n <p>\r\n Mais le seul d’entre nous pour qui la venue de Swann devint l’objet d’une préoccupation douloureuse, ce fut moi. C’est que les soirs où des étrangers, ou seulement M. Swann, étaient là, maman ne montait pas dans ma chambre. Je ne\r\n dînais pas à table, je venais après dîner au jardin, et à neuf heures je disais bonsoir et allais me coucher. Je dînais avant tout le monde et je venais ensuite m’asseoir à table, jusqu’à huit heures où il était convenu que je devais\r\n monter; ce baiser précieux et fragile que maman me confiait d’habitude dans mon lit au moment de m’endormir il me fallait le transporter de la salle à manger dans ma chambre et le garder pendant tout le temps que je me déshabillais,\r\n sans que se brisât sa douceur, sans que se répandît et s’évaporât sa vertu volatile et, justement ces soirs-là où j’aurais eu besoin de le recevoir avec plus de précaution, il fallait que je le prisse, que je le dérobasse brusquement,\r\n publiquement, sans même avoir le temps et la liberté d’esprit nécessaires pour porter à ce que je faisais cette attention des maniaques qui s’efforcent de ne pas penser à autre chose pendant qu’ils ferment une porte, pour pouvoir,\r\n quand l’incertitude maladive leur revient, lui opposer victorieusement le souvenir du moment où ils l’ont fermée. Nous étions tous au jardin quand retentirent les deux coups hésitants de la clochette. On savait que c’était Swann;\r\n néanmoins tout le monde se regarda d’un air interrogateur et on envoya ma grand’mère en reconnaissance. «Pensez à le remercier intelligiblement de son vin, vous savez qu’il est délicieux et la caisse est énorme, recommanda mon\r\n grand-père à ses deux belles-sœurs.» «Ne commencez pas à chuchoter, dit ma grand’tante. Comme c’est confortable d’arriver dans une maison où tout le monde parle bas.» «Ah! voilà M. Swann. Nous allons lui demander s’il croit qu’il fera\r\n beau demain», dit mon père. Ma mère pensait qu’un mot d’elle effacerait toute la peine que dans notre famille on avait pu faire à Swann depuis son mariage. Elle trouva le moyen de l’emmener un peu à l’écart. Mais je la suivis; je ne\r\n pouvais me décider à la quitter d’un pas en pensant que tout à l’heure il faudrait que je la laisse dans la salle à manger et que je remonte dans ma chambre sans avoir comme les autres soirs la consolation qu’elle vînt m’embrasser.\r\n «Voyons, monsieur Swann, lui dit-elle, parlez-moi un peu de votre fille; je suis sûre qu’elle a déjà le goût des belles œuvres comme son papa.» «Mais venez donc vous asseoir avec nous tous sous la véranda», dit mon grand-père en\r\n s’approchant. Ma mère fut obligée de s’interrompre, mais elle tira de cette contrainte même une pensée délicate de plus, comme les bons poètes que la tyrannie de la rime force à trouver leurs plus grandes beautés: «Nous reparlerons\r\n d’elle quand nous serons tous les deux, dit-elle à mi-voix à Swann. Il n’y a qu’une maman qui soit digne de vous comprendre. Je suis sûre que la sienne serait de mon avis.» Nous nous assîmes tous autour de la table de fer. J’aurais\r\n voulu ne pas penser aux heures d’angoisse que je passerais ce soir seul dans ma chambre sans pouvoir m’endormir; je tâchais de me persuader qu’elles n’avaient aucune importance, puisque je les aurais oubliées demain matin, de\r\n m’attacher à des idées d’avenir qui auraient dû me conduire comme sur un pont au delà de l’abîme prochain qui m’effrayait. Mais mon esprit tendu par ma préoccupation, rendu convexe comme le regard que je dardais sur ma mère, ne se\r\n laissait pénétrer par aucune impression étrangère. Les pensées entraient bien en lui, mais à condition de laisser dehors tout élément de beauté ou simplement de drôlerie qui m’eût touché ou distrait. Comme un malade, grâce à un\r\n anesthésique, assiste avec une pleine lucidité à l’opération qu’on pratique sur lui, mais sans rien sentir, je pouvais me réciter des vers que j’aimais ou observer les efforts que mon grand-père faisait pour parler à Swann du duc\r\n d’Audiffret-Pasquier, sans que les premiers me fissent éprouver aucune émotion, les seconds aucune gaîté. Ces efforts furent infructueux. A peine mon grand-père eut-il posé à Swann une question relative à cet orateur qu’une des sœurs\r\n de ma grand’mère aux oreilles de qui cette question résonna comme un silence profond mais intempestif et qu’il était poli de rompre, interpella l’autre: «Imagine-toi, Céline, que j’ai fait la connaissance d’une jeune institutrice\r\n suédoise qui m’a donné sur les coopératives dans les pays scandinaves des détails tout ce qu’il y a de plus intéressants. Il faudra qu’elle vienne dîner ici un soir.» «Je crois bien! répondit sa sœur Flora, mais je n’ai pas perdu mon\r\n temps non plus. J’ai rencontré chez M. Vinteuil un vieux savant qui connaît beaucoup Maubant, et à qui Maubant a expliqué dans le plus grand détail comment il s’y prend pour composer un rôle. C’est tout ce qu’il y a de plus\r\n intéressant. C’est un voisin de M. Vinteuil, je n’en savais rien; et il est très aimable.» «Il n’y a pas que M. Vinteuil qui ait des voisins aimables», s’écria ma tante Céline d’une voix que la timidité rendait forte et la\r\n préméditation, factice, tout en jetant sur Swann ce qu’elle appelait un regard significatif. En même temps ma tante Flora qui avait compris que cette phrase était le remerciement de Céline pour le vin d’Asti, regardait également Swann\r\n avec un air mêlé de congratulation et d’ironie, soit simplement pour souligner le trait d’esprit de sa sœur, soit qu’elle enviât Swann de l’avoir inspiré, soit qu’elle ne pût s’empêcher de se moquer de lui parce qu’elle le croyait sur\r\n la sellette. «Je crois qu’on pourra réussir à avoir ce monsieur à dîner, continua Flora; quand on le met sur Maubant ou sur M<sup>me</sup> Materna, il parle des heures sans s’arrêter.» «Ce doit être délicieux», soupira mon grand-père\r\n dans l’esprit de qui la nature avait malheureusement aussi complètement omis d’inclure la possibilité de s’intéresser passionnément aux coopératives suédoises ou à la composition des rôles de Maubant, qu’elle avait oublié de fournir\r\n celui des sœurs de ma grand’mère du petit grain de sel qu’il faut ajouter soi-même pour y trouver quelque saveur, à un récit sur la vie intime de Molé ou du comte de Paris. «Tenez, dit Swann à mon grand-père, ce que je vais vous dire\r\n a plus de rapports que cela n’en a l’air avec ce que vous me demandiez, car sur certains points les choses n’ont pas énormément changé. Je relisais ce matin dans Saint-Simon quelque chose qui vous aurait amusé. C’est dans le volume\r\n sur son ambassade d’Espagne; ce n’est pas un des meilleurs, ce n’est guère qu’un journal, mais du moins un journal merveilleusement écrit, ce qui fait déjà une première différence avec les assommants journaux que nous nous croyons\r\n obligés de lire matin et soir.» «Je ne suis pas de votre avis, il y a des jours où la lecture des journaux me semble fort agréable...», interrompit ma tante Flora, pour montrer qu’elle avait lu la phrase sur le Corot de Swann dans le\r\n Figaro. «Quand ils parlent de choses ou de gens qui nous intéressent!» enchérit ma tante Céline. «Je ne dis pas non, répondit Swann étonné. Ce que je reproche aux journaux c’est de nous faire faire attention tous les jours à des\r\n choses insignifiantes tandis que nous lisons trois ou quatre fois dans notre vie les livres où il y a des choses essentielles. Du moment que nous déchirons fiévreusement chaque matin la bande du journal, alors on devrait changer les\r\n choses et mettre dans le journal, moi je ne sais pas, les... Pensées de Pascal! (il détacha ce mot d’un ton d’emphase ironique pour ne pas avoir l’air pédant). Et c’est dans le volume doré sur tranches que nous n’ouvrons qu’une fois\r\n tous les dix ans, ajouta-t-il en témoignant pour les choses mondaines ce dédain qu’affectent certains hommes du monde, que nous lirions que la reine de Grèce est allée à Cannes ou que la princesse de Léon a donné un bal costumé. Comme\r\n cela la juste proportion serait rétablie.» Mais regrettant de s’être laissé aller à parler même légèrement de choses sérieuses: «Nous avons une bien belle conversation, dit-il ironiquement, je ne sais pas pourquoi nous abordons ces\r\n «sommets», et se tournant vers mon grand-père: «Donc Saint-Simon raconte que Maulevrier avait eu l’audace de tendre la main à ses fils. Vous savez, c’est ce Maulevrier dont il dit: «Jamais je ne vis dans cette épaisse bouteille que de\r\n l’humeur, de la grossièreté et des sottises.» «Épaisses ou non, je connais des bouteilles où il y a tout autre chose», dit vivement Flora, qui tenait à avoir remercié Swann elle aussi, car le présent de vin d’Asti s’adressait aux\r\n deux. Céline se mit à rire. Swann interloqué reprit: «Je ne sais si ce fut ignorance ou panneau, écrit Saint-Simon, il voulut donner la main à mes enfants. Je m’en aperçus assez tôt pour l’en empêcher.» Mon grand-père s’extasiait déjà\r\n sur «ignorance ou panneau», mais M<sup>lle</sup> Céline, chez qui le nom de Saint-Simon,—un littérateur,—avait empêché l’anesthésie complète des facultés auditives, s’indignait déjà: «Comment? vous admirez cela? Eh bien! c’est du\r\n joli! Mais qu’est-ce que cela peut vouloir dire; est-ce qu’un homme n’est pas autant qu’un autre? Qu’est-ce que cela peut faire qu’il soit duc ou cocher s’il a de l’intelligence et du cœur? Il avait une belle manière d’élever ses\r\n enfants, votre Saint-Simon, s’il ne leur disait pas de donner la main à tous les honnêtes gens. Mais c’est abominable, tout simplement. Et vous osez citer cela?» Et mon grand-père navré, sentant l’impossibilité, devant cette\r\n obstruction, de chercher à faire raconter à Swann, les histoires qui l’eussent amusé disait à voix basse à maman: «Rappelle-moi donc le vers que tu m’as appris et qui me soulage tant dans ces moments-là. Ah! oui: «Seigneur, que de\r\n vertus vous nous faites haïr!» Ah! comme c’est bien!»\r\n </p>\r\n <p>\r\n Je ne quittais pas ma mère des yeux, je savais que quand on serait à table, on ne me permettrait pas de rester pendant toute la durée du dîner et que pour ne pas contrarier mon père, maman ne me laisserait pas l’embrasser à plusieurs\r\n reprises devant le monde, comme si ç’avait été dans ma chambre. Aussi je me promettais, dans la salle à manger, pendant qu’on commencerait à dîner et que je sentirais approcher l’heure, de faire d’avance de ce baiser qui serait si\r\n court et furtif, tout ce que j’en pouvais faire seul, de choisir avec mon regard la place de la joue que j’embrasserais, de préparer ma pensée pour pouvoir grâce à ce commencement mental de baiser consacrer toute la minute que\r\n m’accorderait maman à sentir sa joue contre mes lèvres, comme un peintre qui ne peut obtenir que de courtes séances de pose, prépare sa palette, et a fait d’avance de souvenir, d’après ses notes, tout ce pour quoi il pouvait à la\r\n rigueur se passer de la présence du modèle. Mais voici qu’avant que le dîner fût sonné mon grand-père eut la férocité inconsciente de dire: «Le petit a l’air fatigué, il devrait monter se coucher. On dîne tard du reste ce soir.» Et\r\n mon père, qui ne gardait pas aussi scrupuleusement que ma grand’mère et que ma mère la foi des traités, dit: «Oui, allons, vas te coucher.» Je voulus embrasser maman, à cet instant on entendit la cloche du dîner. «Mais non, voyons,\r\n laisse ta mère, vous vous êtes assez dit bonsoir comme cela, ces manifestations sont ridicules. Allons, monte!» Et il me fallut partir sans viatique; il me fallut monter chaque marche de l’escalier, comme dit l’expression populaire, à\r\n «contre-cœur», montant contre mon cœur qui voulait retourner près de ma mère parce qu’elle ne lui avait pas, en m’embrassant, donné licence de me suivre. Cet escalier détesté où je m’engageais toujours si tristement, exhalait une\r\n odeur de vernis qui avait en quelque sorte absorbé, fixé, cette sorte particulière de chagrin que je ressentais chaque soir et la rendait peut-être plus cruelle encore pour ma sensibilité parce que sous cette forme olfactive mon\r\n intelligence n’en pouvait plus prendre sa part. Quand nous dormons et qu’une rage de dents n’est encore perçue par nous que comme une jeune fille que nous nous efforçons deux cents fois de suite de tirer de l’eau ou que comme un vers\r\n de Molière que nous nous répétons sans arrêter, c’est un grand soulagement de nous réveiller et que notre intelligence puisse débarrasser l’idée de rage de dents, de tout déguisement héroïque ou cadencé. C’est l’inverse de ce\r\n soulagement que j’éprouvais quand mon chagrin de monter dans ma chambre entrait en moi d’une façon infiniment plus rapide, presque instantanée, à la fois insidieuse et brusque, par l’inhalation,—beaucoup plus toxique que la\r\n pénétration morale,—de l’odeur de vernis particulière à cet escalier. Une fois dans ma chambre, il fallut boucher toutes les issues, fermer les volets, creuser mon propre tombeau, en défaisant mes couvertures, revêtir le suaire de ma\r\n chemise de nuit. Mais avant de m’ensevelir dans le lit de fer qu’on avait ajouté dans la chambre parce que j’avais trop chaud l’été sous les courtines de reps du grand lit, j’eus un mouvement de révolte, je voulus essayer d’une ruse\r\n de condamné. J’écrivis à ma mère en la suppliant de monter pour une chose grave que je ne pouvais lui dire dans ma lettre. Mon effroi était que Françoise, la cuisinière de ma tante qui était chargée de s’occuper de moi quand j’étais à\r\n Combray, refusât de porter mon mot. Je me doutais que pour elle, faire une commission à ma mère quand il y avait du monde lui paraîtrait aussi impossible que pour le portier d’un théâtre de remettre une lettre à un acteur pendant\r\n qu’il est en scène. Elle possédait à l’égard des choses qui peuvent ou ne peuvent pas se faire un code impérieux, abondant, subtil et intransigeant sur des distinctions insaisissables ou oiseuses (ce qui lui donnait l’apparence de ces\r\n lois antiques qui, à côté de prescriptions féroces comme de massacrer les enfants à la mamelle, défendent avec une délicatesse exagérée de faire bouillir le chevreau dans le lait de sa mère, ou de manger dans un animal le nerf de la\r\n cuisse). Ce code, si l’on en jugeait par l’entêtement soudain qu’elle mettait à ne pas vouloir faire certaines commissions que nous lui donnions, semblait avoir prévu des complexités sociales et des raffinements mondains tels que rien\r\n dans l’entourage de Françoise et dans sa vie de domestique de village n’avait pu les lui suggérer; et l’on était obligé de se dire qu’il y avait en elle un passé français très ancien, noble et mal compris, comme dans ces cités\r\n manufacturières où de vieux hôtels témoignent qu’il y eut jadis une vie de cour, et où les ouvriers d’une usine de produits chimiques travaillent au milieu de délicates sculptures qui représentent le miracle de saint Théophile ou les\r\n quatre fils Aymon. Dans le cas particulier, l’article du code à cause duquel il était peu probable que sauf le cas d’incendie Françoise allât déranger maman en présence de M. Swann pour un aussi petit personnage que moi, exprimait\r\n simplement le respect qu’elle professait non seulement pour les parents,—comme pour les morts, les prêtres et les rois,—mais encore pour l’étranger à qui on donne l’hospitalité, respect qui m’aurait peut-être touché dans un livre mais\r\n qui m’irritait toujours dans sa bouche, à cause du ton grave et attendri qu’elle prenait pour en parler, et davantage ce soir où le caractère sacré qu’elle conférait au dîner avait pour effet qu’elle refuserait d’en troubler la\r\n cérémonie. Mais pour mettre une chance de mon côté, je n’hésitai pas à mentir et à lui dire que ce n’était pas du tout moi qui avais voulu écrire à maman, mais que c’était maman qui, en me quittant, m’avait recommandé de ne pas\r\n oublier de lui envoyer une réponse relativement à un objet qu’elle m’avait prié de chercher; et elle serait certainement très fâchée si on ne lui remettait pas ce mot. Je pense que Françoise ne me crut pas, car, comme les hommes\r\n primitifs dont les sens étaient plus puissants que les nôtres, elle discernait immédiatement, à des signes insaisissables pour nous, toute vérité que nous voulions lui cacher; elle regarda pendant cinq minutes l’enveloppe comme si\r\n l’examen du papier et l’aspect de l’écriture allaient la renseigner sur la nature du contenu ou lui apprendre à quel article de son code elle devait se référer. Puis elle sortit d’un air résigné qui semblait signifier: «C’est-il pas\r\n malheureux pour des parents d’avoir un enfant pareil!» Elle revint au bout d’un moment me dire qu’on n’en était encore qu’à la glace, qu’il était impossible au maître d’hôtel de remettre la lettre en ce moment devant tout le monde,\r\n mais que, quand on serait aux rince-bouche, on trouverait le moyen de la faire passer à maman. Aussitôt mon anxiété tomba; maintenant ce n’était plus comme tout à l’heure pour jusqu’à demain que j’avais quitté ma mère, puisque mon\r\n petit mot allait, la fâchant sans doute (et doublement parce que ce manège me rendrait ridicule aux yeux de Swann), me faire du moins entrer invisible et ravi dans la même pièce qu’elle, allait lui parler de moi à l’oreille; puisque\r\n cette salle à manger interdite, hostile, où, il y avait un instant encore, la glace elle-même—le «granité»—et les rince-bouche me semblaient recéler des plaisirs malfaisants et mortellement tristes parce que maman les goûtait loin de\r\n moi, s’ouvrait à moi et, comme un fruit devenu doux qui brise son enveloppe, allait faire jaillir, projeter jusqu’à mon cœur enivré l’attention de maman tandis qu’elle lirait mes lignes. Maintenant je n’étais plus séparé d’elle; les\r\n barrières étaient tombées, un fil délicieux nous réunissait. Et puis, ce n’était pas tout: maman allait sans doute venir!\r\n </p>\r\n <p>\r\n L’angoisse que je venais d’éprouver, je pensais que Swann s’en serait bien moqué s’il avait lu ma lettre et en avait deviné le but; or, au contraire, comme je l’ai appris plus tard, une angoisse semblable fut le tourment de longues\r\n années de sa vie et personne, aussi bien que lui peut-être, n’aurait pu me comprendre; lui, cette angoisse qu’il y a à sentir l’être qu’on aime dans un lieu de plaisir où l’on n’est pas, où l’on ne peut pas le rejoindre, c’est l’amour\r\n qui la lui a fait connaître, l’amour auquel elle est en quelque sorte prédestinée, par lequel elle sera accaparée, spécialisée; mais quand, comme pour moi, elle est entrée en nous avant qu’il ait encore fait son apparition dans notre\r\n vie, elle flotte en l’attendant, vague et libre, sans affectation déterminée, au service un jour d’un sentiment, le lendemain d’un autre, tantôt de la tendresse filiale ou de l’amitié pour un camarade. Et la joie avec laquelle je fis\r\n mon premier apprentissage quand Françoise revint me dire que ma lettre serait remise, Swann l’avait bien connue aussi cette joie trompeuse que nous donne quelque ami, quelque parent de la femme que nous aimons, quand arrivant à\r\n l’hôtel ou au théâtre où elle se trouve, pour quelque bal, redoute, ou première où il va la retrouver, cet ami nous aperçoit errant dehors, attendant désespérément quelque occasion de communiquer avec elle. Il nous reconnaît, nous\r\n aborde familièrement, nous demande ce que nous faisons là. Et comme nous inventons que nous avons quelque chose d’urgent à dire à sa parente ou amie, il nous assure que rien n’est plus simple, nous fait entrer dans le vestibule et\r\n nous promet de nous l’envoyer avant cinq minutes. Que nous l’aimons—comme en ce moment j’aimais Françoise—, l’intermédiaire bien intentionné qui d’un mot vient de nous rendre supportable, humaine et presque propice la fête\r\n inconcevable, infernale, au sein de laquelle nous croyions que des tourbillons ennemis, pervers et délicieux entraînaient loin de nous, la faisant rire de nous, celle que nous aimons. Si nous en jugeons par lui, le parent qui nous a\r\n accosté et qui est lui aussi un des initiés des cruels mystères, les autres invités de la fête ne doivent rien avoir de bien démoniaque. Ces heures inaccessibles et suppliciantes où elle allait goûter des plaisirs inconnus, voici que\r\n par une brèche inespérée nous y pénétrons; voici qu’un des moments dont la succession les aurait composées, un moment aussi réel que les autres, même peut-être plus important pour nous, parce que notre maîtresse y est plus mêlée, nous\r\n nous le représentons, nous le possédons, nous y intervenons, nous l’avons créé presque: le moment où on va lui dire que nous sommes là, en bas. Et sans doute les autres moments de la fête ne devaient pas être d’une essence bien\r\n différente de celui-là, ne devaient rien avoir de plus délicieux et qui dût tant nous faire souffrir puisque l’ami bienveillant nous a dit: «Mais elle sera ravie de descendre! Cela lui fera beaucoup plus de plaisir de causer avec vous\r\n que de s’ennuyer là-haut.» Hélas! Swann en avait fait l’expérience, les bonnes intentions d’un tiers sont sans pouvoir sur une femme qui s’irrite de se sentir poursuivie jusque dans une fête par quelqu’un qu’elle n’aime pas. Souvent,\r\n l’ami redescend seul.\r\n </p>\r\n <p>\r\n Ma mère ne vint pas, et sans ménagements pour mon amour-propre (engagé à ce que la fable de la recherche dont elle était censée m’avoir prié de lui dire le résultat ne fût pas démentie) me fit dire par Françoise ces mots: «Il n’y a\r\n pas de réponse» que depuis j’ai si souvent entendu des concierges de «palaces» ou des valets de pied de tripots, rapporter à quelque pauvre fille qui s’étonne: «Comment, il n’a rien dit, mais c’est impossible! Vous avez pourtant bien\r\n remis ma lettre. C’est bien, je vais attendre encore.» Et—de même qu’elle assure invariablement n’avoir pas besoin du bec supplémentaire que le concierge veut allumer pour elle, et reste là, n’entendant plus que les rares propos sur\r\n le temps qu’il fait échangés entre le concierge et un chasseur qu’il envoie tout d’un coup en s’apercevant de l’heure, faire rafraîchir dans la glace la boisson d’un client,—ayant décliné l’offre de Françoise de me faire de la tisane\r\n ou de rester auprès de moi, je la laissai retourner à l’office, je me couchai et je fermai les yeux en tâchant de ne pas entendre la voix de mes parents qui prenaient le café au jardin. Mais au bout de quelques secondes, je sentis\r\n qu’en écrivant ce mot à maman, en m’approchant, au risque de la fâcher, si près d’elle que j’avais cru toucher le moment de la revoir, je m’étais barré la possibilité de m’endormir sans l’avoir revue, et les battements de mon cœur, de\r\n minute en minute devenaient plus douloureux parce que j’augmentais mon agitation en me prêchant un calme qui était l’acceptation de mon infortune. Tout à coup mon anxiété tomba, une félicité m’envahit comme quand un médicament\r\n puissant commence à agir et nous enlève une douleur: je venais de prendre la résolution de ne plus essayer de m’endormir sans avoir revu maman, de l’embrasser coûte que coûte, bien que ce fût avec la certitude d’être ensuite fâché\r\n pour longtemps avec elle, quand elle remonterait se coucher. Le calme qui résultait de mes angoisses finies me mettait dans un allégresse extraordinaire, non moins que l’attente, la soif et la peur du danger. J’ouvris la fenêtre sans\r\n bruit et m’assis au pied de mon lit; je ne faisais presque aucun mouvement afin qu’on ne m’entendît pas d’en bas. Dehors, les choses semblaient, elles aussi, figées en une muette attention à ne pas troubler le clair de lune, qui\r\n doublant et reculant chaque chose par l’extension devant elle de son reflet, plus dense et concret qu’elle-même, avait à la fois aminci et agrandi le paysage comme un plan replié jusque-là, qu’on développe. Ce qui avait besoin de\r\n bouger, quelque feuillage de marronnier, bougeait. Mais son frissonnement minutieux, total, exécuté jusque dans ses moindres nuances et ses dernières délicatesses, ne bavait pas sur le reste, ne se fondait pas avec lui, restait\r\n circonscrit. Exposés sur ce silence qui n’en absorbait rien, les bruits les plus éloignés, ceux qui devaient venir de jardins situés à l’autre bout de la ville, se percevaient détaillés avec un tel «fini» qu’ils semblaient ne devoir\r\n cet effet de lointain qu’à leur pianissimo, comme ces motifs en sourdine si bien exécutés par l’orchestre du Conservatoire que quoiqu’on n’en perde pas une note on croit les entendre cependant loin de la salle du concert et que tous\r\n les vieux abonnés,—les sœurs de ma grand’mère aussi quand Swann leur avait donné ses places,—tendaient l’oreille comme s’ils avaient écouté les progrès lointains d’une armée en marche qui n’aurait pas encore tourné la rue de Trévise.\r\n </p>\r\n <p>\r\n Je savais que le cas dans lequel je me mettais était de tous celui qui pouvait avoir pour moi, de la part de mes parents, les conséquences les plus graves, bien plus graves en vérité qu’un étranger n’aurait pu le supposer, de celles\r\n qu’il aurait cru que pouvaient produire seules des fautes vraiment honteuses. Mais dans l’éducation qu’on me donnait, l’ordre des fautes n’était pas le même que dans l’éducation des autres enfants et on m’avait habitué à placer avant\r\n toutes les autres (parce que sans doute il n’y en avait pas contre lesquelles j’eusse besoin d’être plus soigneusement gardé) celles dont je comprends maintenant que leur caractère commun est qu’on y tombe en cédant à une impulsion\r\n nerveuse. Mais alors on ne prononçait pas ce mot, on ne déclarait pas cette origine qui aurait pu me faire croire que j’étais excusable d’y succomber ou même peut-être incapable d’y résister. Mais je les reconnaissais bien à\r\n l’angoisse qui les précédait comme à la rigueur du châtiment qui les suivait; et je savais que celle que je venais de commettre était de la même famille que d’autres pour lesquelles j’avais été sévèrement puni, quoique infiniment plus\r\n grave. Quand j’irais me mettre sur le chemin de ma mère au moment où elle monterait se coucher, et qu’elle verrait que j’étais resté levé pour lui redire bonsoir dans le couloir, on ne me laisserait plus rester à la maison, on me\r\n mettrait au collège le lendemain, c’était certain. Eh bien! dusse-je me jeter par la fenêtre cinq minutes après, j’aimais encore mieux cela. Ce que je voulais maintenant c’était maman, c’était lui dire bonsoir, j’étais allé trop loin\r\n dans la voie qui menait à la réalisation de ce désir pour pouvoir rebrousser chemin.\r\n </p>\r\n <p>\r\n J’entendis les pas de mes parents qui accompagnaient Swann; et quand le grelot de la porte m’eut averti qu’il venait de partir, j’allai à la fenêtre. Maman demandait à mon père s’il avait trouvé la langouste bonne et si M. Swann avait\r\n repris de la glace au café et à la pistache. «Je l’ai trouvée bien quelconque, dit ma mère; je crois que la prochaine fois il faudra essayer d’un autre parfum.» «Je ne peux pas dire comme je trouve que Swann change, dit ma\r\n grand’tante, il est d’un vieux!» Ma grand’tante avait tellement l’habitude de voir toujours en Swann un même adolescent, qu’elle s’étonnait de le trouver tout à coup moins jeune que l’âge qu’elle continuait à lui donner. Et mes\r\n parents du reste commençaient à lui trouver cette vieillesse anormale, excessive, honteuse et méritée des célibataires, de tous ceux pour qui il semble que le grand jour qui n’a pas de lendemain soit plus long que pour les autres,\r\n parce que pour eux il est vide et que les moments s’y additionnent depuis le matin sans se diviser ensuite entre des enfants. «Je crois qu’il a beaucoup de soucis avec sa coquine de femme qui vit au su de tout Combray avec un certain\r\n monsieur de Charlus. C’est la fable de la ville.» Ma mère fit remarquer qu’il avait pourtant l’air bien moins triste depuis quelque temps. «Il fait aussi moins souvent ce geste qu’il a tout à fait comme son père de s’essuyer les yeux\r\n et de se passer la main sur le front. Moi je crois qu’au fond il n’aime plus cette femme.» «Mais naturellement il ne l’aime plus, répondit mon grand-père. J’ai reçu de lui il y a déjà longtemps une lettre à ce sujet, à laquelle je me\r\n suis empressé de ne pas me conformer, et qui ne laisse aucun doute sur ses sentiments au moins d’amour, pour sa femme. Hé bien! vous voyez, vous ne l’avez pas remercié pour l’Asti», ajouta mon grand-père en se tournant vers ses deux\r\n belles-sœurs. «Comment, nous ne l’avons pas remercié? je crois, entre nous, que je lui ai même tourné cela assez délicatement», répondit ma tante Flora. «Oui, tu as très bien arrangé cela: je t’ai admirée», dit ma tante Céline. «Mais\r\n toi tu as été très bien aussi.» «Oui j’étais assez fière de ma phrase sur les voisins aimables.» «Comment, c’est cela que vous appelez remercier! s’écria mon grand-père. J’ai bien entendu cela, mais du diable si j’ai cru que c’était\r\n pour Swann. Vous pouvez être sûres qu’il n’a rien compris.» «Mais voyons, Swann n’est pas bête, je suis certaine qu’il a apprécié. Je ne pouvais cependant pas lui dire le nombre de bouteilles et le prix du vin!» Mon père et ma mère\r\n restèrent seuls, et s’assirent un instant; puis mon père dit: «Hé bien! si tu veux, nous allons monter nous coucher.» «Si tu veux, mon ami, bien que je n’aie pas l’ombre de sommeil; ce n’est pas cette glace au café si anodine qui a pu\r\n pourtant me tenir si éveillée; mais j’aperçois de la lumière dans l’office et puisque la pauvre Françoise m’a attendue, je vais lui demander de dégrafer mon corsage pendant que tu vas te déshabiller.» Et ma mère ouvrit la porte\r\n treillagée du vestibule qui donnait sur l’escalier. Bientôt, je l’entendis qui montait fermer sa fenêtre. J’allai sans bruit dans le couloir; mon cœur battait si fort que j’avais de la peine à avancer, mais du moins il ne battait plus\r\n d’anxiété, mais d’épouvante et de joie. Je vis dans la cage de l’escalier la lumière projetée par la bougie de maman. Puis je la vis elle-même; je m’élançai. À la première seconde, elle me regarda avec étonnement, ne comprenant pas ce\r\n qui était arrivé. Puis sa figure prit une expression de colère, elle ne me disait même pas un mot, et en effet pour bien moins que cela on ne m’adressait plus la parole pendant plusieurs jours. Si maman m’avait dit un mot, ç’aurait\r\n été admettre qu’on pouvait me reparler et d’ailleurs cela peut-être m’eût paru plus terrible encore, comme un signe que devant la gravité du châtiment qui allait se préparer, le silence, la brouille, eussent été puérils. Une parole\r\n c’eût été le calme avec lequel on répond à un domestique quand on vient de décider de le renvoyer; le baiser qu’on donne à un fils qu’on envoie s’engager alors qu’on le lui aurait refusé si on devait se contenter d’être fâché deux\r\n jours avec lui. Mais elle entendit mon père qui montait du cabinet de toilette où il était allé se déshabiller et pour éviter la scène qu’il me ferait, elle me dit d’une voix entrecoupée par la colère: «Sauve-toi, sauve-toi, qu’au\r\n moins ton père ne t’ait vu ainsi attendant comme un fou!» Mais je lui répétais: «Viens me dire bonsoir», terrifié en voyant que le reflet de la bougie de mon père s’élevait déjà sur le mur, mais aussi usant de son approche comme d’un\r\n moyen de chantage et espérant que maman, pour éviter que mon père me trouvât encore là si elle continuait à refuser, allait me dire: «Rentre dans ta chambre, je vais venir.» Il était trop tard, mon père était devant nous. Sans le\r\n vouloir, je murmurai ces mots que personne n’entendit: «Je suis perdu!»\r\n </p>\r\n <p>\r\n Il n’en fut pas ainsi. Mon père me refusait constamment des permissions qui m’avaient été consenties dans les pactes plus larges octroyés par ma mère et ma grand’mère parce qu’il ne se souciait pas des «principes» et qu’il n’y avait\r\n pas avec lui de «Droit des gens». Pour une raison toute contingente, ou même sans raison, il me supprimait au dernier moment telle promenade si habituelle, si consacrée, qu’on ne pouvait m’en priver sans parjure, ou bien, comme il\r\n avait encore fait ce soir, longtemps avant l’heure rituelle, il me disait: «Allons, monte te coucher, pas d’explication!» Mais aussi, parce qu’il n’avait pas de principes (dans le sens de ma grand’mère), il n’avait pas à proprement\r\n parler d’intransigeance. Il me regarda un instant d’un air étonné et fâché, puis dès que maman lui eut expliqué en quelques mots embarrassés ce qui était arrivé, il lui dit: «Mais va donc avec lui, puisque tu disais justement que tu\r\n n’as pas envie de dormir, reste un peu dans sa chambre, moi je n’ai besoin de rien.» «Mais, mon ami, répondit timidement ma mère, que j’aie envie ou non de dormir, ne change rien à la chose, on ne peut pas habituer cet enfant...»\r\n «Mais il ne s’agit pas d’habituer, dit mon père en haussant les épaules, tu vois bien que ce petit a du chagrin, il a l’air désolé, cet enfant; voyons, nous ne sommes pas des bourreaux! Quand tu l’auras rendu malade, tu seras bien\r\n avancée! Puisqu’il y a deux lits dans sa chambre, dis donc à Françoise de te préparer le grand lit et couche pour cette nuit auprès de lui. Allons, bonsoir, moi qui ne suis pas si nerveux que vous, je vais me coucher.»\r\n </p>\r\n <p>\r\n On ne pouvait pas remercier mon père; on l’eût agacé par ce qu’il appelait des sensibleries. Je restai sans oser faire un mouvement; il était encore devant nous, grand, dans sa robe de nuit blanche sous le cachemire de l’Inde violet\r\n et rose qu’il nouait autour de sa tête depuis qu’il avait des névralgies, avec le geste d’Abraham dans la gravure d’après Benozzo Gozzoli que m’avait donnée M. Swann, disant à Sarah qu’elle a à se départir du côté d’Isaac. Il y a bien\r\n des années de cela. La muraille de l’escalier, où je vis monter le reflet de sa bougie n’existe plus depuis longtemps. En moi aussi bien des choses ont été détruites que je croyais devoir durer toujours et de nouvelles se sont\r\n édifiées donnant naissance à des peines et à des joies nouvelles que je n’aurais pu prévoir alors, de même que les anciennes me sont devenues difficiles à comprendre. Il y a bien longtemps aussi que mon père a cessé de pouvoir dire à\r\n maman: «Va avec le petit.» La possibilité de telles heures ne renaîtra jamais pour moi. Mais depuis peu de temps, je recommence à très bien percevoir si je prête l’oreille, les sanglots que j’eus la force de contenir devant mon père\r\n et qui n’éclatèrent que quand je me retrouvai seul avec maman. En réalité ils n’ont jamais cessé; et c’est seulement parce que la vie se tait maintenant davantage autour de moi que je les entends de nouveau, comme ces cloches de\r\n couvents que couvrent si bien les bruits de la ville pendant le jour qu’on les croirait arrêtées mais qui se remettent à sonner dans le silence du soir.\r\n </p>\r\n <p>\r\n Maman passa cette nuit-là dans ma chambre; au moment où je venais de commettre une faute telle que je m’attendais à être obligé de quitter la maison, mes parents m’accordaient plus que je n’eusse jamais obtenu d’eux comme récompense\r\n d’une belle action. Même à l’heure où elle se manifestait par cette grâce, la conduite de mon père à mon égard gardait ce quelque chose d’arbitraire et d’immérité qui la caractérisait et qui tenait à ce que généralement elle résultait\r\n plutôt de convenances fortuites que d’un plan prémédité. Peut-être même que ce que j’appelais sa sévérité, quand il m’envoyait me coucher, méritait moins ce nom que celle de ma mère ou ma grand’mère, car sa nature, plus différente en\r\n certains points de la mienne que n’était la leur, n’avait probablement pas deviné jusqu’ici combien j’étais malheureux tous les soirs, ce que ma mère et ma grand’mère savaient bien; mais elles m’aimaient assez pour ne pas consentir à\r\n m’épargner de la souffrance, elles voulaient m’apprendre à la dominer afin de diminuer ma sensibilité nerveuse et fortifier ma volonté. Pour mon père, dont l’affection pour moi était d’une autre sorte, je ne sais pas s’il aurait eu ce\r\n courage: pour une fois où il venait de comprendre que j’avais du chagrin, il avait dit à ma mère: «Va donc le consoler.» Maman resta cette nuit-là dans ma chambre et, comme pour ne gâter d’aucun remords ces heures si différentes de ce\r\n que j’avais eu le droit d’espérer, quand Françoise, comprenant qu’il se passait quelque chose d’extraordinaire en voyant maman assise près de moi, qui me tenait la main et me laissait pleurer sans me gronder, lui demanda: «Mais\r\n Madame, qu’a donc Monsieur à pleurer ainsi?» maman lui répondit: «Mais il ne sait pas lui-même, Françoise, il est énervé; préparez-moi vite le grand lit et montez vous coucher.» Ainsi, pour la première fois, ma tristesse n’était plus\r\n considérée comme une faute punissable mais comme un mal involontaire qu’on venait de reconnaître officiellement, comme un état nerveux dont je n’étais pas responsable; j’avais le soulagement de n’avoir plus à mêler de scrupules à\r\n l’amertume de mes larmes, je pouvais pleurer sans péché. Je n’étais pas non plus médiocrement fier vis-à-vis de Françoise de ce retour des choses humaines, qui, une heure après que maman avait refusé de monter dans ma chambre et\r\n m’avait fait dédaigneusement répondre que je devrais dormir, m’élevait à la dignité de grande personne et m’avait fait atteindre tout d’un coup à une sorte de puberté du chagrin, d’émancipation des larmes. J’aurais dû être heureux: je\r\n ne l’étais pas. Il me semblait que ma mère venait de me faire une première concession qui devait lui être douloureuse, que c’était une première abdication de sa part devant l’idéal qu’elle avait conçu pour moi, et que pour la première\r\n fois, elle, si courageuse, s’avouait vaincue. Il me semblait que si je venais de remporter une victoire c’était contre elle, que j’avais réussi comme auraient pu faire la maladie, des chagrins, ou l’âge, à détendre sa volonté, à faire\r\n fléchir sa raison et que cette soirée commençait une ère, resterait comme une triste date. Si j’avais osé maintenant, j’aurais dit à maman: «Non je ne veux pas, ne couche pas ici.» Mais je connaissais la sagesse pratique, réaliste\r\n comme on dirait aujourd’hui, qui tempérait en elle la nature ardemment idéaliste de ma grand’mère, et je savais que, maintenant que le mal était fait, elle aimerait mieux m’en laisser du moins goûter le plaisir calmant et ne pas\r\n déranger mon père. Certes, le beau visage de ma mère brillait encore de jeunesse ce soir-là où elle me tenait si doucement les mains et cherchait à arrêter mes larmes; mais justement il me semblait que cela n’aurait pas dû être, sa\r\n colère eût été moins triste pour moi que cette douceur nouvelle que n’avait pas connue mon enfance; il me semblait que je venais d’une main impie et secrète de tracer dans son âme une première ride et d’y faire apparaître un premier\r\n cheveu blanc. Cette pensée redoubla mes sanglots et alors je vis maman, qui jamais ne se laissait aller à aucun attendrissement avec moi, être tout d’un coup gagnée par le mien et essayer de retenir une envie de pleurer. Comme elle\r\n sentit que je m’en étais aperçu, elle me dit en riant: «Voilà mon petit jaunet, mon petit serin, qui va rendre sa maman aussi bêtasse que lui, pour peu que cela continue. Voyons, puisque tu n’as pas sommeil ni ta maman non plus, ne\r\n restons pas à nous énerver, faisons quelque chose, prenons un de tes livres.» Mais je n’en avais pas là. «Est-ce que tu aurais moins de plaisir si je sortais déjà les livres que ta grand’mère doit te donner pour ta fête? Pense bien:\r\n tu ne seras pas déçu de ne rien avoir après-demain?» J’étais au contraire enchanté et maman alla chercher un paquet de livres dont je ne pus deviner, à travers le papier qui les enveloppait, que la taille courte et large, mais qui,\r\n sous ce premier aspect, pourtant sommaire et voilé, éclipsaient déjà la boîte à couleurs du Jour de l’An et les vers à soie de l’an dernier. C’était la Mare au Diable, François le Champi, la Petite Fadette et les Maîtres Sonneurs. Ma\r\n grand’mère, ai-je su depuis, avait d’abord choisi les poésies de Musset, un volume de Rousseau et Indiana; car si elle jugeait les lectures futiles aussi malsaines que les bonbons et les pâtisseries, elles ne pensait pas que les\r\n grands souffles du génie eussent sur l’esprit même d’un enfant une influence plus dangereuse et moins vivifiante que sur son corps le grand air et le vent du large. Mais mon père l’ayant presque traitée de folle en apprenant les\r\n livres qu’elle voulait me donner, elle était retournée elle-même à Jouy-le-Vicomte chez le libraire pour que je ne risquasse pas de ne pas avoir mon cadeau (c’était un jour brûlant et elle était rentrée si souffrante que le médecin\r\n avait averti ma mère de ne pas la laisser se fatiguer ainsi) et elle s’était rabattue sur les quatre romans champêtres de George Sand. «Ma fille, disait-elle à maman, je ne pourrais me décider à donner à cet enfant quelque chose de\r\n mal écrit.»\r\n </p>\r\n <p>\r\n En réalité, elle ne se résignait jamais à rien acheter dont on ne pût tirer un profit intellectuel, et surtout celui que nous procurent les belles choses en nous apprenant à chercher notre plaisir ailleurs que dans les satisfactions\r\n du bien-être et de la vanité. Même quand elle avait à faire à quelqu’un un cadeau dit utile, quand elle avait à donner un fauteuil, des couverts, une canne, elle les cherchait «anciens», comme si leur longue désuétude ayant effacé\r\n leur caractère d’utilité, ils paraissaient plutôt disposés pour nous raconter la vie des hommes d’autrefois que pour servir aux besoins de la nôtre. Elle eût aimé que j’eusse dans ma chambre des photographies des monuments ou des\r\n paysages les plus beaux. Mais au moment d’en faire l’emplette, et bien que la chose représentée eût une valeur esthétique, elle trouvait que la vulgarité, l’utilité reprenaient trop vite leur place dans le mode mécanique de\r\n représentation, la photographie. Elle essayait de ruser et sinon d’éliminer entièrement la banalité commerciale, du moins de la réduire, d’y substituer pour la plus grande partie de l’art encore, d’y introduire comme plusieurs\r\n «épaisseurs» d’art: au lieu de photographies de la Cathédrale de Chartres, des Grandes Eaux de Saint-Cloud, du Vésuve, elle se renseignait auprès de Swann si quelque grand peintre ne les avait pas représentés, et préférait me donner\r\n des photographies de la Cathédrale de Chartres par Corot, des Grandes Eaux de Saint-Cloud par Hubert Robert, du Vésuve par Turner, ce qui faisait un degré d’art de plus. Mais si le photographe avait été écarté de la représentation du\r\n chef-d’œuvre ou de la nature et remplacé par un grand artiste, il reprenait ses droits pour reproduire cette interprétation même. Arrivée à l’échéance de la vulgarité, ma grand’mère tâchait de la reculer encore. Elle demandait à Swann\r\n si l’œuvre n’avait pas été gravée, préférant, quand c’était possible, des gravures anciennes et ayant encore un intérêt au delà d’elles-mêmes, par exemple celles qui représentent un chef-d’œuvre dans un état où nous ne pouvons plus le\r\n voir aujourd’hui (comme la gravure de la Cène de Léonard avant sa dégradation, par Morgan). Il faut dire que les résultats de cette manière de comprendre l’art de faire un cadeau ne furent pas toujours très brillants. L’idée que je\r\n pris de Venise d’après un dessin du Titien qui est censé avoir pour fond la lagune, était certainement beaucoup moins exacte que celle que m’eussent donnée de simples photographies. On ne pouvait plus faire le compte à la maison,\r\n quand ma grand’tante voulait dresser un réquisitoire contre ma grand’mère, des fauteuils offerts par elle à de jeunes fiancés ou à de vieux époux, qui, à la première tentative qu’on avait faite pour s’en servir, s’étaient\r\n immédiatement effondrés sous le poids d’un des destinataires. Mais ma grand’mère aurait cru mesquin de trop s’occuper de la solidité d’une boiserie où se distinguaient encore une fleurette, un sourire, quelquefois une belle\r\n imagination du passé. Même ce qui dans ces meubles répondait à un besoin, comme c’était d’une façon à laquelle nous ne sommes plus habitués, la charmait comme les vieilles manières de dire où nous voyons une métaphore, effacée, dans\r\n notre moderne langage, par l’usure de l’habitude. Or, justement, les romans champêtres de George Sand qu’elle me donnait pour ma fête, étaient pleins ainsi qu’un mobilier ancien, d’expressions tombées en désuétude et redevenues\r\n imagées, comme on n’en trouve plus qu’à la campagne. Et ma grand’mère les avait achetés de préférence à d’autres comme elle eût loué plus volontiers une propriété où il y aurait eu un pigeonnier gothique ou quelqu’une de ces vieilles\r\n choses qui exercent sur l’esprit une heureuse influence en lui donnant la nostalgie d’impossibles voyages dans le temps.\r\n </p>\r\n <p>\r\n Maman s’assit à côté de mon lit; elle avait pris François le Champi à qui sa couverture rougeâtre et son titre incompréhensible, donnaient pour moi une personnalité distincte et un attrait mystérieux. Je n’avais jamais lu encore de\r\n vrais romans. J’avais entendu dire que George Sand était le type du romancier. Cela me disposait déjà à imaginer dans François le Champi quelque chose d’indéfinissable et de délicieux. Les procédés de narration destinés à exciter la\r\n curiosité ou l’attendrissement, certaines façons de dire qui éveillent l’inquiétude et la mélancolie, et qu’un lecteur un peu instruit reconnaît pour communs à beaucoup de romans, me paraissaient simples—à moi qui considérais un livre\r\n nouveau non comme une chose ayant beaucoup de semblables, mais comme une personne unique, n’ayant de raison d’exister qu’en soi,—une émanation troublante de l’essence particulière à François le Champi. Sous ces événements si\r\n journaliers, ces choses si communes, ces mots si courants, je sentais comme une intonation, une accentuation étrange. L’action s’engagea; elle me parut d’autant plus obscure que dans ce temps-là, quand je lisais, je rêvassais souvent,\r\n pendant des pages entières, à tout autre chose. Et aux lacunes que cette distraction laissait dans le récit, s’ajoutait, quand c’était maman qui me lisait à haute voix, qu’elle passait toutes les scènes d’amour. Aussi tous les\r\n changements bizarres qui se produisent dans l’attitude respective de la meunière et de l’enfant et qui ne trouvent leur explication que dans les progrès d’un amour naissant me paraissaient empreints d’un profond mystère dont je me\r\n figurais volontiers que la source devait être dans ce nom inconnu et si doux de «Champi» qui mettait sur l’enfant, qui le portait sans que je susse pourquoi, sa couleur vive, empourprée et charmante. Si ma mère était une lectrice\r\n infidèle c’était aussi, pour les ouvrages où elle trouvait l’accent d’un sentiment vrai, une lectrice admirable par le respect et la simplicité de l’interprétation, par la beauté et la douceur du son. Même dans la vie, quand c’étaient\r\n des êtres et non des œuvres d’art qui excitaient ainsi son attendrissement ou son admiration, c’était touchant de voir avec quelle déférence elle écartait de sa voix, de son geste, de ses propos, tel éclat de gaîté qui eût pu faire\r\n mal à cette mère qui avait autrefois perdu un enfant, tel rappel de fête, d’anniversaire, qui aurait pu faire penser ce vieillard à son grand âge, tel propos de ménage qui aurait paru fastidieux à ce jeune savant. De même, quand elle\r\n lisait la prose de George Sand, qui respire toujours cette bonté, cette distinction morale que maman avait appris de ma grand’mère à tenir pour supérieures à tout dans la vie, et que je ne devais lui apprendre que bien plus tard à ne\r\n pas tenir également pour supérieures à tout dans les livres, attentive à bannir de sa voix toute petitesse, toute affectation qui eût pu empêcher le flot puissant d’y être reçu, elle fournissait toute la tendresse naturelle, toute\r\n l’ample douceur qu’elles réclamaient à ces phrases qui semblaient écrites pour sa voix et qui pour ainsi dire tenaient tout entières dans le registre de sa sensibilité. Elle retrouvait pour les attaquer dans le ton qu’il faut,\r\n l’accent cordial qui leur préexiste et les dicta, mais que les mots n’indiquent pas; grâce à lui elle amortissait au passage toute crudité dans les temps des verbes, donnait à l’imparfait et au passé défini la douceur qu’il y a dans\r\n la bonté, la mélancolie qu’il y a dans la tendresse, dirigeait la phrase qui finissait vers celle qui allait commencer, tantôt pressant, tantôt ralentissant la marche des syllabes pour les faire entrer, quoique leurs quantités fussent\r\n différentes, dans un rythme uniforme, elle insufflait à cette prose si commune une sorte de vie sentimentale et continue.\r\n </p>\r\n <p>\r\n Mes remords étaient calmés, je me laissais aller à la douceur de cette nuit où j’avais ma mère auprès de moi. Je savais qu’une telle nuit ne pourrait se renouveler; que le plus grand désir que j’eusse au monde, garder ma mère dans ma\r\n chambre pendant ces tristes heures nocturnes, était trop en opposition avec les nécessités de la vie et le vœu de tous, pour que l’accomplissement qu’on lui avait accordé ce soir pût être autre chose que factice et exceptionnel.\r\n Demain mes angoisses reprendraient et maman ne resterait pas là. Mais quand mes angoisses étaient calmées, je ne les comprenais plus; puis demain soir était encore lointain; je me disais que j’aurais le temps d’aviser, bien que ce\r\n temps-là ne pût m’apporter aucun pouvoir de plus, qu’il s’agissait de choses qui ne dépendaient pas de ma volonté et que seul me faisait paraître plus évitables l’intervalle qui les séparait encore de moi.\r\n </p>\r\n <p>...</p>\r\n <p>\r\n C’est ainsi que, pendant longtemps, quand, réveillé la nuit, je me ressouvenais de Combray, je n’en revis jamais que cette sorte de pan lumineux, découpé au milieu d’indistinctes ténèbres, pareil à ceux que l’embrasement d’un feu de\r\n Bengale ou quelque projection électrique éclairent et sectionnent dans un édifice dont les autres parties restent plongées dans la nuit: à la base assez large, le petit salon, la salle à manger, l’amorce de l’allée obscure par où\r\n arriverait M. Swann, l’auteur inconscient de mes tristesses, le vestibule où je m’acheminais vers la première marche de l’escalier, si cruel à monter, qui constituait à lui seul le tronc fort étroit de cette pyramide irrégulière; et,\r\n au faîte, ma chambre à coucher avec le petit couloir à porte vitrée pour l’entrée de maman; en un mot, toujours vu à la même heure, isolé de tout ce qu’il pouvait y avoir autour, se détachant seul sur l’obscurité, le décor strictement\r\n nécessaire (comme celui qu’on voit indiqué en tête des vieilles pièces pour les représentations en province), au drame de mon déshabillage; comme si Combray n’avait consisté qu’en deux étages reliés par un mince escalier, et comme\r\n s’il n’y avait jamais été que sept heures du soir. A vrai dire, j’aurais pu répondre à qui m’eût interrogé que Combray comprenait encore autre chose et existait à d’autres heures. Mais comme ce que je m’en serais rappelé m’eût été\r\n fourni seulement par la mémoire volontaire, la mémoire de l’intelligence, et comme les renseignements qu’elle donne sur le passé ne conservent rien de lui, je n’aurais jamais eu envie de songer à ce reste de Combray. Tout cela était\r\n en réalité mort pour moi.\r\n </p>\r\n <p>Mort à jamais? C’était possible.</p>\r\n <p>Il y a beaucoup de hasard en tout ceci, et un second hasard, celui de notre mort, souvent ne nous permet pas d’attendre longtemps les faveurs du premier.</p>\r\n <p>\r\n Je trouve très raisonnable la croyance celtique que les âmes de ceux que nous avons perdus sont captives dans quelque être inférieur, dans une bête, un végétal, une chose inanimée, perdues en effet pour nous jusqu’au jour, qui pour\r\n beaucoup ne vient jamais, où nous nous trouvons passer près de l’arbre, entrer en possession de l’objet qui est leur prison. Alors elles tressaillent, nous appellent, et sitôt que nous les avons reconnues, l’enchantement est brisé.\r\n Délivrées par nous, elles ont vaincu la mort et reviennent vivre avec nous.\r\n </p>\r\n <p>\r\n Il en est ainsi de notre passé. C’est peine perdue que nous cherchions à l’évoquer, tous les efforts de notre intelligence sont inutiles. Il est caché hors de son domaine et de sa portée, en quelque objet matériel (en la sensation que\r\n nous donnerait cet objet matériel), que nous ne soupçonnons pas. Cet objet, il dépend du hasard que nous le rencontrions avant de mourir, ou que nous ne le rencontrions pas.\r\n </p>\r\n <p>\r\n Il y avait déjà bien des années que, de Combray, tout ce qui n’était pas le théâtre et le drame de mon coucher, n’existait plus pour moi, quand un jour d’hiver, comme je rentrais à la maison, ma mère, voyant que j’avais froid, me\r\n proposa de me faire prendre, contre mon habitude, un peu de thé. Je refusai d’abord et, je ne sais pourquoi, me ravisai. Elle envoya chercher un de ces gâteaux courts et dodus appelés Petites Madeleines qui semblent avoir été moulés\r\n dans la valve rainurée d’une coquille de Saint-Jacques. Et bientôt, machinalement, accablé par la morne journée et la perspective d’un triste lendemain, je portai à mes lèvres une cuillerée du thé où j’avais laissé s’amollir un\r\n morceau de madeleine. Mais à l’instant même où la gorgée mêlée des miettes du gâteau toucha mon palais, je tressaillis, attentif à ce qui se passait d’extraordinaire en moi. Un plaisir délicieux m’avait envahi, isolé, sans la notion\r\n de sa cause. Il m’avait aussitôt rendu les vicissitudes de la vie indifférentes, ses désastres inoffensifs, sa brièveté illusoire, de la même façon qu’opère l’amour, en me remplissant d’une essence précieuse: ou plutôt cette essence\r\n n’était pas en moi, elle était moi. J’avais cessé de me sentir médiocre, contingent, mortel. D’où avait pu me venir cette puissante joie? Je sentais qu’elle était liée au goût du thé et du gâteau, mais qu’elle le dépassait infiniment,\r\n ne devait pas être de même nature. D’où venait-elle? Que signifiait-elle? Où l’appréhender? Je bois une seconde gorgée où je ne trouve rien de plus que dans la première, une troisième qui m’apporte un peu moins que la seconde. Il est\r\n temps que je m’arrête, la vertu du breuvage semble diminuer. Il est clair que la vérité que je cherche n’est pas en lui, mais en moi. Il l’y a éveillée, mais ne la connaît pas, et ne peut que répéter indéfiniment, avec de moins en\r\n moins de force, ce même témoignage que je ne sais pas interpréter et que je veux au moins pouvoir lui redemander et retrouver intact, à ma disposition, tout à l’heure, pour un éclaircissement décisif. Je pose la tasse et me tourne\r\n vers mon esprit. C’est à lui de trouver la vérité. Mais comment? Grave incertitude, toutes les fois que l’esprit se sent dépassé par lui-même; quand lui, le chercheur, est tout ensemble le pays obscur où il doit chercher et où tout\r\n son bagage ne lui sera de rien. Chercher? pas seulement: créer. Il est en face de quelque chose qui n’est pas encore et que seul il peut réaliser, puis faire entrer dans sa lumière.\r\n </p>\r\n <p>\r\n Et je recommence à me demander quel pouvait être cet état inconnu, qui n’apportait aucune preuve logique, mais l’évidence de sa félicité, de sa réalité devant laquelle les autres s’évanouissaient. Je veux essayer de le faire\r\n réapparaître. Je rétrograde par la pensée au moment où je pris la première cuillerée de thé. Je retrouve le même état, sans une clarté nouvelle. Je demande à mon esprit un effort de plus, de ramener encore une fois la sensation qui\r\n s’enfuit. Et pour que rien ne brise l’élan dont il va tâcher de la ressaisir, j’écarte tout obstacle, toute idée étrangère, j’abrite mes oreilles et mon attention contre les bruits de la chambre voisine. Mais sentant mon esprit qui se\r\n fatigue sans réussir, je le force au contraire à prendre cette distraction que je lui refusais, à penser à autre chose, à se refaire avant une tentative suprême. Puis une deuxième fois, je fais le vide devant lui, je remets en face de\r\n lui la saveur encore récente de cette première gorgée et je sens tressaillir en moi quelque chose qui se déplace, voudrait s’élever, quelque chose qu’on aurait désancré, à une grande profondeur; je ne sais ce que c’est, mais cela\r\n monte lentement; j’éprouve la résistance et j’entends la rumeur des distances traversées.\r\n </p>\r\n <p>\r\n Certes, ce qui palpite ainsi au fond de moi, ce doit être l’image, le souvenir visuel, qui, lié à cette saveur, tente de la suivre jusqu’à moi. Mais il se débat trop loin, trop confusément; à peine si je perçois le reflet neutre où se\r\n confond l’insaisissable tourbillon des couleurs remuées; mais je ne puis distinguer la forme, lui demander comme au seul interprète possible, de me traduire le témoignage de sa contemporaine, de son inséparable compagne, la saveur,\r\n lui demander de m’apprendre de quelle circonstance particulière, de quelle époque du passé il s’agit.\r\n </p>\r\n <p>\r\n Arrivera-t-il jusqu’à la surface de ma claire conscience, ce souvenir, l’instant ancien que l’attraction d’un instant identique est venue de si loin solliciter, émouvoir, soulever tout au fond de moi? Je ne sais. Maintenant je ne sens\r\n plus rien, il est arrêté, redescendu peut-être; qui sait s’il remontera jamais de sa nuit? Dix fois il me faut recommencer, me pencher vers lui. Et chaque fois la lâcheté qui nous détourne de toute tâche difficile, de toute œuvre\r\n important, m’a conseillé de laisser cela, de boire mon thé en pensant simplement à mes ennuis d’aujourd’hui, à mes désirs de demain qui se laissent remâcher sans peine.\r\n </p>\r\n <p>\r\n Et tout d’un coup le souvenir m’est apparu. Ce goût c’était celui du petit morceau de madeleine que le dimanche matin à Combray (parce que ce jour-là je ne sortais pas avant l’heure de la messe), quand j’allais lui dire bonjour dans\r\n sa chambre, ma tante Léonie m’offrait après l’avoir trempé dans son infusion de thé ou de tilleul. La vue de la petite madeleine ne m’avait rien rappelé avant que je n’y eusse goûté; peut-être parce que, en ayant souvent aperçu\r\n depuis, sans en manger, sur les tablettes des pâtissiers, leur image avait quitté ces jours de Combray pour se lier à d’autres plus récents; peut-être parce que de ces souvenirs abandonnés si longtemps hors de la mémoire, rien ne\r\n survivait, tout s’était désagrégé; les formes,—et celle aussi du petit coquillage de pâtisserie, si grassement sensuel, sous son plissage sévère et dévot—s’étaient abolies, ou, ensommeillées, avaient perdu la force d’expansion qui\r\n leur eût permis de rejoindre la conscience. Mais, quand d’un passé ancien rien ne subsiste, après la mort des êtres, après la destruction des choses, seules, plus frêles mais plus vivaces, plus immatérielles, plus persistantes, plus\r\n fidèles, l’odeur et la saveur restent encore longtemps, comme des âmes, à se rappeler, à attendre, à espérer, sur la ruine de tout le reste, à porter sans fléchir, sur leur gouttelette presque impalpable, l’édifice immense du\r\n souvenir.\r\n </p>\r\n <p>\r\n Et dès que j’eus reconnu le goût du morceau de madeleine trempé dans le tilleul que me donnait ma tante (quoique je ne susse pas encore et dusse remettre à bien plus tard de découvrir pourquoi ce souvenir me rendait si heureux),\r\n aussitôt la vieille maison grise sur la rue, où était sa chambre, vint comme un décor de théâtre s’appliquer au petit pavillon, donnant sur le jardin, qu’on avait construit pour mes parents sur ses derrières (ce pan tronqué que seul\r\n j’avais revu jusque-là); et avec la maison, la ville, la Place où on m’envoyait avant déjeuner, les rues où j’allais faire des courses depuis le matin jusqu’au soir et par tous les temps, les chemins qu’on prenait si le temps était\r\n beau. Et comme dans ce jeu où les Japonais s’amusent à tremper dans un bol de porcelaine rempli d’eau, de petits morceaux de papier jusque-là indistincts qui, à peine y sont-ils plongés s’étirent, se contournent, se colorent, se\r\n différencient, deviennent des fleurs, des maisons, des personnages consistants et reconnaissables, de même maintenant toutes les fleurs de notre jardin et celles du parc de M. Swann, et les nymphéas de la Vivonne, et les bonnes gens\r\n du village et leurs petits logis et l’église et tout Combray et ses environs, tout cela que prend forme et solidité, est sorti, ville et jardins, de ma tasse de thé.\r\n </p>\r\n <h3>II.</h3>\r\n <p>\r\n Combray de loin, à dix lieues à la ronde, vu du chemin de fer quand nous y arrivions la dernière semaine avant Pâques, ce n’était qu’une église résumant la ville, la représentant, parlant d’elle et pour elle aux lointains, et, quand\r\n on approchait, tenant serrés autour de sa haute mante sombre, en plein champ, contre le vent, comme une pastoure ses brebis, les dos laineux et gris des maisons rassemblées qu’un reste de remparts du moyen âge cernait çà et là d’un\r\n trait aussi parfaitement circulaire qu’une petite ville dans un tableau de primitif. A l’habiter, Combray était un peu triste, comme ses rues dont les maisons construites en pierres noirâtres du pays, précédées de degrés extérieurs,\r\n coiffées de pignons qui rabattaient l’ombre devant elles, étaient assez obscures pour qu’il fallût dès que le jour commençait à tomber relever les rideaux dans les «salles»; des rues aux graves noms de saints (desquels plusieurs\r\n seigneurs de Combray): rue Saint-Hilaire, rue Saint-Jacques où était la maison de ma tante, rue Sainte-Hildegarde, où donnait la grille, et rue du Saint-Esprit sur laquelle s’ouvrait la petite porte latérale de son jardin; et ces rues\r\n de Combray existent dans une partie de ma mémoire si reculée, peinte de couleurs si différentes de celles qui maintenant revêtent pour moi le monde, qu’en vérité elles me paraissent toutes, et l’église qui les dominait sur la Place,\r\n plus irréelles encore que les projections de la lanterne magique; et qu’à certains moments, il me semble que pouvoir encore traverser la rue Saint-Hilaire, pouvoir louer une chambre rue de l’Oiseau—à la vieille hôtellerie de l’Oiseau\r\n flesché, des soupiraux de laquelle montait une odeur de cuisine qui s’élève encore par moments en moi aussi intermittente et aussi chaude,—serait une entrée en contact avec l’Au-delà plus merveilleusement surnaturelle que de faire la\r\n connaissance de Golo et de causer avec Geneviève de Brabant.\r\n </p>\r\n <p>\r\n La cousine de mon grand-père,—ma grand’tante,—chez qui nous habitions, était la mère de cette tante Léonie qui, depuis la mort de son mari, mon oncle Octave, n’avait plus voulu quitter, d’abord Combray, puis à Combray sa maison, puis\r\n sa chambre, puis son lit et ne «descendait» plus, toujours couchée dans un état incertain de chagrin, de débilité physique, de maladie, d’idée fixe et de dévotion. Son appartement particulier donnait sur la rue Saint-Jacques qui\r\n aboutissait beaucoup plus loin au Grand-Pré (par opposition au Petit-Pré, verdoyant au milieu de la ville, entre trois rues), et qui, unie, grisâtre, avec les trois hautes marches de grès presque devant chaque porte, semblait comme un\r\n défilé pratiqué par un tailleur d’images gothiques à même la pierre où il eût sculpté une crèche ou un calvaire. Ma tante n’habitait plus effectivement que deux chambres contiguës, restant l’après-midi dans l’une pendant qu’on aérait\r\n l’autre. C’étaient de ces chambres de province qui,—de même qu’en certains pays des parties entières de l’air ou de la mer sont illuminées ou parfumées par des myriades de protozoaires que nous ne voyons pas,—nous enchantent des mille\r\n odeurs qu’y dégagent les vertus, la sagesse, les habitudes, toute une vie secrète, invisible, surabondante et morale que l’atmosphère y tient en suspens; odeurs naturelles encore, certes, et couleur du temps comme celles de la\r\n campagne voisine, mais déjà casanières, humaines et renfermées, gelée exquise industrieuse et limpide de tous les fruits de l’année qui ont quitté le verger pour l’armoire; saisonnières, mais mobilières et domestiques, corrigeant le\r\n piquant de la gelée blanche par la douceur du pain chaud, oisives et ponctuelles comme une horloge de village, flâneuses et rangées, insoucieuses et prévoyantes, lingères, matinales, dévotes, heureuses d’une paix qui n’apporte qu’un\r\n surcroît d’anxiété et d’un prosaïsme qui sert de grand réservoir de poésie à celui qui la traverse sans y avoir vécu. L’air y était saturé de la fine fleur d’un silence si nourricier, si succulent que je ne m’y avançais qu’avec une\r\n sorte de gourmandise, surtout par ces premiers matins encore froids de la semaine de Pâques où je le goûtais mieux parce que je venais seulement d’arriver à Combray: avant que j’entrasse souhaiter le bonjour à ma tante on me faisait\r\n attendre un instant, dans la première pièce où le soleil, d’hiver encore, était venu se mettre au chaud devant le feu, déjà allumé entre les deux briques et qui badigeonnait toute la chambre d’une odeur de suie, en faisait comme un de\r\n ces grands «devants de four» de campagne, ou de ces manteaux de cheminée de châteaux, sous lesquels on souhaite que se déclarent dehors la pluie, la neige, même quelque catastrophe diluvienne pour ajouter au confort de la réclusion la\r\n poésie de l’hivernage; je faisais quelques pas du prie-Dieu aux fauteuils en velours frappé, toujours revêtus d’un appui-tête au crochet; et le feu cuisant comme une pâte les appétissantes odeurs dont l’air de la chambre était tout\r\n grumeleux et qu’avait déjà fait travailler et «lever» la fraîcheur humide et ensoleillée du matin, il les feuilletait, les dorait, les godait, les boursouflait, en faisant un invisible et palpable gâteau provincial, un immense\r\n «chausson» où, à peine goûtés les arômes plus croustillants, plus fins, plus réputés, mais plus secs aussi du placard, de la commode, du papier à ramages, je revenais toujours avec une convoitise inavouée m’engluer dans l’odeur\r\n médiane, poisseuse, fade, indigeste et fruitée de couvre-lit à fleurs.\r\n </p>\r\n <p>\r\n Dans la chambre voisine, j’entendais ma tante qui causait toute seule à mi-voix. Elle ne parlait jamais qu’assez bas parce qu’elle croyait avoir dans la tête quelque chose de cassé et de flottant qu’elle eût déplacé en parlant trop\r\n fort, mais elle ne restait jamais longtemps, même seule, sans dire quelque chose, parce qu’elle croyait que c’était salutaire pour sa gorge et qu’en empêchant le sang de s’y arrêter, cela rendrait moins fréquents les étouffements et\r\n les angoisses dont elle souffrait; puis, dans l’inertie absolu où elle vivait, elle prêtait à ses moindres sensations une importance extraordinaire; elle les douait d’une motilité qui lui rendait difficile de les garder pour elle, et\r\n à défaut de confident à qui les communiquer, elle se les annonçait à elle-même, en un perpétuel monologue qui était sa seule forme d’activité. Malheureusement, ayant pris l’habitude de penser tout haut, elle ne faisait pas toujours\r\n attention à ce qu’il n’y eût personne dans la chambre voisine, et je l’entendais souvent se dire à elle-même: «Il faut que je me rappelle bien que je n’ai pas dormi» (car ne jamais dormir était sa grande prétention dont notre langage\r\n à tous gardait le respect et la trace: le matin Françoise ne venait pas «l’éveiller», mais «entrait» chez elle; quand ma tante voulait faire un somme dans la journée, on disait qu’elle voulait «réfléchir» ou «reposer»; et quand il lui\r\n arrivait de s’oublier en causant jusqu’à dire: «Ce qui m’a réveillée» ou «j’ai rêvé que», elle rougissait et se reprenait au plus vite).\r\n </p>\r\n <p>\r\n Au bout d’un moment, j’entrais l’embrasser; Françoise faisait infuser son thé; ou, si ma tante se sentait agitée, elle demandait à la place sa tisane et c’était moi qui étais chargé de faire tomber du sac de pharmacie dans une\r\n assiette la quantité de tilleul qu’il fallait mettre ensuite dans l’eau bouillante. Le dessèchement des tiges les avait incurvées en un capricieux treillage dans les entrelacs duquel s’ouvraient les fleurs pâles, comme si un peintre\r\n les eût arrangées, les eût fait poser de la façon la plus ornementale. Les feuilles, ayant perdu ou changé leur aspect, avaient l’air des choses les plus disparates, d’une aile transparente de mouche, de l’envers blanc d’une\r\n étiquette, d’un pétale de rose, mais qui eussent été empilées, concassées ou tressées comme dans la confection d’un nid. Mille petits détails inutiles,—charmante prodigalité du pharmacien,—qu’on eût supprimés dans une préparation\r\n factice, me donnaient, comme un livre où on s’émerveille de rencontrer le nom d’une personne de connaissance, le plaisir de comprendre que c’était bien des tiges de vrais tilleuls, comme ceux que je voyais avenue de la Gare,\r\n modifiées, justement parce que c’étaient non des doubles, mais elles-même et qu’elles avaient vieilli. Et chaque caractère nouveau n’y étant que la métamorphose d’un caractère ancien, dans de petites boules grises je reconnaissais les\r\n boutons verts qui ne sont pas venus à terme; mais surtout l’éclat rose, lunaire et doux qui faisait se détacher les fleurs dans la forêt fragile des tiges où elles étaient suspendues comme de petites roses d’or,—signe, comme la lueur\r\n qui révèle encore sur une muraille la place d’une fresque effacée, de la différence entre les parties de l’arbre qui avaient été «en couleur» et celles qui ne l’avaient pas été—me montrait que ces pétales étaient bien ceux qui avant\r\n de fleurir le sac de pharmacie avaient embaumé les soirs de printemps. Cette flamme rose de cierge, c’était leur couleur encore, mais à demi éteinte et assoupie dans cette vie diminuée qu’était la leur maintenant et qui est comme le\r\n crépuscule des fleurs. Bientôt ma tante pouvait tremper dans l’infusion bouillante dont elle savourait le goût de feuille morte ou de fleur fanée une petite madeleine dont elle me tendait un morceau quand il était suffisamment amolli.\r\n </p>\r\n <p>\r\n D’un côté de son lit était une grande commode jaune en bois de citronnier et une table qui tenait à la fois de l’officine et du maître-autel, où, au-dessus d’une statuette de la Vierge et d’une bouteille de Vichy-Célestins, on\r\n trouvait des livres de messe et des ordonnances de médicaments, tous ce qu’il fallait pour suivre de son lit les offices et son régime, pour ne manquer l’heure ni de la pepsine, ni des vêpres. De l’autre côté, son lit longeait la\r\n fenêtre, elle avait la rue sous les yeux et y lisait du matin au soir, pour se désennuyer, à la façon des princes persans, la chronique quotidienne mais immémoriale de Combray, qu’elle commentait ensuite avec Françoise.\r\n </p>\r\n <p>\r\n Je n’étais pas avec ma tante depuis cinq minutes, qu’elle me renvoyait par peur que je la fatigue. Elle tendait à mes lèvres son triste front pâle et fade sur lequel, à cette heure matinale, elle n’avait pas encore arrangé ses faux\r\n cheveux, et où les vertèbres transparaissaient comme les pointes d’une couronne d’épines ou les grains d’un rosaire, et elle me disait: «Allons, mon pauvre enfant, va-t’en, va te préparer pour la messe; et si en bas tu rencontres\r\n Françoise, dis-lui de ne pas s’amuser trop longtemps avec vous, qu’elle monte bientôt voir si je n’ai besoin de rien.»\r\n </p>\r\n <p>\r\n Françoise, en effet, qui était depuis des années a son service et ne se doutait pas alors qu’elle entrerait un jour tout à fait au nôtre délaissait un peu ma tante pendant les mois où nous étions là. Il y avait eu dans mon enfance,\r\n avant que nous allions à Combray, quand ma tante Léonie passait encore l’hiver à Paris chez sa mère, un temps où je connaissais si peu Françoise que, le 1<sup>er</sup> janvier, avant d’entrer chez ma grand’tante, ma mère me mettait\r\n dans la main une pièce de cinq francs et me disait: «Surtout ne te trompe pas de personne. Attends pour donner que tu m’entendes dire: «Bonjour Françoise»; en même temps je te toucherai légèrement le bras. A peine arrivions-nous dans\r\n l’obscure antichambre de ma tante que nous apercevions dans l’ombre, sous les tuyaux d’un bonnet éblouissant, raide et fragile comme s’il avait été de sucre filé, les remous concentriques d’un sourire de reconnaissance anticipé.\r\n C’était Françoise, immobile et debout dans l’encadrement de la petite porte du corridor comme une statue de sainte dans sa niche. Quand on était un peu habitué à ces ténèbres de chapelle, on distinguait sur son visage l’amour\r\n désintéressé de l’humanité, le respect attendri pour les hautes classes qu’exaltait dans les meilleures régions de son cœur l’espoir des étrennes. Maman me pinçait le bras avec violence et disait d’une voix forte: «Bonjour Françoise.»\r\n A ce signal mes doigts s’ouvraient et je lâchais la pièce qui trouvait pour la recevoir une main confuse, mais tendue. Mais depuis que nous allions à Combray je ne connaissais personne mieux que Françoise; nous étions ses préférés,\r\n elle avait pour nous, au moins pendant les premières années, avec autant de considération que pour ma tante, un goût plus vif, parce que nous ajoutions, au prestige de faire partie de la famille (elle avait pour les liens invisibles\r\n que noue entre les membres d’une famille la circulation d’un même sang, autant de respect qu’un tragique grec), le charme de n’être pas ses maîtres habituels. Aussi, avec quelle joie elle nous recevait, nous plaignant de n’avoir pas\r\n encore plus beau temps, le jour de notre arrivée, la veille de Pâques, où souvent il faisait un vent glacial, quand maman lui demandait des nouvelles de sa fille et de ses neveux, si son petit-fils était gentil, ce qu’on comptait\r\n faire de lui, s’il ressemblerait à sa grand’mère.\r\n </p>\r\n <p>Et quand il n’y avait plus de monde là, maman qui savait que Françoise pleurait encore ses parents morts depuis des années, lui parlait d’eux avec douceur, lui demandait mille détails sur ce qu’avait été leur vie.</p>\r\n <p>\r\n Elle avait deviné que Françoise n’aimait pas son gendre et qu’il lui gâtait le plaisir qu’elle avait à être avec sa fille, avec qui elle ne causait pas aussi librement quand il était là. Aussi, quand Françoise allait les voir, à\r\n quelques lieues de Combray, maman lui disait en souriant: «N’est-ce pas Françoise, si Julien a été obligé de s’absenter et si vous avez Marguerite à vous toute seule pour toute la journée, vous serez désolée, mais vous vous ferez une\r\n raison?» Et Françoise disait en riant: «Madame sait tout; madame est pire que les rayons X (elle disait x avec une difficulté affectée et un sourire pour se railler elle-même, ignorante, d’employer ce terme savant), qu’on a fait venir\r\n pour M<sup>me</sup> Octave et qui voient ce que vous avez dans le cœur», et disparaissait, confuse qu’on s’occupât d’elle, peut-être pour qu’on ne la vît pas pleurer; maman était la première personne qui lui donnât cette douce émotion\r\n de sentir que sa vie, ses bonheurs, ses chagrins de paysanne pouvaient présenter de l’intérêt, être un motif de joie ou de tristesse pour une autre qu’elle-même. Ma tante se résignait à se priver un peu d’elle pendant notre séjour,\r\n sachant combien ma mère appréciait le service de cette bonne si intelligente et active, qui était aussi belle dès cinq heures du matin dans sa cuisine, sous son bonnet dont le tuyautage éclatant et fixe avait l’air d’être en biscuit,\r\n que pour aller à la grand’messe; qui faisait tout bien, travaillant comme un cheval, qu’elle fût bien portante ou non, mais sans bruit, sans avoir l’air de rien faire, la seule des bonnes de ma tante qui, quand maman demandait de\r\n l’eau chaude ou du café noir, les apportait vraiment bouillants; elle était un de ces serviteurs qui, dans une maison, sont à la fois ceux qui déplaisent le plus au premier abord à un étranger, peut-être parce qu’ils ne prennent pas\r\n la peine de faire sa conquête et n’ont pas pour lui de prévenance, sachant très bien qu’ils n’ont aucun besoin de lui, qu’on cesserait de le recevoir plutôt que de les renvoyer; et qui sont en revanche ceux à qui tiennent le plus les\r\n maîtres qui ont éprouvé leur capacités réelles, et ne se soucient pas de cet agrément superficiel, de ce bavardage servile qui fait favorablement impression à un visiteur, mais qui recouvre souvent une inéducable nullité.\r\n </p>\r\n <p>\r\n Quand Françoise, après avoir veillé à ce que mes parents eussent tout ce qu’il leur fallait, remontait une première fois chez ma tante pour lui donner sa pepsine et lui demander ce qu’elle prendrait pour déjeuner, il était bien rare\r\n qu’il ne fallût pas donner déjà son avis ou fournir des explications sur quelque événement d’importance:\r\n </p>\r\n <p>\r\n —«Françoise, imaginez-vous que M<sup>me</sup> Goupil est passée plus d’un quart d’heure en retard pour aller chercher sa sœur; pour peu qu’elle s’attarde sur son chemin cela ne me surprendrait point qu’elle arrive après l’élévation.»\r\n </p>\r\n <p>—«Hé! il n’y aurait rien d’étonnant», répondait Françoise.</p>\r\n <p>\r\n —«Françoise, vous seriez venue cinq minutes plus tôt, vous auriez vu passer M<sup>me</sup> Imbert qui tenait des asperges deux fois grosses comme celles de la mère Callot; tâchez donc de savoir par sa bonne où elle les a eues. Vous\r\n qui, cette année, nous mettez des asperges à toutes les sauces, vous auriez pu en prendre de pareilles pour nos voyageurs.»\r\n </p>\r\n <p>—«Il n’y aurait rien d’étonnant qu’elles viennent de chez M. le Curé», disait Françoise.</p>\r\n <p>\r\n —«Ah! je vous crois bien, ma pauvre Françoise, répondait ma tante en haussant les épaules, chez M. le Curé! Vous savez bien qu’il ne fait pousser que de petites méchantes asperges de rien. Je vous dis que celles-là étaient grosses\r\n comme le bras. Pas comme le vôtre, bien sûr, mais comme mon pauvre bras qui a encore tant maigri cette année.»\r\n </p>\r\n <p>—«Françoise, vous n’avez pas entendu ce carillon qui m’a cassé la tête?»</p>\r\n <p>—«Non, madame Octave.»</p>\r\n <p>\r\n —«Ah! ma pauvre fille, il faut que vous l’ayez solide votre tête, vous pouvez remercier le Bon Dieu. C’était la Maguelone qui était venue chercher le docteur Piperaud. Il est ressorti tout de suite avec elle et ils ont tourné par la\r\n rue de l’Oiseau. Il faut qu’il y ait quelque enfant de malade.»\r\n </p>\r\n <p>—«Eh! là, mon Dieu», soupirait Françoise, qui ne pouvait pas entendre parler d’un malheur arrivé à un inconnu, même dans une partie du monde éloignée, sans commencer à gémir.</p>\r\n <p>\r\n —«Françoise, mais pour qui donc a-t-on sonné la cloche des morts? Ah! mon Dieu, ce sera pour M<sup>me</sup> Rousseau. Voilà-t-il pas que j’avais oublié qu’elle a passé l’autre nuit. Ah! il est temps que le Bon Dieu me rappelle, je ne\r\n sais plus ce que j’ai fait de ma tête depuis la mort de mon pauvre Octave. Mais je vous fais perdre votre temps, ma fille.»\r\n </p>\r\n <p>—«Mais non, madame Octave, mon temps n’est pas si cher; celui qui l’a fait ne nous l’a pas vendu. Je vas seulement voir si mon feu ne s’éteint pas.»</p>\r\n <p>\r\n Ainsi Françoise et ma tante appréciaient-elles ensemble au cours de cette séance matinale, les premiers événements du jour. Mais quelquefois ces événements revêtaient un caractère si mystérieux et si grave que ma tante sentait qu’elle\r\n ne pourrait pas attendre le moment où Françoise monterait, et quatre coups de sonnette formidables retentissaient dans la maison.\r\n </p>\r\n <p>—«Mais, madame Octave, ce n’est pas encore l’heure de la pepsine, disait Françoise. Est-ce que vous vous êtes senti une faiblesse?»</p>\r\n <p>\r\n —«Mais non, Françoise, disait ma tante, c’est-à-dire si, vous savez bien que maintenant les moments où je n’ai pas de faiblesse sont bien rares; un jour je passerai comme M<sup>me</sup> Rousseau sans avoir eu le temps de me\r\n reconnaître; mais ce n’est pas pour cela que je sonne. Croyez-vous pas que je viens de voir comme je vous vois M<sup>me</sup> Goupil avec une fillette que je ne connais point. Allez donc chercher deux sous de sel chez Camus. C’est\r\n bien rare si Théodore ne peut pas vous dire qui c’est.»\r\n </p>\r\n <p>—«Mais ça sera la fille à M. Pupin», disait Françoise qui préférait s’en tenir à une explication immédiate, ayant été déjà deux fois depuis le matin chez Camus.</p>\r\n <p>—«La fille à M. Pupin! Oh! je vous crois bien, ma pauvre Françoise! Avec cela que je ne l’aurais pas reconnue?»</p>\r\n <p>—«Mais je ne veux pas dire la grande, madame Octave, je veux dire la gamine, celle qui est en pension à Jouy. Il me ressemble de l’avoir déjà vue ce matin.»</p>\r\n <p>\r\n —«Ah! à moins de ça, disait ma tante. Il faudrait qu’elle soit venue pour les fêtes. C’est cela! Il n’y a pas besoin de chercher, elle sera venue pour les fêtes. Mais alors nous pourrions bien voir tout à l’heure M<sup>me</sup>\r\n Sazerat venir sonner chez sa sœur pour le déjeuner. Ce sera ça! J’ai vu le petit de chez Galopin qui passait avec une tarte! Vous verrez que la tarte allait chez M<sup>me</sup> Goupil.»\r\n </p>\r\n <p>\r\n —«Dès l’instant que M<sup>me</sup> Goupil a de la visite, madame Octave, vous n’allez pas tarder à voir tout son monde rentrer pour le déjeuner, car il commence à ne plus être de bonne heure», disait Françoise qui, pressé de\r\n redescendre s’occuper du déjeuner, n’était pas fâchée de laisser à ma tante cette distraction en perspective.\r\n </p>\r\n <p>\r\n —«Oh! pas avant midi, répondait ma tante d’un ton résigné, tout en jetant sur la pendule un coup d’œil inquiet, mais furtif pour ne pas laisser voir qu’elle, qui avait renoncé à tout, trouvait pourtant, à apprendre que M<sup>me</sup>\r\n Goupil avait à déjeuner, un plaisir aussi vif, et qui se ferait malheureusement attendre encore un peu plus d’une heure. Et encore cela tombera pendant mon déjeuner!» ajouta-t-elle à mi-voix pour elle-même. Son déjeuner lui était une\r\n distraction suffisante pour qu’elle n’en souhaitât pas une autre en même temps. «Vous n’oublierez pas au moins de me donner mes œufs à la crème dans une assiette plate?» C’étaient les seules qui fussent ornées de sujets, et ma tante\r\n s’amusait à chaque repas à lire la légende de celle qu’on lui servait ce jour-là. Elle mettait ses lunettes, déchiffrait: Alibaba et quarante voleurs, Aladin ou la Lampe merveilleuse, et disait en souriant: Très bien, très bien.\r\n </p>\r\n <p>—«Je serais bien allée chez Camus...» disait Françoise en voyant que ma tante ne l’y enverrait plus.</p>\r\n <p>—«Mais non, ce n’est plus la peine, c’est sûrement M<sup>lle</sup> Pupin. Ma pauvre Françoise, je regrette de vous avoir fait monter pour rien.»</p>\r\n <p>\r\n Mais ma tante savait bien que ce n’était pas pour rien qu’elle avait sonné Françoise, car, à Combray, une personne «qu’on ne connaissait point» était un être aussi peu croyable qu’un dieu de la mythologie, et de fait on ne se\r\n souvenait pas que, chaque fois que s’était produite, dans la rue de Saint-Esprit ou sur la place, une de ces apparitions stupéfiantes, des recherches bien conduites n’eussent pas fini par réduire le personnage fabuleux aux proportions\r\n d’une «personne qu’on connaissait», soit personnellement, soit abstraitement, dans son état civil, en tant qu’ayant tel degré de parenté avec des gens de Combray. C’était le fils de M<sup>me</sup> Sauton qui rentrait du service, la\r\n nièce de l’abbé Perdreau qui sortait de couvent, le frère du curé, percepteur à Châteaudun qui venait de prendre sa retraite ou qui était venu passer les fêtes. On avait eu en les apercevant l’émotion de croire qu’il y avait à Combray\r\n des gens qu’on ne connaissait point simplement parce qu’on ne les avait pas reconnus ou identifiés tout de suite. Et pourtant, longtemps à l’avance, M<sup>me</sup> Sauton et le curé avaient prévenu qu’ils attendaient leurs\r\n «voyageurs». Quand le soir, je montais, en rentrant, raconter notre promenade à ma tante, si j’avais l’imprudence de lui dire que nous avions rencontré près du Pont-Vieux, un homme que mon grand-père ne connaissait pas: «Un homme que\r\n grand-père ne connaissait point, s’écriait elle. Ah! je te crois bien!» Néanmoins un peu émue de cette nouvelle, elle voulait en avoir le cœur net, mon grand-père était mandé. «Qui donc est-ce que vous avez rencontré près du\r\n Pont-Vieux, mon oncle? un homme que vous ne connaissiez point?»—«Mais si, répondait mon grand-père, c’était Prosper le frère du jardinier de M<sup>me</sup>\r\n Bouillebœuf.»—«Ah! bien», disait ma tante, tranquillisée et un peu rouge; haussant les épaules avec un sourire ironique, elle ajoutait: «Aussi il me disait que vous aviez rencontré un homme que vous ne connaissiez point!» Et on me\r\n recommandait d’être plus circonspect une autre fois et de ne plus agiter ainsi ma tante par des paroles irréfléchies. On connaissait tellement bien tout le monde, à Combray, bêtes et gens, que si ma tante avait vu par hasard passer un\r\n chien «qu’elle ne connaissait point», elle ne cessait d’y penser et de consacrer à ce fait incompréhensible ses talents d’induction et ses heures de liberté.\r\n </p>\r\n <p>—«Ce sera le chien de M<sup>me</sup> Sazerat», disait Françoise, sans grande conviction, mais dans un but d’apaisement et pour que ma tante ne se «fende pas la tête.»</p>\r\n <p>—«Comme si je ne connaissais pas le chien de M<sup>me</sup> Sazerat!» répondait ma tante dont l’esprit critique n’admettait pas si facilement un fait.</p>\r\n <p>—«Ah! ce sera le nouveau chien que M. Galopin a rapporté de Lisieux.»</p>\r\n <p>—«Ah! à moins de ça.»</p>\r\n <p>\r\n —«Il paraît que c’est une bête bien affable», ajoutait Françoise qui tenait le renseignement de Théodore, «spirituelle comme une personne, toujours de bonne humeur, toujours aimable, toujours quelque chose de gracieux. C’est rare\r\n qu’une bête qui n’a que cet âge-là soit déjà si galante. Madame Octave, il va falloir que je vous quitte, je n’ai pas le temps de m’amuser, voilà bientôt dix heures, mon fourneau n’est seulement pas éclairé, et j’ai encore à plumer\r\n mes asperges.»\r\n </p>\r\n <p>—«Comment, Françoise, encore des asperges! mais c’est une vraie maladie d’asperges que vous avez cette année, vous allez en fatiguer nos Parisiens!»</p>\r\n <p>—«Mais non, madame Octave, ils aiment bien ça. Ils rentreront de l’église avec de l’appétit et vous verrez qu’ils ne les mangeront pas avec le dos de la cuiller.»</p>\r\n <p>—«Mais à l’église, ils doivent y être déjà; vous ferez bien de ne pas perdre de temps. Allez surveiller votre déjeuner.»</p>\r\n <p>\r\n Pendant que ma tante devisait ainsi avec Françoise, j’accompagnais mes parents à la messe. Que je l’aimais, que je la revois bien, notre Église! Son vieux porche par lequel nous entrions, noir, grêlé comme une écumoire, était dévié et\r\n profondément creusé aux angles (de même que le bénitier où il nous conduisait) comme si le doux effleurement des mantes des paysannes entrant à l’église et de leurs doigts timides prenant de l’eau bénite, pouvait, répété pendant des\r\n siècles, acquérir une force destructive, infléchir la pierre et l’entailler de sillons comme en trace la roue des carrioles dans la borne contre laquelle elle bute tous les jours. Ses pierres tombales, sous lesquelles la noble\r\n poussière des abbés de Combray, enterrés là, faisait au chœur comme un pavage spirituel, n’étaient plus elles-mêmes de la matière inerte et dure, car le temps les avait rendues douces et fait couler comme du miel hors des limites de\r\n leur propre équarrissure qu’ici elles avaient dépassées d’un flot blond, entraînant à la dérive une majuscule gothique en fleurs, noyant les violettes blanches du marbre; et en deçà desquelles, ailleurs, elles s’étaient résorbées,\r\n contractant encore l’elliptique inscription latine, introduisant un caprice de plus dans la disposition de ces caractères abrégés, rapprochant deux lettres d’un mot dont les autres avaient été démesurément distendues. Ses vitraux ne\r\n chatoyaient jamais tant que les jours où le soleil se montrait peu, de sorte que fît-il gris dehors, on était sûr qu’il ferait beau dans l’église; l’un était rempli dans toute sa grandeur par un seul personnage pareil à un Roi de jeu\r\n de cartes, qui vivait là-haut, sous un dais architectural, entre ciel et terre; (et dans le reflet oblique et bleu duquel, parfois les jours de semaine, à midi, quand il n’y a pas d’office,—à l’un de ces rares moments où l’église\r\n aérée, vacante, plus humaine, luxueuse, avec du soleil sur son riche mobilier, avait l’air presque habitable comme le hall de pierre sculptée et de verre peint, d’un hôtel de style moyen âge,—on voyait s’agenouiller un instant M<sup\r\n >me</sup\r\n >\r\n Sazerat, posant sur le prie-Dieu voisin un paquet tout ficelé de petits fours qu’elle venait de prendre chez le pâtissier d’en face et qu’elle allait rapporter pour le déjeuner); dans un autre une montagne de neige rose, au pied de\r\n laquelle se livrait un combat, semblait avoir givré à même la verrière qu’elle boursouflait de son trouble grésil comme une vitre à laquelle il serait resté des flocons, mais des flocons éclairés par quelque aurore (par la même sans\r\n doute qui empourprait le retable de l’autel de tons si frais qu’ils semblaient plutôt posés là momentanément par une lueur du dehors prête à s’évanouir que par des couleurs attachées à jamais à la pierre); et tous étaient si anciens\r\n qu’on voyait çà et là leur vieillesse argentée étinceler de la poussière des siècles et monter brillante et usée jusqu’à la corde la trame de leur douce tapisserie de verre. Il y en avait un qui était un haut compartiment divisé en\r\n une centaine de petits vitraux rectangulaires où dominait le bleu, comme un grand jeu de cartes pareil à ceux qui devaient distraire le roi Charles VI; mais soit qu’un rayon eût brillé, soit que mon regard en bougeant eût promené à\r\n travers la verrière tour à tour éteinte et rallumée, un mouvant et précieux incendie, l’instant d’après elle avait pris l’éclat changeant d’une traîne de paon, puis elle tremblait et ondulait en une pluie flamboyante et fantastique\r\n qui dégouttait du haut de la voûte sombre et rocheuse, le long des parois humides, comme si c’était dans la nef de quelque grotte irisée de sinueux stalactites que je suivais mes parents, qui portaient leur paroissien; un instant\r\n après les petits vitraux en losange avaient pris la transparence profonde, l’infrangible dureté de saphirs qui eussent été juxtaposés sur quelque immense pectoral, mais derrière lesquels on sentait, plus aimé que toutes ces richesses,\r\n un sourire momentané de soleil; il était aussi reconnaissable dans le flot bleu et doux dont il baignait les pierreries que sur le pavé de la place ou la paille du marché; et, même à nos premiers dimanches quand nous étions arrivés\r\n avant Pâques, il me consolait que la terre fût encore nue et noire, en faisant épanouir, comme en un printemps historique et qui datait des successeurs de saint Louis, ce tapis éblouissant et doré de myosotis en verre.\r\n </p>\r\n <p>\r\n Deux tapisseries de haute lice représentaient le couronnement d’Esther (le tradition voulait qu’on eût donné à Assuérus les traits d’un roi de France et à Esther ceux d’une dame de Guermantes dont il était amoureux) auxquelles leurs\r\n couleurs, en fondant, avaient ajouté une expression, un relief, un éclairage: un peu de rose flottait aux lèvres d’Esther au delà du dessin de leur contour, le jaune de sa robe s’étalait si onctueusement, si grassement, qu’elle en\r\n prenait une sorte de consistance et s’enlevait vivement sur l’atmosphère refoulée; et la verdure des arbres restée vive dans les parties basses du panneau de soie et de laine, mais ayant «passé» dans le haut, faisait se détacher en\r\n plus pâle, au-dessus des troncs foncés, les hautes branches jaunissantes, dorées et comme à demi effacées par la brusque et oblique illumination d’un soleil invisible. Tout cela et plus encore les objets précieux venus à l’église de\r\n personnages qui étaient pour moi presque des personnages de légende (la croix d’or travaillée disait-on par saint Éloi et donnée par Dagobert, le tombeau des fils de Louis le Germanique, en porphyre et en cuivre émaillé) à cause de\r\n quoi je m’avançais dans l’église, quand nous gagnions nos chaises, comme dans une vallée visitée des fées, où le paysan s’émerveille de voir dans un rocher, dans un arbre, dans une mare, la trace palpable de leur passage surnaturel,\r\n tout cela faisait d’elle pour moi quelque chose d’entièrement différent du reste de la ville: un édifice occupant, si l’on peut dire, un espace à quatre dimensions—la quatrième étant celle du Temps,—déployant à travers les siècles son\r\n vaisseau qui, de travée en travée, de chapelle en chapelle, semblait vaincre et franchir non pas seulement quelques mètres, mais des époques successives d’où il sortait victorieux; dérobant le rude et farouche XI<sup>e</sup> siècle\r\n dans l’épaisseur de ses murs, d’où il n’apparaissait avec ses lourds cintres bouchés et aveuglés de grossiers moellons que par la profonde entaille que creusait près du porche l’escalier du clocher, et, même là, dissimulé par les\r\n gracieuses arcades gothiques qui se pressaient coquettement devant lui comme de plus grandes sœurs, pour le cacher aux étrangers, se placent en souriant devant un jeune frère rustre, grognon et mal vêtu; élevant dans le ciel au-dessus\r\n de la Place, sa tour qui avait contemplé saint Louis et semblait le voir encore; et s’enfonçant avec sa crypte dans une nuit mérovingienne où, nous guidant à tâtons sous la voûte obscure et puissamment nervurée comme la membrane d’une\r\n immense chauve-souris de pierre, Théodore et sa sœur nous éclairaient d’une bougie le tombeau de la petite fille de Sigebert, sur lequel une profonde valve,—comme la trace d’un fossile,—avait été creusée, disait-on, «par une lampe de\r\n cristal qui, le soir du meurtre de la princesse franque, s’était détachée d’elle-même des chaînes d’or où elle était suspendue à la place de l’actuelle abside, et, sans que le cristal se brisât, sans que la flamme s’éteignît, s’était\r\n enfoncée dans la pierre et l’avait fait mollement céder sous elle.»\r\n </p>\r\n <p>\r\n L’abside de l’église de Combray, peut-on vraiment en parler? Elle était si grossière, si dénuée de beauté artistique et même d’élan religieux. Du dehors, comme le croisement des rues sur lequel elle donnait était en contre-bas, sa\r\n grossière muraille s’exhaussait d’un soubassement en moellons nullement polis, hérissés de cailloux, et qui n’avait rien de particulièrement ecclésiastique, les verrières semblaient percées à une hauteur excessive, et le tout avait\r\n plus l’air d’un mur de prison que d’église. Et certes, plus tard, quand je me rappelais toutes les glorieuses absides que j’ai vues, il ne me serait jamais venu à la pensée de rapprocher d’elles l’abside de Combray. Seulement, un\r\n jour, au détour d’une petite rue provinciale, j’aperçus, en face du croisement de trois ruelles, une muraille fruste et surélevée, avec des verrières percées en haut et offrant le même aspect asymétrique que l’abside de Combray. Alors\r\n je ne me suis pas demandé comme à Chartres ou à Reims avec quelle puissance y était exprimé le sentiment religieux, mais je me suis involontairement écrié: «L’Église!»\r\n </p>\r\n <p>\r\n L’église! Familière; mitoyenne, rue Saint-Hilaire, où était sa porte nord, de ses deux voisines, la pharmacie de M. Rapin et la maison de M<sup>me</sup> Loiseau, qu’elle touchait sans aucune séparation; simple citoyenne de Combray qui\r\n aurait pu avoir son numéro dans la rue si les rues de Combray avaient eu des numéros, et où il semble que le facteur aurait dû s’arrêter le matin quand il faisait sa distribution, avant d’entrer chez M<sup>me</sup> Loiseau et en\r\n sortant de chez M. Rapin, il y avait pourtant entre elle et tout ce qui n’était pas elle une démarcation que mon esprit n’a jamais pu arriver à franchir. M<sup>me</sup> Loiseau avait beau avoir à sa fenêtre des fuchsias, qui prenaient\r\n la mauvaise habitude de laisser leurs branches courir toujours partout tête baissée, et dont les fleurs n’avaient rien de plus pressé, quand elles étaient assez grandes, que d’aller rafraîchir leurs joues violettes et congestionnées\r\n contre la sombre façade de l’église, les fuchsias ne devenaient pas sacrés pour cela pour moi; entre les fleurs et la pierre noircie sur laquelle elles s’appuyaient, si mes yeux ne percevaient pas d’intervalle, mon esprit réservait un\r\n abîme.\r\n </p>\r\n <p>\r\n On reconnaissait le clocher de Saint-Hilaire de bien loin, inscrivant sa figure inoubliable à l’horizon où Combray n’apparaissait pas encore; quand du train qui, la semaine de Pâques, nous amenait de Paris, mon père l’apercevait qui\r\n filait tour à tour sur tous les sillons du ciel, faisant courir en tous sens son petit coq de fer, il nous disait: «Allons, prenez les couvertures, on est arrivé.» Et dans une des plus grandes promenades que nous faisions de Combray,\r\n il y avait un endroit où la route resserrée débouchait tout à coup sur un immense plateau fermé à l’horizon par des forêts déchiquetées que dépassait seul la fine pointe du clocher de Saint-Hilaire, mais si mince, si rose, qu’elle\r\n semblait seulement rayée sur le ciel par un ongle qui aurait voulu donner à se paysage, à ce tableau rien que de nature, cette petite marque d’art, cette unique indication humaine. Quand on se rapprochait et qu’on pouvait apercevoir\r\n le reste de la tour carrée et à demi détruite qui, moins haute, subsistait à côté de lui, on était frappé surtout de ton rougeâtre et sombre des pierres; et, par un matin brumeux d’automne, on aurait dit, s’élevant au-dessus du violet\r\n orageux des vignobles, une ruine de pourpre presque de la couleur de la vigne vierge.\r\n </p>\r\n <p>\r\n Souvent sur la place, quand nous rentrions, ma grand’mère me faisait arrêter pour le regarder. Des fenêtres de sa tour, placées deux par deux les unes au-dessus des autres, avec cette juste et originale proportion dans les distances\r\n qui ne donne pas de la beauté et de la dignité qu’aux visages humains, il lâchait, laissait tomber à intervalles réguliers des volées de corbeaux qui, pendant un moment, tournoyaient en criant, comme si les vieilles pierres qui les\r\n laissaient s’ébattre sans paraître les voir, devenues tout d’un coup inhabitables et dégageant un principe d’agitation infinie, les avait frappés et repoussés. Puis, après avoir rayé en tous sens le velours violet de l’air du soir,\r\n brusquement calmés ils revenaient s’absorber dans la tour, de néfaste redevenue propice, quelques-uns posés çà et là, ne semblant pas bouger, mais happant peut-être quelque insecte, sur la pointe d’un clocheton, comme une mouette\r\n arrêtée avec l’immobilité d’un pêcheur à la crête d’une vague. Sans trop savoir pourquoi, ma grand’mère trouvait au clocher de Saint-Hilaire cette absence de vulgarité, de prétention, de mesquinerie, qui lui faisait aimer et croire\r\n riches d’une influence bienfaisante, la nature, quand la main de l’homme ne l’avait pas, comme faisait le jardinier de ma grand’tante, rapetissée, et les œuvres de génie. Et sans doute, toute partie de l’église qu’on apercevait la\r\n distinguait de tout autre édifice par une sorte de pensée qui lui était infuse, mais c’était dans son clocher qu’elle semblait prendre conscience d’elle-même, affirmer une existence individuelle et responsable. C’était lui qui parlait\r\n pour elle. Je crois surtout que, confusément, ma grand’mère trouvait au clocher de Combray ce qui pour elle avait le plus de prix au monde, l’air naturel et l’air distingué. Ignorante en architecture, elle disait: «Mes enfants,\r\n moquez-vous de moi si vous voulez, il n’est peut-être pas beau dans les règles, mais sa vieille figure bizarre me plaît. Je suis sûre que s’il jouait du piano, il ne jouerait pas sec.» Et en le regardant, en suivant des yeux la douce\r\n tension, l’inclinaison fervente de ses pentes de pierre qui se rapprochaient en s’élevant comme des mains jointes qui prient, elle s’unissait si bien à l’effusion de la flèche, que son regard semblait s’élancer avec elle; et en même\r\n temps elle souriait amicalement aux vieilles pierres usées dont le couchant n’éclairait plus que le faîte et qui, à partir du moment où elles entraient dans cette zone ensoleillée, adoucies par la lumière, paraissaient tout d’un coup\r\n montées bien plus haut, lointaines, comme un chant repris «en voix de tête» une octave au-dessus.\r\n </p>\r\n <p>\r\n C’était le clocher de Saint-Hilaire qui donnait à toutes les occupations, à toutes les heures, à tous les points de vue de la ville, leur figure, leur couronnement, leur consécration. De ma chambre, je ne pouvais apercevoir que sa\r\n base qui avait été recouverte d’ardoises; mais quand, le dimanche, je les voyais, par une chaude matinée d’été, flamboyer comme un soleil noir, je me disais: «Mon-Dieu! neuf heures! il faut se préparer pour aller à la grand’messe si\r\n je veux avoir le temps d’aller embrasser tante Léonie avant», et je savais exactement la couleur qu’avait le soleil sur la place, la chaleur et la poussière du marché, l’ombre que faisait le store du magasin où maman entrerait\r\n peut-être avant la messe dans une odeur de toile écrue, faire emplette de quelque mouchoir que lui ferait montrer, en cambrant la taille, le patron qui, tout en se préparant à fermer, venait d’aller dans l’arrière-boutique passer sa\r\n veste du dimanche et se savonner les mains qu’il avait l’habitude, toutes les cinq minutes, même dans les circonstances les plus mélancoliques, de frotter l’une contre l’autre d’un air d’entreprise, de partie fine et de réussite.\r\n </p>\r\n <p>\r\n Quand après la messe, on entrait dire à Théodore d’apporter une brioche plus grosse que d’habitude parce que nos cousins avaient profité du beau temps pour venir de Thiberzy déjeuner avec nous, on avait devant soi le clocher qui, doré\r\n et cuit lui-même comme une plus grande brioche bénie, avec des écailles et des égouttements gommeux de soleil, piquait sa pointe aiguë dans le ciel bleu. Et le soir, quand je rentrais de promenade et pensais au moment où il faudrait\r\n tout à l’heure dire bonsoir à ma mère et ne plus la voir, il était au contraire si doux, dans la journée finissante, qu’il avait l’air d’être posé et enfoncé comme un coussin de velours brun sur le ciel pâli qui avait cédé sous sa\r\n pression, s’était creusé légèrement pour lui faire sa place et refluait sur ses bords; et les cris des oiseaux qui tournaient autour de lui semblaient accroître son silence, élancer encore sa flèche et lui donner quelque chose\r\n d’ineffable.\r\n </p>\r\n <p>\r\n Même dans les courses qu’on avait à faire derrière l’église, là où on ne la voyait pas, tout semblait ordonné par rapport au clocher surgi ici ou là entre les maisons, peut-être plus émouvant encore quand il apparaissait ainsi sans\r\n l’église. Et certes, il y en a bien d’autres qui sont plus beaux vus de cette façon, et j’ai dans mon souvenir des vignettes de clochers dépassant les toits, qui ont un autre caractère d’art que celles que composaient les tristes rues\r\n de Combray. Je n’oublierai jamais, dans une curieuse ville de Normandie voisine de Balbec, deux charmants hôtels du XVIII<sup>e</sup> siècle, qui me sont à beaucoup d’égards chers et vénérables et entre lesquels, quand on la regarde\r\n du beau jardin qui descend des perrons vers la rivière, la flèche gothique d’une église qu’ils cachent s’élance, ayant l’air de terminer, de surmonter leurs façades, mais d’une matière si différente, si précieuse, si annelée, si rose,\r\n si vernie, qu’on voit bien qu’elle n’en fait pas plus partie que de deux beaux galets unis, entre lesquels elle est prise sur la plage, la flèche purpurine et crénelée de quelque coquillage fuselé en tourelle et glacé d’émail. Même à\r\n Paris, dans un des quartiers les plus laids de la ville, je sais une fenêtre où on voit après un premier, un second et même un troisième plan fait des toits amoncelés de plusieurs rues, une cloche violette, parfois rougeâtre, parfois\r\n aussi, dans les plus nobles «épreuves» qu’en tire l’atmosphère, d’un noir décanté de cendres, laquelle n’est autre que le dôme Saint-Augustin et qui donne à cette vue de Paris le caractère de certaines vues de Rome par Piranesi. Mais\r\n comme dans aucune de ces petites gravures, avec quelque goût que ma mémoire ait pu les exécuter elle ne put mettre ce que j’avais perdu depuis longtemps, le sentiment qui nous fait non pas considérer une chose comme un spectacle, mais\r\n y croire comme en un être sans équivalent, aucune d’elles ne tient sous sa dépendance toute une partie profonde de ma vie, comme fait le souvenir de ces aspects du clocher de Combray dans les rues qui sont derrière l’église. Qu’on le\r\n vît à cinq heures, quand on allait chercher les lettres à la poste, à quelques maisons de soi, à gauche, surélevant brusquement d’une cime isolée la ligne de faîte des toits; que si, au contraire, on voulait entrer demander des\r\n nouvelles de M<sup>me</sup> Sazerat, on suivît des yeux cette ligne redevenue basse après la descente de son autre versant en sachant qu’il faudrait tourner à la deuxième rue après le clocher; soit qu’encore, poussant plus loin, si on\r\n allait à la gare, on le vît obliquement, montrant de profil des arêtes et des surfaces nouvelles comme un solide surpris à un moment inconnu de sa révolution; ou que, des bords de la Vivonne, l’abside musculeusement ramassée et\r\n remontée par la perspective semblât jaillir de l’effort que le clocher faisait pour lancer sa flèche au cœur du ciel: c’était toujours à lui qu’il fallait revenir, toujours lui qui dominait tout, sommant les maisons d’un pinacle\r\n inattendu, levé avant moi comme le doigt de Dieu dont le corps eût été caché dans la foule des humains sans que je le confondisse pour cela avec elle. Et aujourd’hui encore si, dans une grande ville de province ou dans un quartier de\r\n Paris que je connais mal, un passant qui m’a «mis dans mon chemin» me montre au loin, comme un point de repère, tel beffroi d’hôpital, tel clocher de couvent levant la pointe de son bonnet ecclésiastique au coin d’une rue que je dois\r\n prendre, pour peu que ma mémoire puisse obscurément lui trouver quelque trait de ressemblance avec la figure chère et disparue, le passant, s’il se retourne pour s’assurer que je ne m’égare pas, peut, à son étonnement, m’apercevoir\r\n qui, oublieux de la promenade entreprise ou de la course obligée, reste là, devant le clocher, pendant des heures, immobile, essayant de me souvenir, sentant au fond de moi des terres reconquises sur l’oubli qui s’assèchent et se\r\n rebâtissent; et sans doute alors, et plus anxieusement que tout à l’heure quand je lui demandais de me renseigner, je cherche encore mon chemin, je tourne une rue...mais...c’est dans mon cœur...\r\n </p>\r\n <p>\r\n En rentrant de la messe, nous rencontrions souvent M. Legrandin qui, retenu à Paris par sa profession d’ingénieur, ne pouvait, en dehors des grandes vacances, venir à sa propriété de Combray que du samedi soir au lundi matin. C’était\r\n un de ces hommes qui, en dehors d’une carrière scientifique où ils ont d’ailleurs brillamment réussi, possèdent une culture toute différente, littéraire, artistique, que leur spécialisation professionnelle n’utilise pas et dont\r\n profite leur conversation. Plus lettrés que bien des littérateurs (nous ne savions pas à cette époque que M. Legrandin eût une certaine réputation comme écrivain et nous fûmes très étonnés de voir qu’un musicien célèbre avait composé\r\n une mélodie sur des vers de lui), doués de plus de «facilité» que bien des peintres, ils s’imaginent que la vie qu’ils mènent n’est pas celle qui leur aurait convenu et apportent à leurs occupations positives soit une insouciance\r\n mêlée de fantaisie, soit une application soutenue et hautaine, méprisante, amère et consciencieuse. Grand, avec une belle tournure, un visage pensif et fin aux longues moustaches blondes, au regard bleu et désenchanté, d’une politesse\r\n raffinée, causeur comme nous n’en avions jamais entendu, il était aux yeux de ma famille qui le citait toujours en exemple, le type de l’homme d’élite, prenant la vie de la façon la plus noble et la plus délicate. Ma grand’mère lui\r\n reprochait seulement de parler un peu trop bien, un peu trop comme un livre, de ne pas avoir dans son langage le naturel qu’il y avait dans ses cravates lavallière toujours flottantes, dans son veston droit presque d’écolier. Elle\r\n s’étonnait aussi des tirades enflammées qu’il entamait souvent contre l’aristocratie, la vie mondaine, le snobisme, «certainement le péché auquel pense saint Paul quand il parle du péché pour lequel il n’y a pas de rémission.»\r\n </p>\r\n <p>\r\n L’ambition mondaine était un sentiment que ma grand’mère était si incapable de ressentir et presque de comprendre qu’il lui paraissait bien inutile de mettre tant d’ardeur à la flétrir. De plus elle ne trouvait pas de très bon goût\r\n que M. Legrandin dont la sœur était mariée près de Balbec avec un gentilhomme bas-normand se livrât à des attaques aussi violentes encore les nobles, allant jusqu’à reprocher à la Révolution de ne les avoir pas tous guillotinés.\r\n </p>\r\n <p>—Salut, amis! nous disait-il en venant à notre rencontre. Vous êtes heureux d’habiter beaucoup ici; demain il faudra que je rentre à Paris, dans ma niche.</p>\r\n <p>\r\n —«Oh! ajoutait-il, avec ce sourire doucement ironique et déçu, un peu distrait, qui lui était particulier, certes il y a dans ma maison toutes les choses inutiles. Il n’y manque que le nécessaire, un grand morceau de ciel comme ici.\r\n Tâchez de garder toujours un morceau de ciel au-dessus de votre vie, petit garçon, ajoutait-il en se tournant vers moi. Vous avez une jolie âme, d’une qualité rare, une nature d’artiste, ne la laissez pas manquer de ce qu’il lui\r\n faut.»\r\n </p>\r\n <p>\r\n Quand, à notre retour, ma tante nous faisait demander si M<sup>me</sup> Goupil était arrivée en retard à la messe, nous étions incapables de la renseigner. En revanche nous ajoutions à son trouble en lui disant qu’un peintre\r\n travaillait dans l’église à copier le vitrail de Gilbert le Mauvais. Françoise, envoyée aussitôt chez l’épicier, était revenue bredouille par la faute de l’absence de Théodore à qui sa double profession de chantre ayant une part de\r\n l’entretien de l’église, et de garçon épicier donnait, avec des relations dans tous les mondes, un savoir universel.\r\n </p>\r\n <p>—«Ah! soupirait ma tante, je voudrais que ce soit déjà l’heure d’Eulalie. Il n’y a vraiment qu’elle qui pourra me dire cela.»</p>\r\n <p>\r\n Eulalie était une fille boiteuse, active et sourde qui s’était «retirée» après la mort de M<sup>me</sup> de la Bretonnerie où elle avait été en place depuis son enfance et qui avait pris à côté de l’église une chambre, d’où elle\r\n descendait tout le temps soit aux offices, soit, en dehors des offices, dire une petite prière ou donner un coup de main à Théodore; le reste du temps elle allait voir des personnes malades comme ma tante Léonie à qui elle racontait\r\n ce qui s’était passé à la messe ou aux vêpres. Elle ne dédaignait pas d’ajouter quelque casuel à la petite rente que lui servait la famille de ses anciens maîtres en allant de temps en temps visiter le linge du curé ou de quelque\r\n autre personnalité marquante du monde clérical de Combray. Elle portait au-dessus d’une mante de drap noir un petit béguin blanc, presque de religieuse, et une maladie de peau donnait à une partie de ses joues et à son nez recourbé,\r\n les tons rose vif de la balsamine. Ses visites étaient la grande distraction de ma tante Léonie qui ne recevait plus guère personne d’autre, en dehors de M. le Curé. Ma tante avait peu à peu évincé tous les autres visiteurs parce\r\n qu’ils avaient le tort à ses yeux de rentrer tous dans l’une ou l’autre des deux catégories de gens qu’elle détestait. Les uns, les pires et dont elle s’était débarrassée les premiers, étaient ceux qui lui conseillaient de ne pas\r\n «s’écouter» et professaient, fût-ce négativement et en ne la manifestant que par certains silences de désapprobation ou par certains sourires de doute, la doctrine subversive qu’une petite promenade au soleil et un bon bifteck\r\n saignant (quand elle gardait quatorze heures sur l’estomac deux méchantes gorgées d’eau de Vichy!) lui feraient plus de bien que son lit et ses médecines. L’autre catégorie se composait des personnes qui avaient l’air de croire\r\n qu’elle était plus gravement malade qu’elle ne pensait, était aussi gravement malade qu’elle le disait. Aussi, ceux qu’elle avait laissé monter après quelques hésitations et sur les officieuses instances de Françoise et qui, au cours\r\n de leur visite, avaient montré combien ils étaient indignes de la faveur qu’on leur faisait en risquant timidement un: «Ne croyez-vous pas que si vous vous secouiez un peu par un beau temps», ou qui, au contraire, quand elle leur\r\n avait dit: «Je suis bien bas, bien bas, c’est la fin, mes pauvres amis», lui avaient répondu: «Ah! quand on n’a pas la santé! Mais vous pouvez durer encore comme ça», ceux-là, les uns comme les autres, étaient sûrs de ne plus jamais\r\n être reçus. Et si Françoise s’amusait de l’air épouvanté de ma tante quand de son lit elle avait aperçu dans la rue du Saint-Esprit une de ces personnes qui avait l’air de venir chez elle ou quand elle avait entendu un coup de\r\n sonnette, elle riait encore bien plus, et comme d’un bon tour, des ruses toujours victorieuses de ma tante pour arriver à les faire congédier et de leur mine déconfite en s’en retournant sans l’avoir vue, et, au fond admirait sa\r\n maîtresse qu’elle jugeait supérieure à tous ces gens puisqu’elle ne voulait pas les recevoir. En somme, ma tante exigeait à la fois qu’on l’approuvât dans son régime, qu’on la plaignît pour ses souffrances et qu’on la rassurât sur son\r\n avenir.\r\n </p>\r\n <p>\r\n C’est à quoi Eulalie excellait. Ma tante pouvait lui dire vingt fois en une minute: «C’est la fin, ma pauvre Eulalie», vingt fois Eulalie répondait: «Connaissant votre maladie comme vous la connaissez, madame Octave, vous irez à cent\r\n ans, comme me disait hier encore M<sup>me</sup> Sazerin.» (Une des plus fermes croyances d’Eulalie et que le nombre imposant des démentis apportés par l’expérience n’avait pas suffi à entamer, était que M<sup>me</sup> Sazerat\r\n s’appelait M<sup>me</sup> Sazerin.)\r\n </p>\r\n <p>—Je ne demande pas à aller à cent ans, répondait ma tante qui préférait ne pas voir assigner à ses jours un terme précis.</p>\r\n <p>\r\n Et comme Eulalie savait avec cela comme personne distraire ma tante sans la fatiguer, ses visites qui avaient lieu régulièrement tous les dimanches sauf empêchement inopiné, étaient pour ma tante un plaisir dont la perspective\r\n l’entretenait ces jours-là dans un état agréable d’abord, mais bien vite douloureux comme une faim excessive, pour peu qu’Eulalie fût en retard. Trop prolongée, cette volupté d’attendre Eulalie tournait en supplice, ma tante ne\r\n cessait de regarder l’heure, bâillait, se sentait des faiblesses. Le coup de sonnette d’Eulalie, s’il arrivait tout à la fin de la journée, quand elle ne l’espérait plus, la faisait presque se trouver mal. En réalité, le dimanche,\r\n elle ne pensait qu’à cette visite et sitôt le déjeuner fini, Françoise avait hâte que nous quittions la salle à manger pour qu’elle pût monter «occuper» ma tante. Mais (surtout à partir du moment où les beaux jours s’installaient à\r\n Combray) il y avait bien longtemps que l’heure altière de midi, descendue de la tour de Saint-Hilaire qu’elle armoriait des douze fleurons momentanés de sa couronne sonore avait retenti autour de notre table, auprès du pain bénit venu\r\n lui aussi familièrement en sortant de l’église, quand nous étions encore assis devant les assiettes des Mille et une Nuits, appesantis par la chaleur et surtout par le repas. Car, au fond permanent d’œufs, de côtelettes, de pommes de\r\n terre, de confitures, de biscuits, qu’elle ne nous annonçait même plus, Françoise ajoutait—selon les travaux des champs et des vergers, le fruit de la marée, les hasards du commerce, les politesses des voisins et son propre génie, et\r\n si bien que notre menu, comme ces quatre-feuilles qu’on sculptait au XIII<sup>e</sup> siècle au portail des cathédrales, reflétait un peu le rythme des saisons et les épisodes de la vie—: une barbue parce que la marchande lui en avait\r\n garanti la fraîcheur, une dinde parce qu’elle en avait vu une belle au marché de Roussainville-le-Pin, des cardons à la moelle parce qu’elle ne nous en avait pas encore fait de cette manière-là, un gigot rôti parce que le grand air\r\n creuse et qu’il avait bien le temps de descendre d’ici sept heures, des épinards pour changer, des abricots parce que c’était encore une rareté, des groseilles parce que dans quinze jours il n’y en aurait plus, des framboises que M.\r\n Swann avait apportées exprès, des cerises, les premières qui vinssent du cerisier du jardin après deux ans qu’il n’en donnait plus, du fromage à la crème que j’aimais bien autrefois, un gâteau aux amandes parce qu’elle l’avait\r\n commandé la veille, une brioche parce que c’était notre tour de l’offrir. Quand tout cela était fini, composée expressément pour nous, mais dédiée plus spécialement à mon père qui était amateur, une crème au chocolat, inspiration,\r\n attention personnelle de Françoise, nous était offerte, fugitive et légère comme une œuvre de circonstance où elle avait mis tout son talent. Celui qui eût refusé d’en goûter en disant: «J’ai fini, je n’ai plus faim», se serait\r\n immédiatement ravalé au rang de ces goujats qui, même dans le présent qu’un artiste leur fait d’une de ses œuvres, regardent au poids et à la matière alors que n’y valent que l’intention et la signature. Même en laisser une seule\r\n goutte dans le plat eût témoigné de la même impolitesse que se lever avant la fin du morceau au nez du compositeur.\r\n </p>\r\n <p>\r\n Enfin ma mère me disait: «Voyons, ne reste pas ici indéfiniment, monte dans ta chambre si tu as trop chaud dehors, mais va d’abord prendre l’air un instant pour ne pas lier en sortant de table.» J’allais m’asseoir près de la pompe et\r\n de son auge, souvent ornée, comme un fond gothique, d’une salamandre, qui sculptait sur la pierre fruste le relief mobile de son corps allégorique et fuselé, sur le banc sans dossier ombragé d’un lilas, dans ce petit coin du jardin\r\n qui s’ouvrait par une porte de service sur la rue du Saint-Esprit et de la terre peu soignée duquel s’élevait par deux degrés, en saillie de la maison, et comme une construction indépendante, l’arrière-cuisine. On apercevait son\r\n dallage rouge et luisant comme du porphyre. Elle avait moins l’air de l’antre de Françoise que d’un petit temple à Vénus. Elle regorgeait des offrandes du crémier, du fruitier, de la marchande de légumes, venus parfois de hameaux\r\n assez lointains pour lui dédier les prémices de leurs champs. Et son faîte était toujours couronné du roucoulement d’une colombe.\r\n </p>\r\n <p>\r\n Autrefois, je ne m’attardais pas dans le bois consacré qui l’entourait, car, avant de monter lire, j’entrais dans le petit cabinet de repos que mon oncle Adolphe, un frère de mon grand-père, ancien militaire qui avait pris sa retraite\r\n comme commandant, occupait au rez-de-chaussée, et qui, même quand les fenêtres ouvertes laissaient entrer la chaleur, sinon les rayons du soleil qui atteignaient rarement jusque-là, dégageait inépuisablement cette odeur obscure et\r\n fraîche, à la fois forestière et ancien régime, qui fait rêver longuement les narines, quand on pénètre dans certains pavillons de chasse abandonnés. Mais depuis nombre d’années je n’entrais plus dans le cabinet de mon oncle Adolphe,\r\n ce dernier ne venant plus à Combray à cause d’une brouille qui était survenue entre lui et ma famille, par ma faute, dans les circonstances suivantes:\r\n </p>\r\n <p>\r\n Une ou deux fois par mois, à Paris, on m’envoyait lui faire une visite, comme il finissait de déjeuner, en simple vareuse, servi par son domestique en veste de travail de coutil rayé violet et blanc. Il se plaignait en ronchonnant que\r\n je n’étais pas venu depuis longtemps, qu’on l’abandonnait; il m’offrait un massepain ou une mandarine, nous traversions un salon dans lequel on ne s’arrêtait jamais, où on ne faisait jamais de feu, dont les murs étaient ornés de\r\n moulures dorées, les plafonds peints d’un bleu qui prétendait imiter le ciel et les meubles capitonnés en satin comme chez mes grands-parents, mais jaune; puis nous passions dans ce qu’il appelait son cabinet de «travail» aux murs\r\n duquel étaient accrochées de ces gravures représentant sur fond noir une déesse charnue et rose conduisant un char, montée sur un globe, ou une étoile au front, qu’on aimait sous le second Empire parce qu’on leur trouvait un air\r\n pompéien, puis qu’on détesta, et qu’on recommence à aimer pour une seule et même raison, malgré les autres qu’on donne et qui est qu’elles ont l’air second Empire. Et je restais avec mon oncle jusqu’à ce que son valet de chambre vînt\r\n lui demander, de la part du cocher, pour quelle heure celui-ci devait atteler. Mon oncle se plongeait alors dans une méditation qu’aurait craint de troubler d’un seul mouvement son valet de chambre émerveillé, et dont il attendait\r\n avec curiosité le résultat, toujours identique. Enfin, après une hésitation suprême, mon oncle prononçait infailliblement ces mots: «Deux heures et quart», que le valet de chambre répétait avec étonnement, mais sans discuter: «Deux\r\n heures et quart? bien...je vais le dire...»\r\n </p>\r\n <p>\r\n A cette époque j’avais l’amour du théâtre, amour platonique, car mes parents ne m’avaient encore jamais permis d’y aller, et je me représentais d’une façon si peu exacte les plaisirs qu’on y goûtait que je n’étais pas éloigné de\r\n croire que chaque spectateur regardait comme dans un stéréoscope un décor qui n’était que pour lui, quoique semblable au millier d’autres que regardait, chacun pour soi, le reste des spectateurs.\r\n </p>\r\n <p>\r\n Tous les matins je courais jusqu’à la colonne Moriss pour voir les spectacles qu’elle annonçait. Rien n’était plus désintéressé et plus heureux que les rêves offerts à mon imagination par chaque pièce annoncée et qui étaient\r\n conditionnés à la fois par les images inséparables des mots qui en composaient le titre et aussi de la couleur des affiches encore humides et boursouflées de colle sur lesquelles il se détachait. Si ce n’est une de ces œuvres étranges\r\n comme le Testament de César Girodot et Œdipe-Roi lesquelles s’inscrivaient, non sur l’affiche verte de l’Opéra-Comique, mais sur l’affiche lie de vin de la Comédie-Française, rien ne me paraissait plus différent de l’aigrette\r\n étincelante et blanche des Diamants de la Couronne que le satin lisse et mystérieux du Domino Noir, et, mes parents m’ayant dit que quand j’irais pour la première fois au théâtre j’aurais à choisir entre ces deux pièces, cherchant à\r\n approfondir successivement le titre de l’une et le titre de l’autre, puisque c’était tout ce que je connaissais d’elles, pour tâcher de saisir en chacun le plaisir qu’il me promettait et de le comparer à celui que recélait l’autre,\r\n j’arrivais à me représenter avec tant de force, d’une part une pièce éblouissante et fière, de l’autre une pièce douce et veloutée, que j’étais aussi incapable de décider laquelle aurait ma préférence, que si, pour le dessert, on\r\n m’avait donné à opter encore du riz à l’Impératrice et de la crème au chocolat.\r\n </p>\r\n <p>\r\n Toutes mes conversations avec mes camarades portaient sur ces acteurs dont l’art, bien qu’il me fût encore inconnu, était la première forme, entre toutes celles qu’il revêt, sous laquelle se laissait pressentir par moi, l’Art. Entre\r\n la manière que l’un ou l’autre avait de débiter, de nuancer une tirade, les différences les plus minimes me semblaient avoir une importance incalculable. Et, d’après ce que l’on m’avait dit d’eux, je les classais par ordre de talent,\r\n dans des listes que je me récitais toute la journée: et qui avaient fini par durcir dans mon cerveau et par le gêner de leur inamovibilité.\r\n </p>\r\n <p>\r\n Plus tard, quand je fus au collège, chaque fois que pendant les classes, je correspondais, aussitôt que le professeur avait la tête tournée, avec un nouvel ami, ma première question était toujours pour lui demander s’il était déjà\r\n allé au théâtre et s’il trouvait que le plus grand acteur était bien Got, le second Delaunay, etc. Et si, à son avis, Febvre ne venait qu’après Thiron, ou Delaunay qu’après Coquelin, la soudaine motilité que Coquelin, perdant la\r\n rigidité de la pierre, contractait dans mon esprit pour y passer au deuxième rang, et l’agilité miraculeuse, la féconde animation dont se voyait doué Delaunay pour reculer au quatrième, rendait la sensation du fleurissement et de la\r\n vie à mon cerveau assoupli et fertilisé.\r\n </p>\r\n <p>\r\n Mais si les acteurs me préoccupaient ainsi, si la vue de Maubant sortant un après-midi du Théâtre-Français m’avait causé le saisissement et les souffrances de l’amour, combien le nom d’une étoile flamboyant à la porte d’un théâtre,\r\n combien, à la glace d’un coupé qui passait dans la rue avec ses chevaux fleuris de roses au frontail, la vue du visage d’une femme que je pensais être peut-être une actrice, laissait en moi un trouble plus prolongé, un effort\r\n impuissant et douloureux pour me représenter sa vie! Je classais par ordre de talent les plus illustres: Sarah Bernhardt, la Berma, Bartet, Madeleine Brohan, Jeanne Samary, mais toutes m’intéressaient. Or mon oncle en connaissait\r\n beaucoup, et aussi des cocottes que je ne distinguais pas nettement des actrices. Il les recevait chez lui. Et si nous n’allions le voir qu’à certains jours c’est que, les autres jours, venaient des femmes avec lesquelles sa famille\r\n n’aurait pas pu se rencontrer, du moins à son avis à elle, car, pour mon oncle, au contraire, sa trop grande facilité à faire à de jolies veuves qui n’avaient peut-être jamais été mariées, à des comtesses de nom ronflant, qui n’était\r\n sans doute qu’un nom de guerre, la politesse de les présenter à ma grand’mère ou même à leur donner des bijoux de famille, l’avait déjà brouillé plus d’une fois avec mon grand-père. Souvent, à un nom d’actrice qui venait dans la\r\n conversation, j’entendais mon père dire à ma mère, en souriant: «Une amie de ton oncle»; et je pensais que le stage que peut-être pendant des années des hommes importants faisaient inutilement à la porte de telle femme qui ne\r\n répondait pas à leurs lettres et les faisait chasser par le concierge de son hôtel, mon oncle aurait pu en dispenser un gamin comme moi en le présentant chez lui à l’actrice, inapprochable à tant d’autres, qui était pour lui une\r\n intime amie.\r\n </p>\r\n <p>\r\n Aussi,—sous le prétexte qu’une leçon qui avait été déplacée tombait maintenant si mal qu’elle m’avait empêché plusieurs fois et m’empêcherait encore de voir mon oncle—un jour, autre que celui qui était réservé aux visites que nous lui\r\n faisions, profitant de ce que mes parents avaient déjeuné de bonne heure, je sortis et au lieu d’aller regarder la colonne d’affiches, pour quoi on me laissait aller seul, je courus jusqu’à lui. Je remarquai devant sa porte une\r\n voiture attelée de deux chevaux qui avaient aux œillères un œillet rouge comme avait le cocher à sa boutonnière. De l’escalier j’entendis un rire et une voix de femme, et dès que j’eus sonné, un silence, puis le bruit de portes qu’on\r\n fermait. Le valet de chambre vint ouvrir, et en me voyant parut embarrassé, me dit que mon oncle était très occupé, ne pourrait sans doute pas me recevoir et tandis qu’il allait pourtant le prévenir la même voix que j’avais entendue\r\n disait: «Oh, si! laisse-le entrer; rien qu’une minute, cela m’amuserait tant. Sur la photographie qui est sur ton bureau, il ressemble tant à sa maman, ta nièce, dont la photographie est à côté de la sienne, n’est-ce pas? Je voudrais\r\n le voir rien qu’un instant, ce gosse.»\r\n </p>\r\n <p>J’entendis mon oncle grommeler, se fâcher; finalement le valet de chambre me fit entrer.</p>\r\n <p>\r\n Sur la table, il y avait la même assiette de massepains que d’habitude; mon oncle avait sa vareuse de tous les jours, mais en face de lui, en robe de soie rose avec un grand collier de perles au cou, était assise une jeune femme qui\r\n achevait de manger une mandarine. L’incertitude où j’étais s’il fallait dire madame ou mademoiselle me fit rougir et n’osant pas trop tourner les yeux de son côté de peur d’avoir à lui parler, j’allai embrasser mon oncle. Elle me\r\n regardait en souriant, mon oncle lui dit: «Mon neveu», sans lui dire mon nom, ni me dire le sien, sans doute parce que, depuis les difficultés qu’il avait eues avec mon grand-père, il tâchait autant que possible d’éviter tout trait\r\n d’union entre sa famille et ce genre de relations.\r\n </p>\r\n <p>—«Comme il ressemble à sa mère,» dit-elle.</p>\r\n <p>—«Mais vous n’avez jamais vu ma nièce qu’en photographie, dit vivement mon oncle d’un ton bourru.»</p>\r\n <p>\r\n —«Je vous demande pardon, mon cher ami, je l’ai croisée dans l’escalier l’année dernière quand vous avez été si malade. Il est vrai que je ne l’ai vue que le temps d’un éclair et que votre escalier est bien noir, mais cela m’a suffi\r\n pour l’admirer. Ce petit jeune homme a ses beaux yeux et aussi ça, dit-elle, en traçant avec son doigt une ligne sur le bas de son front. Est-ce que madame votre nièce porte le même nom que vous, ami? demanda-t-elle à mon oncle.»\r\n </p>\r\n <p>—«Il ressemble surtout à son père, grogna mon oncle qui ne se souciait pas plus de faire des présentations à distance en disant le nom de maman que d’en faire de près. C’est tout à fait son père et aussi ma pauvre mère.»</p>\r\n <p>—«Je ne connais pas son père, dit la dame en rose avec une légère inclinaison de la tête, et je n’ai jamais connu votre pauvre mère, mon ami. Vous vous souvenez, c’est peu après votre grand chagrin que nous nous sommes connus.»</p>\r\n <p>\r\n J’éprouvais une petite déception, car cette jeune dame ne différait pas des autres jolies femmes que j’avais vues quelquefois dans ma famille notamment de la fille d’un de nos cousins chez lequel j’allais tous les ans le premier\r\n janvier. Mieux habillée seulement, l’amie de mon oncle avait le même regard vif et bon, elle avait l’air aussi franc et aimant. Je ne lui trouvais rien de l’aspect théâtral que j’admirais dans les photographies d’actrices, ni de\r\n l’expression diabolique qui eût été en rapport avec la vie qu’elle devait mener. J’avais peine à croire que ce fût une cocotte et surtout je n’aurais pas cru que ce fût une cocotte chic si je n’avais pas vu la voiture à deux chevaux,\r\n la robe rose, le collier de perles, si je n’avais pas su que mon oncle n’en connaissait que de la plus haute volée. Mais je me demandais comment le millionnaire qui lui donnait sa voiture et son hôtel et ses bijoux pouvait avoir du\r\n plaisir à manger sa fortune pour une personne qui avait l’air si simple et comme il faut. Et pourtant en pensant à ce que devait être sa vie, l’immoralité m’en troublait peut-être plus que si elle avait été concrétisée devant moi en\r\n une apparence spéciale,—d’être ainsi invisible comme le secret de quelque roman, de quelque scandale qui avait fait sortir de chez ses parents bourgeois et voué à tout le monde, qui avait fait épanouir en beauté et haussé jusqu’au\r\n demi-monde et à la notoriété celle que ses jeux de physionomie, ses intonations de voix, pareils à tant d’autres que je connaissais déjà, me faisaient malgré moi considérer comme une jeune fille de bonne famille, qui n’était plus\r\n d’aucune famille.\r\n </p>\r\n <p>On était passé dans le «cabinet de travail», et mon oncle, d’un air un peu gêné par ma présence, lui offrit des cigarettes.</p>\r\n <p>\r\n —«Non, dit-elle, cher, vous savez que je suis habituée à celles que le grand-duc m’envoie. Je lui ai dit que vous en étiez jaloux.» Et elle tira d’un étui des cigarettes couvertes d’inscriptions étrangères et dorées. «Mais si,\r\n reprit-elle tout d’un coup, je dois avoir rencontré chez vous le père de ce jeune homme. N’est-ce pas votre neveu? Comment ai-je pu l’oublier? Il a été tellement bon, tellement exquis pour moi, dit-elle d’un air modeste et sensible.»\r\n Mais en pensant à ce qu’avait pu être l’accueil rude qu’elle disait avoir trouvé exquis, de mon père, moi qui connaissais sa réserve et sa froideur, j’étais gêné, comme par une indélicatesse qu’il aurait commise, de cette inégalité\r\n entre la reconnaissance excessive qui lui était accordée et son amabilité insuffisante. Il m’a semblé plus tard que c’était un des côtés touchants du rôle de ces femmes oisives et studieuses qu’elles consacrent leur générosité, leur\r\n talent, un rêve disponible de beauté sentimentale—car, comme les artistes, elles ne le réalisent pas, ne le font pas entrer dans les cadres de l’existence commune,—et un or qui leur coûte peu, à enrichir d’un sertissage précieux et\r\n fin la vie fruste et mal dégrossie des hommes. Comme celle-ci, dans le fumoir où mon oncle était en vareuse pour la recevoir, répandait son corps si doux, sa robe de soie rose, ses perles, l’élégance qui émane de l’amitié d’un\r\n grand-duc, de même elle avait pris quelque propos insignifiant de mon père, elle l’avait travaillé avec délicatesse, lui avait donné un tour, une appellation précieuse et y enchâssant un de ses regards d’une si belle eau, nuancé\r\n d’humilité et de gratitude, elle le rendait changé en un bijou artiste, en quelque chose de «tout à fait exquis».\r\n </p>\r\n <p>—«Allons, voyons, il est l’heure que tu t’en ailles», me dit mon oncle.</p>\r\n <p>\r\n Je me levai, j’avais une envie irrésistible de baiser la main de la dame en rose, mais il me semblait que c’eût été quelque chose d’audacieux comme un enlèvement. Mon cœur battait tandis que je me disais: «Faut-il le faire, faut-il ne\r\n pas le faire», puis je cessai de me demander ce qu’il fallait faire pour pouvoir faire quelque chose. Et d’un geste aveugle et insensé, dépouillé de toutes les raisons que je trouvais il y avait un moment en sa faveur, je portai à mes\r\n lèvres la main qu’elle me tendait.\r\n </p>\r\n <p>\r\n —«Comme il est gentil! il est déjà galant, il a un petit œil pour les femmes: il tient de son oncle. Ce sera un parfait gentleman», ajouta-t-elle en serrant les dents pour donner à la phrase un accent légèrement britannique. «Est-ce\r\n qu’il ne pourrait pas venir une fois prendre a cup of tea, comme disent nos voisins les Anglais; il n’aurait qu’à m’envoyer un «bleu» le matin.\r\n </p>\r\n <p>\r\n Je ne savais pas ce que c’était qu’un «bleu». Je ne comprenais pas la moitié des mots que disait la dame, mais la crainte que n’y fut cachée quelque question à laquelle il eût été impoli de ne pas répondre, m’empêchait de cesser de\r\n les écouter avec attention, et j’en éprouvais une grande fatigue.\r\n </p>\r\n <p>\r\n —«Mais non, c’est impossible, dit mon oncle, en haussant les épaules, il est très tenu, il travaille beaucoup. Il a tous les prix à son cours, ajouta-t-il, à voix basse pour que je n’entende pas ce mensonge et que je n’y contredise\r\n pas. Qui sait, ce sera peut-être un petit Victor Hugo, une espèce de Vaulabelle, vous savez.»\r\n </p>\r\n <p>\r\n —«J’adore les artistes, répondit la dame en rose, il n’y a qu’eux qui comprennent les femmes... Qu’eux et les êtres d’élite comme vous. Excusez mon ignorance, ami. Qui est Vaulabelle? Est-ce les volumes dorés qu’il y a dans la petite\r\n bibliothèque vitrée de votre boudoir? Vous savez que vous m’avez promis de me les prêter, j’en aurai grand soin.»\r\n </p>\r\n <p>\r\n Mon oncle qui détestait prêter ses livres ne répondit rien et me conduisit jusqu’à l’antichambre. Éperdu d’amour pour la dame en rose, je couvris de baisers fous les joues pleines de tabac de mon vieil oncle, et tandis qu’avec assez\r\n d’embarras il me laissait entendre sans oser me le dire ouvertement qu’il aimerait autant que je ne parlasse pas de cette visite à mes parents, je lui disais, les larmes aux yeux, que le souvenir de sa bonté était en moi si fort que\r\n je trouverais bien un jour le moyen de lui témoigner ma reconnaissance. Il était si fort en effet que deux heures plus tard, après quelques phrases mystérieuses et qui ne me parurent pas donner à mes parents une idée assez nette de la\r\n nouvelle importance dont j’étais doué, je trouvai plus explicite de leur raconter dans les moindres détails la visite que je venais de faire. Je ne croyais pas ainsi causer d’ennuis à mon oncle. Comment l’aurais-je cru, puisque je ne\r\n le désirais pas. Et je ne pouvais supposer que mes parents trouveraient du mal dans une visite où je n’en trouvais pas. N’arrive-t-il pas tous les jours qu’un ami nous demande de ne pas manquer de l’excuser auprès d’une femme à qui il\r\n a été empêché d’écrire, et que nous négligions de le faire jugeant que cette personne ne peut pas attacher d’importance à un silence qui n’en a pas pour nous? Je m’imaginais, comme tout le monde, que le cerveau des autres était un\r\n réceptacle inerte et docile, sans pouvoir de réaction spécifique sur ce qu’on y introduisait; et je ne doutais pas qu’en déposant dans celui de mes parents la nouvelle de la connaissance que mon oncle m’avait fait faire, je ne leur\r\n transmisse en même temps comme je le souhaitais, le jugement bienveillant que je portais sur cette présentation. Mes parents malheureusement s’en remirent à des principes entièrement différents de ceux que je leur suggérais d’adopter,\r\n quand ils voulurent apprécier l’action de mon oncle. Mon père et mon grand-père eurent avec lui des explications violentes; j’en fus indirectement informé. Quelques jours après, croisant dehors mon oncle qui passait en voiture\r\n découverte, je ressentis la douleur, la reconnaissance, le remords que j’aurais voulu lui exprimer. A côté de leur immensité, je trouvai qu’un coup de chapeau serait mesquin et pourrait faire supposer à mon oncle que je ne me croyais\r\n pas tenu envers lui à plus qu’à une banale politesse. Je résolus de m’abstenir de ce geste insuffisant et je détournai la tête. Mon oncle pensa que je suivais en cela les ordres de mes parents, il ne le leur pardonna pas, et il est\r\n mort bien des années après sans qu’aucun de nous l’ait jamais revu.\r\n </p>\r\n <p>\r\n Aussi je n’entrais plus dans le cabinet de repos maintenant fermé, de mon oncle Adolphe, et après m’être attardé aux abords de l’arrière-cuisine, quand Françoise, apparaissant sur le parvis, me disait: «Je vais laisser ma fille de\r\n cuisine servir le café et monter l’eau chaude, il faut que je me sauve chez M<sup>me</sup> Octave», je me décidais à rentrer et montais directement lire chez moi. La fille de cuisine était une personne morale, une institution\r\n permanente à qui des attributions invariables assuraient une sorte de continuité et d’identité, à travers la succession des formes passagères en lesquelles elle s’incarnait: car nous n’eûmes jamais la même deux ans de suite. L’année\r\n où nous mangeâmes tant d’asperges, la fille de cuisine habituellement chargée de les «plumer» était une pauvre créature maladive, dans un état de grossesse déjà assez avancé quand nous arrivâmes à Pâques, et on s’étonnait même que\r\n Françoise lui laissât faire tant de courses et de besogne, car elle commençait à porter difficilement devant elle la mystérieuse corbeille, chaque jour plus remplie, dont on devinait sous ses amples sarraus la forme magnifique.\r\n Ceux-ci rappelaient les houppelandes qui revêtent certaines des figures symboliques de Giotto dont M. Swann m’avait donné des photographies. C’est lui-même qui nous l’avait fait remarquer et quand il nous demandait des nouvelles de la\r\n fille de cuisine, il nous disait: «Comment va la Charité de Giotto?» D’ailleurs elle-même, la pauvre fille, engraissée par sa grossesse, jusqu’à la figure, jusqu’aux joues qui tombaient droites et carrées, ressemblait en effet assez à\r\n ces vierges, fortes et hommasses, matrones plutôt, dans lesquelles les vertus sont personnifiées à l’Arena. Et je me rends compte maintenant que ces Vertus et ces Vices de Padoue lui ressemblaient encore d’une autre manière. De même\r\n que l’image de cette fille était accrue par le symbole ajouté qu’elle portait devant son ventre, sans avoir l’air d’en comprendre le sens, sans que rien dans son visage en traduisît la beauté et l’esprit, comme un simple et pesant\r\n fardeau, de même c’est sans paraître s’en douter que la puissante ménagère qui est représentée à l’Arena au-dessous du nom «Caritas» et dont la reproduction était accrochée au mur de ma salle d’études, à Combray, incarne cette vertu,\r\n c’est sans qu’aucune pensée de charité semble avoir jamais pu être exprimée par son visage énergique et vulgaire. Par une belle invention du peintre elle foule aux pieds les trésors de la terre, mais absolument comme si elle piétinait\r\n des raisins pour en extraire le jus ou plutôt comme elle aurait monté sur des sacs pour se hausser; et elle tend à Dieu son cœur enflammé, disons mieux, elle le lui «passe», comme une cuisinière passe un tire-bouchon par le soupirail\r\n de son sous-sol à quelqu’un qui le lui demande à la fenêtre du rez-de-chaussée. L’Envie, elle, aurait eu davantage une certaine expression d’envie. Mais dans cette fresque-là encore, le symbole tient tant de place et est représenté\r\n comme si réel, le serpent qui siffle aux lèvres de l’Envie est si gros, il lui remplit si complètement sa bouche grande ouverte, que les muscles de sa figure sont distendus pour pouvoir le contenir, comme ceux d’un enfant qui gonfle\r\n un ballon avec son souffle, et que l’attention de l’Envie—et la nôtre du même coup—tout entière concentrée sur l’action de ses lèvres, n’a guère de temps à donner à d’envieuses pensées.\r\n </p>\r\n <p>\r\n Malgré toute l’admiration que M. Swann professait pour ces figures de Giotto, je n’eus longtemps aucun plaisir à considérer dans notre salle d’études, où on avait accroché les copies qu’il m’en avait rapportées, cette Charité sans\r\n charité, cette Envie qui avait l’air d’une planche illustrant seulement dans un livre de médecine la compression de la glotte ou de la luette par une tumeur de la langue ou par l’introduction de l’instrument de l’opérateur, une\r\n Justice, dont le visage grisâtre et mesquinement régulier était celui-là même qui, à Combray, caractérisait certaines jolies bourgeoises pieuses et sèches que je voyais à la messe et dont plusieurs étaient enrôlées d’avance dans les\r\n milices de réserve de l’Injustice. Mais plus tard j’ai compris que l’étrangeté saisissante, la beauté spéciale de ces fresques tenait à la grande place que le symbole y occupait, et que le fait qu’il fût représenté non comme un\r\n symbole puisque la pensée symbolisée n’était pas exprimée, mais comme réel, comme effectivement subi ou matériellement manié, donnait à la signification de l’œuvre quelque chose de plus littéral et de plus précis, à son enseignement\r\n quelque chose de plus concret et de plus frappant. Chez la pauvre fille de cuisine, elle aussi, l’attention n’était-elle pas sans cesse ramenée à son ventre par le poids qui le tirait; et de même encore, bien souvent la pensée des\r\n agonisants est tournée vers le côté effectif, douloureux, obscur, viscéral, vers cet envers de la mort qui est précisément le côté qu’elle leur présente, qu’elle leur fait rudement sentir et qui ressemble beaucoup plus à un fardeau\r\n qui les écrase, à une difficulté de respirer, à un besoin de boire, qu’à ce que nous appelons l’idée de la mort.\r\n </p>\r\n <p>\r\n Il fallait que ces Vertus et ces Vices de Padoue eussent en eux bien de la réalité puisqu’ils m’apparaissaient comme aussi vivants que la servante enceinte, et qu’elle-même ne me semblait pas beaucoup moins allégorique. Et peut-être\r\n cette non-participation (du moins apparente) de l’âme d’un être à la vertu qui agit par lui, a aussi en dehors de sa valeur esthétique une réalité sinon psychologique, au moins, comme on dit, physiognomonique. Quand, plus tard, j’ai\r\n eu l’occasion de rencontrer, au cours de ma vie, dans des couvents par exemple, des incarnations vraiment saintes de la charité active, elles avaient généralement un air allègre, positif, indifférent et brusque de chirurgien pressé,\r\n ce visage où ne se lit aucune commisération, aucun attendrissement devant la souffrance humaine, aucune crainte de la heurter, et qui est le visage sans douceur, le visage antipathique et sublime de la vraie bonté.\r\n </p>\r\n <p>\r\n Pendant que la fille de cuisine,—faisant briller involontairement la supériorité de Françoise, comme l’Erreur, par le contraste, rend plus éclatant le triomphe de la Vérité—servait du café qui, selon maman n’était que de l’eau chaude,\r\n et montait ensuite dans nos chambres de l’eau chaude qui était à peine tiède, je m’étais étendu sur mon lit, un livre à la main, dans ma chambre qui protégeait en tremblant sa fraîcheur transparente et fragile contre le soleil de\r\n l’après-midi derrière ses volets presque clos où un reflet de jour avait pourtant trouvé moyen de faire passer ses ailes jaunes, et restait immobile entre le bois et le vitrage, dans un coin, comme un papillon posé. Il faisait à peine\r\n assez clair pour lire, et la sensation de la splendeur de la lumière ne m’était donnée que par les coups frappés dans la rue de la Cure par Camus (averti par Françoise que ma tante ne «reposait pas» et qu’on pouvait faire du bruit)\r\n contre des caisses poussiéreuses, mais qui, retentissant dans l’atmosphère sonore, spéciale aux temps chauds, semblaient faire voler au loin des astres écarlates; et aussi par les mouches qui exécutaient devant moi, dans leur petit\r\n concert, comme la musique de chambre de l’été: elle ne l’évoque pas à la façon d’un air de musique humaine, qui, entendu par hasard à la belle saison, vous la rappelle ensuite; elle est unie à l’été par un lien plus nécessaire: née\r\n des beaux jours, ne renaissant qu’avec eux, contenant un peu de leur essence, elle n’en réveille pas seulement l’image dans notre mémoire, elle en certifie le retour, la présence effective, ambiante, immédiatement accessible.\r\n </p>\r\n <p>\r\n Cette obscure fraîcheur de ma chambre était au plein soleil de la rue, ce que l’ombre est au rayon, c’est-à-dire aussi lumineuse que lui, et offrait à mon imagination le spectacle total de l’été dont mes sens si j’avais été en\r\n promenade, n’auraient pu jouir que par morceaux; et ainsi elle s’accordait bien à mon repos qui (grâce aux aventures racontées par mes livres et qui venaient l’émouvoir) supportait pareil au repos d’une main immobile au milieu d’une\r\n eau courante, le choc et l’animation d’un torrent d’activité.\r\n </p>\r\n <p>\r\n Mais ma grand’mère, même si le temps trop chaud s’était gâté, si un orage ou seulement un grain était survenu, venait me supplier de sortir. Et ne voulant pas renoncer à ma lecture, j’allais du moins la continuer au jardin, sous le\r\n marronnier, dans une petite guérite en sparterie et en toile au fond de laquelle j’étais assis et me croyais caché aux yeux des personnes qui pourraient venir faire visite à mes parents.\r\n </p>\r\n <p>\r\n Et ma pensée n’était-elle pas aussi comme une autre crèche au fond de laquelle je sentais que je restais enfoncé, même pour regarder ce qui se passait au dehors? Quand je voyais un objet extérieur, la conscience que je le voyais\r\n restait entre moi et lui, le bordait d’un mince liseré spirituel qui m’empêchait de jamais toucher directement sa matière; elle se volatilisait en quelque sorte avant que je prisse contact avec elle, comme un corps incandescent qu’on\r\n approche d’un objet mouillé ne touche pas son humidité parce qu’il se fait toujours précéder d’une zone d’évaporation. Dans l’espèce d’écran diapré d’états différents que, tandis que je lisais, déployait simultanément ma conscience,\r\n et qui allaient des aspirations les plus profondément cachées en moi-même jusqu’à la vision tout extérieure de l’horizon que j’avais, au bout du jardin, sous les yeux, ce qu’il y avait d’abord en moi, de plus intime, la poignée sans\r\n cesse en mouvement qui gouvernait le reste, c’était ma croyance en la richesse philosophique, en la beauté du livre que je lisais, et mon désir de me les approprier, quel que fût ce livre. Car, même si je l’avais acheté à Combray, en\r\n l’apercevant devant l’épicerie Borange, trop distante de la maison pour que Françoise pût s’y fournir comme chez Camus, mais mieux achalandée comme papeterie et librairie, retenu par des ficelles dans la mosaïque des brochures et des\r\n livraisons qui revêtaient les deux vantaux de sa porte plus mystérieuse, plus semée de pensées qu’une porte de cathédrale, c’est que je l’avais reconnu pour m’avoir été cité comme un ouvrage remarquable par le professeur ou le\r\n camarade qui me paraissait à cette époque détenir le secret de la vérité et de la beauté à demi pressenties, à demi incompréhensibles, dont la connaissance était le but vague mais permanent de ma pensée.\r\n </p>\r\n <p>\r\n Après cette croyance centrale qui, pendant ma lecture, exécutait d’incessants mouvements du dedans au dehors, vers la découverte de la vérité, venaient les émotions que me donnait l’action à laquelle je prenais part, car ces\r\n après-midi-là étaient plus remplis d’événements dramatiques que ne l’est souvent toute une vie. C’était les événements qui survenaient dans le livre que je lisais; il est vrai que les personnages qu’ils affectaient n’étaient pas\r\n «Réels», comme disait Françoise. Mais tous les sentiments que nous font éprouver la joie ou l’infortune d’un personnage réel ne se produisent en nous que par l’intermédiaire d’une image de cette joie ou de cette infortune;\r\n l’ingéniosité du premier romancier consista à comprendre que dans l’appareil de nos émotions, l’image étant le seul élément essentiel, la simplification qui consisterait à supprimer purement et simplement les personnages réels serait\r\n un perfectionnement décisif. Un être réel, si profondément que nous sympathisions avec lui, pour une grande part est perçu par nos sens, c’est-à-dire nous reste opaque, offre un poids mort que notre sensibilité ne peut soulever. Qu’un\r\n malheur le frappe, ce n’est qu’en une petite partie de la notion totale que nous avons de lui, que nous pourrons en être émus; bien plus, ce n’est qu’en une partie de la notion totale qu’il a de soi qu’il pourra l’être lui-même. La\r\n trouvaille du romancier a été d’avoir l’idée de remplacer ces parties impénétrables à l’âme par une quantité égale de parties immatérielles, c’est-à-dire que notre âme peut s’assimiler. Qu’importe dès lors que les actions, les\r\n émotions de ces êtres d’un nouveau genre nous apparaissent comme vraies, puisque nous les avons faites nôtres, puisque c’est en nous qu’elles se produisent, qu’elles tiennent sous leur dépendance, tandis que nous tournons\r\n fiévreusement les pages du livre, la rapidité de notre respiration et l’intensité de notre regard. Et une fois que le romancier nous a mis dans cet état, où comme dans tous les états purement intérieurs, toute émotion est décuplée, où\r\n son livre va nous troubler à la façon d’un rêve mais d’un rêve plus clair que ceux que nous avons en dormant et dont le souvenir durera davantage, alors, voici qu’il déchaîne en nous pendant une heure tous les bonheurs et tous les\r\n malheurs possibles dont nous mettrions dans la vie des années à connaître quelques-uns, et dont les plus intenses ne nous seraient jamais révélés parce que la lenteur avec laquelle ils se produisent nous en ôte la perception; (ainsi\r\n notre cœur change, dans la vie, et c’est la pire douleur; mais nous ne la connaissons que dans la lecture, en imagination: dans la réalité il change, comme certains phénomènes de la nature se produisent, assez lentement pour que, si\r\n nous pouvons constater successivement chacun de ses états différents, en revanche la sensation même du changement nous soit épargnée).\r\n </p>\r\n <p>\r\n Déjà moins intérieur à mon corps que cette vie des personnages, venait ensuite, à demi projeté devant moi, le paysage où se déroulait l’action et qui exerçait sur ma pensée une bien plus grande influence que l’autre, que celui que\r\n j’avais sous les yeux quand je les levais du livre. C’est ainsi que pendant deux étés, dans la chaleur du jardin de Combray, j’ai eu, à cause du livre que je lisais alors, la nostalgie d’un pays montueux et fluviatile, où je verrais\r\n beaucoup de scieries et où, au fond de l’eau claire, des morceaux de bois pourrissaient sous des touffes de cresson: non loin montaient le long de murs bas, des grappes de fleurs violettes et rougeâtres. Et comme le rêve d’une femme\r\n qui m’aurait aimé était toujours présent à ma pensée, ces étés-là ce rêve fut imprégné de la fraîcheur des eaux courantes; et quelle que fût la femme que j’évoquais, des grappes de fleurs violettes et rougeâtres s’élevaient aussitôt\r\n de chaque côté d’elle comme des couleurs complémentaires.\r\n </p>\r\n <p>\r\n Ce n’était pas seulement parce qu’une image dont nous rêvons reste toujours marquée, s’embellit et bénéficie du reflet des couleurs étrangères qui par hasard l’entourent dans notre rêverie; car ces paysages des livres que je lisais\r\n n’étaient pas pour moi que des paysages plus vivement représentés à mon imagination que ceux que Combray mettait sous mes yeux, mais qui eussent été analogues. Par le choix qu’en avait fait l’auteur, par la foi avec laquelle ma pensée\r\n allait au-devant de sa parole comme d’une révélation, ils me semblaient être—impression que ne me donnait guère le pays où je me trouvais, et surtout notre jardin, produit sans prestige de la correcte fantaisie du jardinier que\r\n méprisait ma grand’mère—une part véritable de la Nature elle-même, digne d’être étudiée et approfondie.\r\n </p>\r\n <p>\r\n Si mes parents m’avaient permis, quand je lisais un livre, d’aller visiter la région qu’il décrivait, j’aurais cru faire un pas inestimable dans la conquête de la vérité. Car si on a la sensation d’être toujours entouré de son âme, ce\r\n n’est pas comme d’une prison immobile: plutôt on est comme emporté avec elle dans un perpétuel élan pour la dépasser, pour atteindre à l’extérieur, avec une sorte de découragement, entendant toujours autour de soi cette sonorité\r\n identique qui n’est pas écho du dehors mais retentissement d’une vibration interne. On cherche à retrouver dans les choses, devenues par là précieuses, le reflet que notre âme a projeté sur elles; on est déçu en constatant qu’elles\r\n semblent dépourvues dans la nature, du charme qu’elles devaient, dans notre pensée, au voisinage de certaines idées; parfois on convertit toutes les forces de cette âme en habileté, en splendeur pour agir sur des êtres dont nous\r\n sentons bien qu’ils sont situés en dehors de nous et que nous ne les atteindrons jamais. Aussi, si j’imaginais toujours autour de la femme que j’aimais, les lieux que je désirais le plus alors, si j’eusse voulu que ce fût elle qui me\r\n les fît visiter, qui m’ouvrît l’accès d’un monde inconnu, ce n’était pas par le hasard d’une simple association de pensée; non, c’est que mes rêves de voyage et d’amour n’étaient que des moments—que je sépare artificiellement\r\n aujourd’hui comme si je pratiquais des sections à des hauteurs différentes d’un jet d’eau irisé et en apparence immobile—dans un même et infléchissable jaillissement de toutes les forces de ma vie.\r\n </p>\r\n <p>\r\n Enfin, en continuant à suivre du dedans au dehors les états simultanément juxtaposés dans ma conscience, et avant d’arriver jusqu’à l’horizon réel qui les enveloppait, je trouve des plaisirs d’un autre genre, celui d’être bien assis,\r\n de sentir la bonne odeur de l’air, de ne pas être dérangé par une visite; et, quand une heure sonnait au clocher de Saint-Hilaire, de voir tomber morceau par morceau ce qui de l’après-midi était déjà consommé, jusqu’à ce que\r\n j’entendisse le dernier coup qui me permettait de faire le total et après lequel, le long silence qui le suivait, semblait faire commencer, dans le ciel bleu, toute la partie qui m’était encore concédée pour lire jusqu’au bon dîner\r\n qu’apprêtait Françoise et qui me réconforterait des fatigues prises, pendant la lecture du livre, à la suite de son héros. Et à chaque heure il me semblait que c’était quelques instants seulement auparavant que la précédente avait\r\n sonné; la plus récente venait s’inscrire tout près de l’autre dans le ciel et je ne pouvais croire que soixante minutes eussent tenu dans ce petit arc bleu qui était compris entre leurs deux marques d’or. Quelquefois même cette heure\r\n prématurée sonnait deux coups de plus que la dernière; il y en avait donc une que je n’avais pas entendue, quelque chose qui avait eu lieu n’avait pas eu lieu pour moi; l’intérêt de la lecture, magique comme un profond sommeil, avait\r\n donné le change à mes oreilles hallucinées et effacé la cloche d’or sur la surface azurée du silence. Beaux après-midi du dimanche sous le marronnier du jardin de Combray, soigneusement vidés par moi des incidents médiocres de mon\r\n existence personnelle que j’y avais remplacés par une vie d’aventures et d’aspirations étranges au sein d’un pays arrosé d’eaux vives, vous m’évoquez encore cette vie quand je pense à vous et vous la contenez en effet pour l’avoir peu\r\n à peu contournée et enclose—tandis que je progressais dans ma lecture et que tombait la chaleur du jour—dans le cristal successif, lentement changeant et traversé de feuillages, de vos heures silencieuses, sonores, odorantes et\r\n limpides.\r\n </p>\r\n <p>\r\n Quelquefois j’étais tiré de ma lecture, dès le milieu de l’après-midi par la fille du jardinier, qui courait comme une folle, renversant sur son passage un oranger, se coupant un doigt, se cassant une dent et criant: «Les voilà, les\r\n voilà!» pour que Françoise et moi nous accourions et ne manquions rien du spectacle. C’était les jours où, pour des manœuvres de garnison, la troupe traversait Combray, prenant généralement la rue Sainte-Hildegarde. Tandis que nos\r\n domestiques, assis en rang sur des chaises en dehors de la grille, regardaient les promeneurs dominicaux de Combray et se faisaient voir d’eux, la fille du jardinier par la fente que laissaient entre elles deux maisons lointaines de\r\n l’avenue de la Gare, avait aperçu l’éclat des casques. Les domestiques avaient rentré précipitamment leurs chaises, car quand les cuirassiers défilaient rue Sainte-Hildegarde, ils en remplissaient toute la largeur, et le galop des\r\n chevaux rasait les maisons couvrant les trottoirs submergés comme des berges qui offrent un lit trop étroit à un torrent déchaîné.\r\n </p>\r\n <p>\r\n —«Pauvres enfants, disait Françoise à peine arrivée à la grille et déjà en larmes; pauvre jeunesse qui sera fauchée comme un pré; rien que d’y penser j’en suis choquée», ajoutait-elle en mettant la main sur son cœur, là où elle avait\r\n reçu ce choc.\r\n </p>\r\n <p>—«C’est beau, n’est-ce pas, madame Françoise, de voir des jeunes gens qui ne tiennent pas à la vie? disait le jardinier pour la faire «monter».</p>\r\n <p>Il n’avait pas parlé en vain:</p>\r\n <p>\r\n —«De ne pas tenir à la vie? Mais à quoi donc qu’il faut tenir, si ce n’est pas à la vie, le seul cadeau que le bon Dieu ne fasse jamais deux fois. Hélas! mon Dieu! C’est pourtant vrai qu’ils n’y tiennent pas! Je les ai vus en 70; ils\r\n n’ont plus peur de la mort, dans ces misérables guerres; c’est ni plus ni moins des fous; et puis ils ne valent plus la corde pour les pendre, ce n’est pas des hommes, c’est des lions.» (Pour Françoise la comparaison d’un homme à un\r\n lion, qu’elle prononçait li-on, n’avait rien de flatteur.)\r\n </p>\r\n <p>\r\n La rue Sainte-Hildegarde tournait trop court pour qu’on pût voir venir de loin, et c’était par cette fente entre les deux maisons de l’avenue de la gare qu’on apercevait toujours de nouveaux casques courant et brillant au soleil. Le\r\n jardinier aurait voulu savoir s’il y en avait encore beaucoup à passer, et il avait soif, car le soleil tapait. Alors tout d’un coup, sa fille s’élançant comme d’une place assiégée, faisait une sortie, atteignait l’angle de la rue, et\r\n après avoir bravé cent fois la mort, venait nous rapporter, avec une carafe de coco, la nouvelle qu’ils étaient bien un mille qui venaient sans arrêter, du côté de Thiberzy et de Méséglise. Françoise et le jardinier, réconciliés,\r\n discutaient sur la conduite à tenir en cas de guerre:\r\n </p>\r\n <p>—«Voyez-vous, Françoise, disait le jardinier, la révolution vaudrait mieux, parce que quand on la déclare il n’y a que ceux qui veulent partir qui y vont.»</p>\r\n <p>—«Ah! oui, au moins je comprends cela, c’est plus franc.»</p>\r\n <p>Le jardinier croyait qu’à la déclaration de guerre on arrêtait tous les chemins de fer.</p>\r\n <p>—«Pardi, pour pas qu’on se sauve», disait Françoise.</p>\r\n <p>\r\n Et le jardinier: «Ah! ils sont malins», car il n’admettait pas que la guerre ne fût pas une espèce de mauvais tour que l’État essayait de jouer au peuple et que, si on avait eu le moyen de le faire, il n’est pas une seule personne qui\r\n n’eût filé.\r\n </p>\r\n <p>\r\n Mais Françoise se hâtait de rejoindre ma tante, je retournais à mon livre, les domestiques se réinstallaient devant la porte à regarder tomber la poussière et l’émotion qu’avaient soulevées les soldats. Longtemps après que l’accalmie\r\n était venue, un flot inaccoutumé de promeneurs noircissait encore les rues de Combray. Et devant chaque maison, même celles où ce n’était pas l’habitude, les domestiques ou même les maîtres, assis et regardant, festonnaient le seuil\r\n d’un liséré capricieux et sombre comme celui des algues et des coquilles dont une forte marée laisse le crêpe et la broderie au rivage, après qu’elle s’est éloignée.\r\n </p>\r\n <p>\r\n Sauf ces jours-là, je pouvais d’habitude, au contraire, lire tranquille. Mais l’interruption et le commentaire qui furent apportés une fois par une visite de Swann à la lecture que j’étais en train de faire du livre d’un auteur tout\r\n nouveau pour moi, Bergotte, eut cette conséquence que, pour longtemps, ce ne fut plus sur un mur décoré de fleurs violettes en quenouille, mais sur un fond tout autre, devant le portail d’une cathédrale gothique, que se détacha\r\n désormais l’image d’une des femmes dont je rêvais.\r\n </p>\r\n <p>\r\n J’avais entendu parler de Bergotte pour la première fois par un de mes camarades plus âgé que moi et pour qui j’avais une grande admiration, Bloch. En m’entendant lui avouer mon admiration pour la Nuit d’Octobre, il avait fait éclater\r\n un rire bruyant comme une trompette et m’avait dit: «Défie-toi de ta dilection assez basse pour le sieur de Musset. C’est un coco des plus malfaisants et une assez sinistre brute. Je dois confesser, d’ailleurs, que lui et même le\r\n nommé Racine, ont fait chacun dans leur vie un vers assez bien rythmé, et qui a pour lui, ce qui est selon moi le mérite suprême, de ne signifier absolument rien. C’est: «La blanche Oloossone et la blanche Camire» et «La fille de\r\n Minos et de Pasiphaé». Ils m’ont été signalés à la décharge de ces deux malandrins par un article de mon très cher maître, le père Leconte, agréable aux Dieux Immortels. A propos voici un livre que je n’ai pas le temps de lire en ce\r\n moment qui est recommandé, paraît-il, par cet immense bonhomme. Il tient, m’a-t-on dit, l’auteur, le sieur Bergotte, pour un coco des plus subtils; et bien qu’il fasse preuve, des fois, de mansuétudes assez mal explicables, sa parole\r\n est pour moi oracle delphique. Lis donc ces proses lyriques, et si le gigantesque assembleur de rythmes qui a écrit Bhagavat et le Levrier de Magnus a dit vrai, par Apollôn, tu goûteras, cher maître, les joies nectaréennes de\r\n l’Olympos.» C’est sur un ton sarcastique qu’il m’avait demandé de l’appeler «cher maître» et qu’il m’appelait lui-même ainsi. Mais en réalité nous prenions un certain plaisir à ce jeu, étant encore rapprochés de l’âge où on croit\r\n qu’on crée ce qu’on nomme.\r\n </p>\r\n <p>\r\n Malheureusement, je ne pus pas apaiser en causant avec Bloch et en lui demandant des explications, le trouble où il m’avait jeté quand il m’avait dit que les beaux vers (à moi qui n’attendais d’eux rien moins que la révélation de la\r\n vérité) étaient d’autant plus beaux qu’ils ne signifiaient rien du tout. Bloch en effet ne fut pas réinvité à la maison. Il y avait d’abord été bien accueilli. Mon grand-père, il est vrai, prétendait que chaque fois que je me liais\r\n avec un de mes camarades plus qu’avec les autres et que je l’amenais chez nous, c’était toujours un juif, ce qui ne lui eût pas déplu en principe—même son ami Swann était d’origine juive—s’il n’avait trouvé que ce n’était pas\r\n d’habitude parmi les meilleurs que je le choisissais. Aussi quand j’amenais un nouvel ami il était bien rare qu’il ne fredonnât pas: «O Dieu de nos Pères» de la Juive ou bien «Israël romps ta chaîne», ne chantant que l’air\r\n naturellement (Ti la lam ta lam, talim), mais j’avais peur que mon camarade ne le connût et ne rétablît les paroles.\r\n </p>\r\n <p>\r\n Avant de les avoir vus, rien qu’en entendant leur nom qui, bien souvent, n’avait rien de particulièrement israélite, il devinait non seulement l’origine juive de ceux de mes amis qui l’étaient en effet, mais même ce qu’il y avait\r\n quelquefois de fâcheux dans leur famille.\r\n </p>\r\n <p>—«Et comment s’appelle-t-il ton ami qui vient ce soir?»</p>\r\n <p>—«Dumont, grand-père.»</p>\r\n <p>—«Dumont! Oh! je me méfie.»</p>\r\n <p>Et il chantait:</p>\r\n <p class="poem">\r\n «Archers, faites bonne garde!<br />\r\n Veillez sans trêve et sans bruit»;\r\n </p>\r\n <p>\r\n Et après nous avoir posé adroitement quelques questions plus précises, il s’écriait: «À la garde! À la garde!» ou, si c’était le patient lui-même déjà arrivé qu’il avait forcé à son insu, par un interrogatoire dissimulé, à confesser\r\n ses origines, alors pour nous montrer qu’il n’avait plus aucun doute, il se contentait de nous regarder en fredonnant imperceptiblement:\r\n </p>\r\n <p class="poem">\r\n «De ce timide Israëlite<br />\r\n Quoi! vous guidez ici les pas!»\r\n </p>\r\n <p>ou:</p>\r\n <p class="poem">«Champs paternels, Hébron, douce vallée.»</p>\r\n <p>ou encore:</p>\r\n <p class="poem">«Oui, je suis de la race élue.»</p>\r\n <p>\r\n Ces petites manies de mon grand-père n’impliquaient aucun sentiment malveillant à l’endroit de mes camarades. Mais Bloch avait déplu à mes parents pour d’autres raisons. Il avait commencé par agacer mon père qui, le voyant mouillé,\r\n lui avait dit avec intérêt:\r\n </p>\r\n <p>—«Mais, monsieur Bloch, quel temps fait-il donc, est-ce qu’il a plu? Je n’y comprends rien, le baromètre était excellent.»</p>\r\n <p>Il n’en avait tiré que cette réponse:</p>\r\n <p>—«Monsieur, je ne puis absolument vous dire s’il a plu. Je vis si résolument en dehors des contingences physiques que mes sens ne prennent pas la peine de me les notifier.»</p>\r\n <p>—«Mais, mon pauvre fils, il est idiot ton ami, m’avait dit mon père quand Bloch fut parti. Comment! il ne peut même pas me dire le temps qu’il fait! Mais il n’y a rien de plus intéressant! C’est un imbécile.</p>\r\n <p>Puis Bloch avait déplu à ma grand’mère parce que, après le déjeuner comme elle disait qu’elle était un peu souffrante, il avait étouffé un sanglot et essuyé des larmes.</p>\r\n <p>—«Comment veux-tu que ça soit sincère, me dit-elle, puisqu’il ne me connaît pas; ou bien alors il est fou.»</p>\r\n <p>Et enfin il avait mécontenté tout le monde parce que, étant venu déjeuner une heure et demie en retard et couvert de boue, au lieu de s’excuser, il avait dit:</p>\r\n <p>\r\n —«Je ne me laisse jamais influencer par les perturbations de l’atmosphère ni par les divisions conventionnelles du temps. Je réhabiliterais volontiers l’usage de la pipe d’opium et du kriss malais, mais j’ignore celui de ces\r\n instruments infiniment plus pernicieux et d’ailleurs platement bourgeois, la montre et le parapluie.»\r\n </p>\r\n <p>\r\n Il serait malgré tout revenu à Combray. Il n’était pas pourtant l’ami que mes parents eussent souhaité pour moi; ils avaient fini par penser que les larmes que lui avait fait verser l’indisposition de ma grand’mère n’étaient pas\r\n feintes; mais ils savaient d’instinct ou par expérience que les élans de notre sensibilité ont peu d’empire sur la suite de nos actes et la conduite de notre vie, et que le respect des obligations morales, la fidélité aux amis,\r\n l’exécution d’une œuvre, l’observance d’un régime, ont un fondement plus sûr dans des habitudes aveugles que dans ces transports momentanés, ardents et stériles. Ils auraient préféré pour moi à Bloch des compagnons qui ne me\r\n donneraient pas plus qu’il n’est convenu d’accorder à ses amis, selon les règles de la morale bourgeoise; qui ne m’enverraient pas inopinément une corbeille de fruits parce qu’ils auraient ce jour-là pensé à moi avec tendresse, mais\r\n qui, n’étant pas capables de faire pencher en ma faveur la juste balance des devoirs et des exigences de l’amitié sur un simple mouvement de leur imagination et de leur sensibilité, ne la fausseraient pas davantage à mon préjudice.\r\n Nos torts même font difficilement départir de ce qu’elles nous doivent ces natures dont ma grand’tante était le modèle, elle qui brouillée depuis des années avec une nièce à qui elle ne parlait jamais, ne modifia pas pour cela le\r\n testament où elle lui laissait toute sa fortune, parce que c’était sa plus proche parente et que cela «se devait».\r\n </p>\r\n <p>\r\n Mais j’aimais Bloch, mes parents voulaient me faire plaisir, les problèmes insolubles que je me posais à propos de la beauté dénuée de signification de la fille de Minos et de Pasiphaé me fatiguaient davantage et me rendaient plus\r\n souffrant que n’auraient fait de nouvelles conversations avec lui, bien que ma mère les jugeât pernicieuses. Et on l’aurait encore reçu à Combray si, après ce dîner, comme il venait de m’apprendre—nouvelle qui plus tard eut beaucoup\r\n d’influence sur ma vie, et la rendit plus heureuse, puis plus malheureuse—que toutes les femmes ne pensaient qu’à l’amour et qu’il n’y en a pas dont on ne pût vaincre les résistances, il ne m’avait assuré avoir entendu dire de la\r\n façon la plus certaine que ma grand’tante avait eu une jeunesse orageuse et avait été publiquement entretenue. Je ne pus me tenir de répéter ces propos à mes parents, on le mit à la porte quand il revint, et quand je l’abordai ensuite\r\n dans la rue, il fut extrêmement froid pour moi.\r\n </p>\r\n <p>Mais au sujet de Bergotte il avait dit vrai.</p>\r\n <p><!--end chapter--></p>\r\n <p>The rest of the text has been removed for performance reasons.</p>\r\n <div style="display: block; margin-top: 4em"></div>\r\n <section class="pg-boilerplate pgheader" id="pg-footer" lang="en">\r\n <div id="pg-end-separator">\r\n <span>*** END OF THE PROJECT GUTENBERG EBOOK DU CÔTÉ DE CHEZ SWANN ***</span>\r\n </div>\r\n\r\n <div>Updated editions will replace the previous one—the old editions will be renamed.</div>\r\n\r\n <div>\r\n Creating the works from print editions not protected by U.S. copyright law means that no one owns a United States copyright in these works, so the Foundation (and you!) can copy and distribute it in the United States without\r\n permission and without paying copyright royalties. Special rules, set forth in the General Terms of Use part of this license, apply to copying and distributing Project Gutenberg™ electronic works to protect the PROJECT GUTENBERG™\r\n concept and trademark. Project Gutenberg is a registered trademark, and may not be used if you charge for an eBook, except by following the terms of the trademark license, including paying royalties for use of the Project Gutenberg\r\n trademark. If you do not charge anything for copies of this eBook, complying with the trademark license is very easy. You may use this eBook for nearly any purpose such as creation of derivative works, reports, performances and\r\n research. Project Gutenberg eBooks may be modified and printed and given away—you may do practically ANYTHING in the United States with eBooks not protected by U.S. copyright law. Redistribution is subject to the trademark license,\r\n especially commercial redistribution.\r\n </div>\r\n\r\n <div id="project-gutenberg-license">START: FULL LICENSE</div>\r\n <h2 id="pg-footer-heading">THE FULL PROJECT GUTENBERG LICENSE</h2>\r\n <div class="agate">PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK</div>\r\n\r\n <div>\r\n To protect the Project Gutenberg™ mission of promoting the free distribution of electronic works, by using or distributing this work (or any other work associated in any way with the phrase “Project Gutenberg”), you agree to comply\r\n with all the terms of the Full Project Gutenberg™ License available with this file or online at www.gutenberg.org/license.\r\n </div>\r\n\r\n <div class="secthead">Section 1. General Terms of Use and Redistributing Project Gutenberg™ electronic works</div>\r\n\r\n <div>\r\n 1.A. By reading or using any part of this Project Gutenberg™ electronic work, you indicate that you have read, understand, agree to and accept all the terms of this license and intellectual property (trademark/copyright) agreement. If\r\n you do not agree to abide by all the terms of this agreement, you must cease using and return or destroy all copies of Project Gutenberg™ electronic works in your possession. If you paid a fee for obtaining a copy of or access to a\r\n Project Gutenberg™ electronic work and you do not agree to be bound by the terms of this agreement, you may obtain a refund from the person or entity to whom you paid the fee as set forth in paragraph 1.E.8.\r\n </div>\r\n\r\n <div>\r\n 1.B. “Project Gutenberg” is a registered trademark. It may only be used on or associated in any way with an electronic work by people who agree to be bound by the terms of this agreement. There are a few things that you can do with\r\n most Project Gutenberg™ electronic works even without complying with the full terms of this agreement. See paragraph 1.C below. There are a lot of things you can do with Project Gutenberg™ electronic works if you follow the terms of\r\n this agreement and help preserve free future access to Project Gutenberg™ electronic works. See paragraph 1.E below.\r\n </div>\r\n\r\n <div>\r\n 1.C. The Project Gutenberg Literary Archive Foundation (“the Foundation” or PGLAF), owns a compilation copyright in the collection of Project Gutenberg™ electronic works. Nearly all the individual works in the collection are in the\r\n public domain in the United States. If an individual work is unprotected by copyright law in the United States and you are located in the United States, we do not claim a right to prevent you from copying, distributing, performing,\r\n displaying or creating derivative works based on the work as long as all references to Project Gutenberg are removed. Of course, we hope that you will support the Project Gutenberg™ mission of promoting free access to electronic works\r\n by freely sharing Project Gutenberg™ works in compliance with the terms of this agreement for keeping the Project Gutenberg™ name associated with the work. You can easily comply with the terms of this agreement by keeping this work in\r\n the same format with its attached full Project Gutenberg™ License when you share it without charge with others.\r\n </div>\r\n\r\n <div>\r\n 1.D. The copyright laws of the place where you are located also govern what you can do with this work. Copyright laws in most countries are in a constant state of change. If you are outside the United States, check the laws of your\r\n country in addition to the terms of this agreement before downloading, copying, displaying, performing, distributing or creating derivative works based on this work or any other Project Gutenberg™ work. The Foundation makes no\r\n representations concerning the copyright status of any work in any country other than the United States.\r\n </div>\r\n\r\n <div>1.E. Unless you have removed all references to Project Gutenberg:</div>\r\n\r\n <div>\r\n 1.E.1. The following sentence, with active links to, or other immediate access to, the full Project Gutenberg™ License must appear prominently whenever any copy of a Project Gutenberg™ work (any work on which the phrase “Project\r\n Gutenberg” appears, or with which the phrase “Project Gutenberg” is associated) is accessed, displayed, performed, viewed, copied or distributed:\r\n </div>\r\n\r\n <blockquote>\r\n <div>\r\n This eBook is for the use of anyone anywhere in the United States and most other parts of the world at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project\r\n Gutenberg License included with this eBook or online at <a href="https://www.gutenberg.org">www.gutenberg.org</a>. If you are not located in the United States, you will have to check the laws of the country where you are located\r\n before using this eBook.\r\n </div>\r\n </blockquote>\r\n\r\n <div>\r\n 1.E.2. If an individual Project Gutenberg™ electronic work is derived from texts not protected by U.S. copyright law (does not contain a notice indicating that it is posted with permission of the copyright holder), the work can be\r\n copied and distributed to anyone in the United States without paying any fees or charges. If you are redistributing or providing access to a work with the phrase “Project Gutenberg” associated with or appearing on the work, you must\r\n comply either with the requirements of paragraphs 1.E.1 through 1.E.7 or obtain permission for the use of the work and the Project Gutenberg™ trademark as set forth in paragraphs 1.E.8 or 1.E.9.\r\n </div>\r\n\r\n <div>\r\n 1.E.3. If an individual Project Gutenberg™ electronic work is posted with the permission of the copyright holder, your use and distribution must comply with both paragraphs 1.E.1 through 1.E.7 and any additional terms imposed by the\r\n copyright holder. Additional terms will be linked to the Project Gutenberg™ License for all works posted with the permission of the copyright holder found at the beginning of this work.\r\n </div>\r\n\r\n <div>1.E.4. Do not unlink or detach or remove the full Project Gutenberg™ License terms from this work, or any files containing a part of this work or any other work associated with Project Gutenberg™.</div>\r\n\r\n <div>\r\n 1.E.5. Do not copy, display, perform, distribute or redistribute this electronic work, or any part of this electronic work, without prominently displaying the sentence set forth in paragraph 1.E.1 with active links or immediate access\r\n to the full terms of the Project Gutenberg™ License.\r\n </div>\r\n\r\n <div>\r\n 1.E.6. You may convert to and distribute this work in any binary, compressed, marked up, nonproprietary or proprietary form, including any word processing or hypertext form. However, if you provide access to or distribute copies of a\r\n Project Gutenberg™ work in a format other than “Plain Vanilla ASCII” or other format used in the official version posted on the official Project Gutenberg™ website (www.gutenberg.org), you must, at no additional cost, fee or expense\r\n to the user, provide a copy, a means of exporting a copy, or a means of obtaining a copy upon request, of the work in its original “Plain Vanilla ASCII” or other form. Any alternate format must include the full Project Gutenberg™\r\n License as specified in paragraph 1.E.1.\r\n </div>\r\n\r\n <div>1.E.7. Do not charge a fee for access to, viewing, displaying, performing, copying or distributing any Project Gutenberg™ works unless you comply with paragraph 1.E.8 or 1.E.9.</div>\r\n\r\n <div>1.E.8. You may charge a reasonable fee for copies of or providing access to or distributing Project Gutenberg™ electronic works provided that:</div>\r\n\r\n <ul>\r\n <li>\r\n • You pay a royalty fee of 20% of the gross profits you derive from the use of Project Gutenberg™ works calculated using the method you already use to calculate your applicable taxes. The fee is owed to the owner of the Project\r\n Gutenberg™ trademark, but he has agreed to donate royalties under this paragraph to the Project Gutenberg Literary Archive Foundation. Royalty payments must be paid within 60 days following each date on which you prepare (or are\r\n legally required to prepare) your periodic tax returns. Royalty payments should be clearly marked as such and sent to the Project Gutenberg Literary Archive Foundation at the address specified in Section 4, “Information about\r\n donations to the Project Gutenberg Literary Archive Foundation.”\r\n </li>\r\n\r\n <li>\r\n • You provide a full refund of any money paid by a user who notifies you in writing (or by e-mail) within 30 days of receipt that s/he does not agree to the terms of the full Project Gutenberg™ License. You must require such a\r\n user to return or destroy all copies of the works possessed in a physical medium and discontinue all use of and all access to other copies of Project Gutenberg™ works.\r\n </li>\r\n\r\n <li>• You provide, in accordance with paragraph 1.F.3, a full refund of any money paid for a work or a replacement copy, if a defect in the electronic work is discovered and reported to you within 90 days of receipt of the work.</li>\r\n\r\n <li>• You comply with all other terms of this agreement for free distribution of Project Gutenberg™ works.</li>\r\n </ul>\r\n\r\n <div>\r\n 1.E.9. If you wish to charge a fee or distribute a Project Gutenberg™ electronic work or group of works on different terms than are set forth in this agreement, you must obtain permission in writing from the Project Gutenberg Literary\r\n Archive Foundation, the manager of the Project Gutenberg™ trademark. Contact the Foundation as set forth in Section 3 below.\r\n </div>\r\n\r\n <div>1.F.</div>\r\n\r\n <div>\r\n 1.F.1. Project Gutenberg volunteers and employees expend considerable effort to identify, do copyright research on, transcribe and proofread works not protected by U.S. copyright law in creating the Project Gutenberg™ collection.\r\n Despite these efforts, Project Gutenberg™ electronic works, and the medium on which they may be stored, may contain “Defects,” such as, but not limited to, incomplete, inaccurate or corrupt data, transcription errors, a copyright or\r\n other intellectual property infringement, a defective or damaged disk or other medium, a computer virus, or computer codes that damage or cannot be read by your equipment.\r\n </div>\r\n\r\n <div>\r\n 1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the “Right of Replacement or Refund” described in paragraph 1.F.3, the Project Gutenberg Literary Archive Foundation, the owner of the Project Gutenberg™ trademark, and any\r\n other party distributing a Project Gutenberg™ electronic work under this agreement, disclaim all liability to you for damages, costs and expenses, including legal fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\r\n LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE PROVIDED IN PARAGRAPH 1.F.3. YOU AGREE THAT THE FOUNDATION, THE TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE LIABLE TO YOU FOR ACTUAL, DIRECT,\r\n INDIRECT, CONSEQUENTIAL, PUNITIVE OR INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH DAMAGE.\r\n </div>\r\n\r\n <div>\r\n 1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a defect in this electronic work within 90 days of receiving it, you can receive a refund of the money (if any) you paid for it by sending a written explanation to the\r\n person you received the work from. If you received the work on a physical medium, you must return the medium with your written explanation. The person or entity that provided you with the defective work may elect to provide a\r\n replacement copy in lieu of a refund. If you received the work electronically, the person or entity providing it to you may choose to give you a second opportunity to receive the work electronically in lieu of a refund. If the second\r\n copy is also defective, you may demand a refund in writing without further opportunities to fix the problem.\r\n </div>\r\n\r\n <div>\r\n 1.F.4. Except for the limited right of replacement or refund set forth in paragraph 1.F.3, this work is provided to you ‘AS-IS’, WITH NO OTHER WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF\r\n MERCHANTABILITY OR FITNESS FOR ANY PURPOSE.\r\n </div>\r\n\r\n <div>\r\n 1.F.5. Some states do not allow disclaimers of certain implied warranties or the exclusion or limitation of certain types of damages. If any disclaimer or limitation set forth in this agreement violates the law of the state applicable\r\n to this agreement, the agreement shall be interpreted to make the maximum disclaimer or limitation permitted by the applicable state law. The invalidity or unenforceability of any provision of this agreement shall not void the\r\n remaining provisions.\r\n </div>\r\n\r\n <div>\r\n 1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the trademark owner, any agent or employee of the Foundation, anyone providing copies of Project Gutenberg™ electronic works in accordance with this agreement, and any\r\n volunteers associated with the production, promotion and distribution of Project Gutenberg™ electronic works, harmless from all liability, costs and expenses, including legal fees, that arise directly or indirectly from any of the\r\n following which you do or cause to occur: (a) distribution of this or any Project Gutenberg™ work, (b) alteration, modification, or additions or deletions to any Project Gutenberg™ work, and (c) any Defect you cause.\r\n </div>\r\n\r\n <div class="secthead">Section 2. Information about the Mission of Project Gutenberg™</div>\r\n\r\n <div>\r\n Project Gutenberg™ is synonymous with the free distribution of electronic works in formats readable by the widest variety of computers including obsolete, old, middle-aged and new computers. It exists because of the efforts of\r\n hundreds of volunteers and donations from people in all walks of life.\r\n </div>\r\n\r\n <div>\r\n Volunteers and financial support to provide volunteers with the assistance they need are critical to reaching Project Gutenberg™’s goals and ensuring that the Project Gutenberg™ collection will remain freely available for generations\r\n to come. In 2001, the Project Gutenberg Literary Archive Foundation was created to provide a secure and permanent future for Project Gutenberg™ and future generations. To learn more about the Project Gutenberg Literary Archive\r\n Foundation and how your efforts and donations can help, see Sections 3 and 4 and the Foundation information page at www.gutenberg.org.\r\n </div>\r\n\r\n <div class="secthead">Section 3. Information about the Project Gutenberg Literary Archive Foundation</div>\r\n\r\n <div>\r\n The Project Gutenberg Literary Archive Foundation is a non-profit 501(c)(3) educational corporation organized under the laws of the state of Mississippi and granted tax exempt status by the Internal Revenue Service. The Foundation’s\r\n EIN or federal tax identification number is 64-6221541. Contributions to the Project Gutenberg Literary Archive Foundation are tax deductible to the full extent permitted by U.S. federal laws and your state’s laws.\r\n </div>\r\n\r\n <div>\r\n The Foundation’s business office is located at 809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887. Email contact links and up to date contact information can be found at the Foundation’s website and official page at\r\n www.gutenberg.org/contact\r\n </div>\r\n\r\n <div class="secthead">Section 4. Information about Donations to the Project Gutenberg Literary Archive Foundation</div>\r\n\r\n <div>\r\n Project Gutenberg™ depends upon and cannot survive without widespread public support and donations to carry out its mission of increasing the number of public domain and licensed works that can be freely distributed in\r\n machine-readable form accessible by the widest array of equipment including outdated equipment. Many small donations ($1 to $5,000) are particularly important to maintaining tax exempt status with the IRS.\r\n </div>\r\n\r\n <div>\r\n The Foundation is committed to complying with the laws regulating charities and charitable donations in all 50 states of the United States. Compliance requirements are not uniform and it takes a considerable effort, much paperwork and\r\n many fees to meet and keep up with these requirements. We do not solicit donations in locations where we have not received written confirmation of compliance. To SEND DONATIONS or determine the status of compliance for any particular\r\n state visit <a href="https://www.gutenberg.org/donate/">www.gutenberg.org/donate</a>.\r\n </div>\r\n\r\n <div>\r\n While we cannot and do not solicit contributions from states where we have not met the solicitation requirements, we know of no prohibition against accepting unsolicited donations from donors in such states who approach us with offers\r\n to donate.\r\n </div>\r\n\r\n <div>International donations are gratefully accepted, but we cannot make any statements concerning tax treatment of donations received from outside the United States. U.S. laws alone swamp our small staff.</div>\r\n\r\n <div>\r\n Please check the Project Gutenberg web pages for current donation methods and addresses. Donations are accepted in a number of other ways including checks, online payments and credit card donations. To donate, please visit:\r\n www.gutenberg.org/donate\r\n </div>\r\n\r\n <div class="secthead">Section 5. General Information About Project Gutenberg™ electronic works</div>\r\n\r\n <div>\r\n Professor Michael S. Hart was the originator of the Project Gutenberg™ concept of a library of electronic works that could be freely shared with anyone. For forty years, he produced and distributed Project Gutenberg™ eBooks with only\r\n a loose network of volunteer support.\r\n </div>\r\n\r\n <div>\r\n Project Gutenberg™ eBooks are often created from several printed editions, all of which are confirmed as not protected by copyright in the U.S. unless a copyright notice is included. Thus, we do not necessarily keep eBooks in\r\n compliance with any particular paper edition.\r\n </div>\r\n\r\n <div>Most people start at our website which has the main PG search facility: <a href="https://www.gutenberg.org">www.gutenberg.org</a>.</div>\r\n\r\n <div>\r\n This website includes information about Project Gutenberg™, including how to make donations to the Project Gutenberg Literary Archive Foundation, how to help produce our new eBooks, and how to subscribe to our email newsletter to hear\r\n about new eBooks.\r\n </div>\r\n </section>\r\n';
function OrderedMap(content) {
this.content = content;
}
OrderedMap.prototype = {
constructor: OrderedMap,
find: function(key) {
for (var i = 0; i < this.content.length; i += 2)
if (this.content[i] === key)
return i;
return -1;
},
// :: (string) → ?any
// Retrieve the value stored under `key`, or return undefined when
// no such key exists.
get: function(key) {
var found2 = this.find(key);
return found2 == -1 ? void 0 : this.content[found2 + 1];
},
// :: (string, any, ?string) → OrderedMap
// Create a new map by replacing the value of `key` with a new
// value, or adding a binding to the end of the map. If `newKey` is
// given, the key of the binding will be replaced with that key.
update: function(key, value, newKey) {
var self = newKey && newKey != key ? this.remove(newKey) : this;
var found2 = self.find(key), content = self.content.slice();
if (found2 == -1) {
content.push(newKey || key, value);
} else {
content[found2 + 1] = value;
if (newKey)
content[found2] = newKey;
}
return new OrderedMap(content);
},
// :: (string) → OrderedMap
// Return a map with the given key removed, if it existed.
remove: function(key) {
var found2 = this.find(key);
if (found2 == -1)
return this;
var content = this.content.slice();
content.splice(found2, 2);
return new OrderedMap(content);
},
// :: (string, any) → OrderedMap
// Add a new key to the start of the map.
addToStart: function(key, value) {
return new OrderedMap([key, value].concat(this.remove(key).content));
},
// :: (string, any) → OrderedMap
// Add a new key to the end of the map.
addToEnd: function(key, value) {
var content = this.remove(key).content.slice();
content.push(key, value);
return new OrderedMap(content);
},
// :: (string, string, any) → OrderedMap
// Add a key after the given key. If `place` is not found, the new
// key is added to the end.
addBefore: function(place, key, value) {
var without = this.remove(key), content = without.content.slice();
var found2 = without.find(place);
content.splice(found2 == -1 ? content.length : found2, 0, key, value);
return new OrderedMap(content);
},
// :: ((key: string, value: any))
// Call the given function for each key/value pair in the map, in
// order.
forEach: function(f) {
for (var i = 0; i < this.content.length; i += 2)
f(this.content[i], this.content[i + 1]);
},
// :: (union<Object, OrderedMap>) → OrderedMap
// Create a new map by prepending the keys in this map that don't
// appear in `map` before the keys in `map`.
prepend: function(map2) {
map2 = OrderedMap.from(map2);
if (!map2.size)
return this;
return new OrderedMap(map2.content.concat(this.subtract(map2).content));
},
// :: (union<Object, OrderedMap>) → OrderedMap
// Create a new map by appending the keys in this map that don't
// appear in `map` after the keys in `map`.
append: function(map2) {
map2 = OrderedMap.from(map2);
if (!map2.size)
return this;
return new OrderedMap(this.subtract(map2).content.concat(map2.content));
},
// :: (union<Object, OrderedMap>) → OrderedMap
// Create a map containing all the keys in this map that don't
// appear in `map`.
subtract: function(map2) {
var result = this;
map2 = OrderedMap.from(map2);
for (var i = 0; i < map2.content.length; i += 2)
result = result.remove(map2.content[i]);
return result;
},
// :: () → Object
// Turn ordered map into a plain object.
toObject: function() {
var result = {};
this.forEach(function(key, value) {
result[key] = value;
});
return result;
},
// :: number
// The amount of keys in this map.
get size() {
return this.content.length >> 1;
}
};
OrderedMap.from = function(value) {
if (value instanceof OrderedMap)
return value;
var content = [];
if (value)
for (var prop in value)
content.push(prop, value[prop]);
return new OrderedMap(content);
};
function findDiffStart(a, b, pos) {
for (let i = 0; ; i++) {
if (i == a.childCount || i == b.childCount)
return a.childCount == b.childCount ? null : pos;
let childA = a.child(i), childB = b.child(i);
if (childA == childB) {
pos += childA.nodeSize;
continue;
}
if (!childA.sameMarkup(childB))
return pos;
if (childA.isText && childA.text != childB.text) {
for (let j = 0; childA.text[j] == childB.text[j]; j++)
pos++;
return pos;
}
if (childA.content.size || childB.content.size) {
let inner = findDiffStart(childA.content, childB.content, pos + 1);
if (inner != null)
return inner;
}
pos += childA.nodeSize;
}
}
function findDiffEnd(a, b, posA, posB) {
for (let iA = a.childCount, iB = b.childCount; ; ) {
if (iA == 0 || iB == 0)
return iA == iB ? null : { a: posA, b: posB };
let childA = a.child(--iA), childB = b.child(--iB), size = childA.nodeSize;
if (childA == childB) {
posA -= size;
posB -= size;
continue;
}
if (!childA.sameMarkup(childB))
return { a: posA, b: posB };
if (childA.isText && childA.text != childB.text) {
let same = 0, minSize = Math.min(childA.text.length, childB.text.length);
while (same < minSize && childA.text[childA.text.length - same - 1] == childB.text[childB.text.length - same - 1]) {
same++;
posA--;
posB--;
}
return { a: posA, b: posB };
}
if (childA.content.size || childB.content.size) {
let inner = findDiffEnd(childA.content, childB.content, posA - 1, posB - 1);
if (inner)
return inner;
}
posA -= size;
posB -= size;
}
}
class Fragment {
/**
@internal
*/
constructor(content, size) {
this.content = content;
this.size = size || 0;
if (size == null)
for (let i = 0; i < content.length; i++)
this.size += content[i].nodeSize;
}
/**
Invoke a callback for all descendant nodes between the given two
positions (relative to start of this fragment). Doesn't descend
into a node when the callback returns `false`.
*/
nodesBetween(from2, to, f, nodeStart = 0, parent) {
for (let i = 0, pos = 0; pos < to; i++) {
let child = this.content[i], end = pos + child.nodeSize;
if (end > from2 && f(child, nodeStart + pos, parent || null, i) !== false && child.content.size) {
let start = pos + 1;
child.nodesBetween(Math.max(0, from2 - start), Math.min(child.content.size, to - start), f, nodeStart + start);
}
pos = end;
}
}
/**
Call the given callback for every descendant node. `pos` will be
relative to the start of the fragment. The callback may return
`false` to prevent traversal of a given node's children.
*/
descendants(f) {
this.nodesBetween(0, this.size, f);
}
/**
Extract the text between `from` and `to`. See the same method on
*/
textBetween(from2, to, blockSeparator, leafText) {
let text2 = "", separated = true;
this.nodesBetween(from2, to, (node, pos) => {
if (node.isText) {
text2 += node.text.slice(Math.max(from2, pos) - pos, to - pos);
separated = !blockSeparator;
} else if (node.isLeaf) {
if (leafText) {
text2 += typeof leafText === "function" ? leafText(node) : leafText;
} else if (node.type.spec.leafText) {
text2 += node.type.spec.leafText(node);
}
separated = !blockSeparator;
} else if (!separated && node.isBlock) {
text2 += blockSeparator;
separated = true;
}
}, 0);
return text2;
}
/**
Create a new fragment containing the combined content of this
fragment and the other.
*/
append(other) {
if (!other.size)
return this;
if (!this.size)
return other;
let last = this.lastChild, first2 = other.firstChild, content = this.content.slice(), i = 0;
if (last.isText && last.sameMarkup(first2)) {
content[content.length - 1] = last.withText(last.text + first2.text);
i = 1;
}
for (; i < other.content.length; i++)
content.push(other.content[i]);
return new Fragment(content, this.size + other.size);
}
/**
Cut out the sub-fragment between the two given positions.
*/
cut(from2, to = this.size) {
if (from2 == 0 && to == this.size)
return this;
let result = [], size = 0;
if (to > from2)
for (let i = 0, pos = 0; pos < to; i++) {
let child = this.content[i], end = pos + child.nodeSize;
if (end > from2) {
if (pos < from2 || end > to) {
if (child.isText)
child = child.cut(Math.max(0, from2 - pos), Math.min(child.text.length, to - pos));
else
child = child.cut(Math.max(0, from2 - pos - 1), Math.min(child.content.size, to - pos - 1));
}
result.push(child);
size += child.nodeSize;
}
pos = end;
}
return new Fragment(result, size);
}
/**
@internal
*/
cutByIndex(from2, to) {
if (from2 == to)
return Fragment.empty;
if (from2 == 0 && to == this.content.length)
return this;
return new Fragment(this.content.slice(from2, to));
}
/**
Create a new fragment in which the node at the given index is
replaced by the given node.
*/
replaceChild(index, node) {
let current = this.content[index];
if (current == node)
return this;
let copy2 = this.content.slice();
let size = this.size + node.nodeSize - current.nodeSize;
copy2[index] = node;
return new Fragment(copy2, size);
}
/**
Create a new fragment by prepending the given node to this
fragment.
*/
addToStart(node) {
return new Fragment([node].concat(this.content), this.size + node.nodeSize);
}
/**
Create a new fragment by appending the given node to this
fragment.
*/
addToEnd(node) {
return new Fragment(this.content.concat(node), this.size + node.nodeSize);
}
/**
Compare this fragment to another one.
*/
eq(other) {
if (this.content.length != other.content.length)
return false;
for (let i = 0; i < this.content.length; i++)
if (!this.content[i].eq(other.content[i]))
return false;
return true;
}
/**
The first child of the fragment, or `null` if it is empty.
*/
get firstChild() {
return this.content.length ? this.content[0] : null;
}
/**
The last child of the fragment, or `null` if it is empty.
*/
get lastChild() {
return this.content.length ? this.content[this.content.length - 1] : null;
}
/**
The number of child nodes in this fragment.
*/
get childCount() {
return this.content.length;
}
/**
Get the child node at the given index. Raise an error when the
index is out of range.
*/
child(index) {
let found2 = this.content[index];
if (!found2)
throw new RangeError("Index " + index + " out of range for " + this);
return found2;
}
/**
Get the child node at the given index, if it exists.
*/
maybeChild(index) {
return this.content[index] || null;
}
/**
Call `f` for every child node, passing the node, its offset
into this parent node, and its index.
*/
forEach(f) {
for (let i = 0, p = 0; i < this.content.length; i++) {
let child = this.content[i];
f(child, p, i);
p += child.nodeSize;
}
}
/**
Find the first position at which this fragment and another
fragment differ, or `null` if they are the same.
*/
findDiffStart(other, pos = 0) {
return findDiffStart(this, other, pos);
}
/**
Find the first position, searching from the end, at which this
fragment and the given fragment differ, or `null` if they are
the same. Since this position will not be the same in both
nodes, an object with two separate positions is returned.
*/
findDiffEnd(other, pos = this.size, otherPos = other.size) {
return findDiffEnd(this, other, pos, otherPos);
}
/**
Find the index and inner offset corresponding to a given relative
position in this fragment. The result object will be reused
(overwritten) the next time the function is called. (Not public.)
*/
findIndex(pos, round = -1) {
if (pos == 0)
return retIndex(0, pos);
if (pos == this.size)
return retIndex(this.content.length, pos);
if (pos > this.size || pos < 0)
throw new RangeError(`Position ${pos} outside of fragment (${this})`);
for (let i = 0, curPos = 0; ; i++) {
let cur = this.child(i), end = curPos + cur.nodeSize;
if (end >= pos) {
if (end == pos || round > 0)
return retIndex(i + 1, end);
return retIndex(i, curPos);
}
curPos = end;
}
}
/**
Return a debugging string that describes this fragment.
*/
toString() {
return "<" + this.toStringInner() + ">";
}
/**
@internal
*/
toStringInner() {
return this.content.join(", ");
}
/**
Create a JSON-serializeable representation of this fragment.
*/
toJSON() {
return this.content.length ? this.content.map((n) => n.toJSON()) : null;
}
/**
Deserialize a fragment from its JSON representation.
*/
static fromJSON(schema, value) {
if (!value)
return Fragment.empty;
if (!Array.isArray(value))
throw new RangeError("Invalid input for Fragment.fromJSON");
return new Fragment(value.map(schema.nodeFromJSON));
}
/**
Build a fragment from an array of nodes. Ensures that adjacent
text nodes with the same marks are joined together.
*/
static fromArray(array) {
if (!array.length)
return Fragment.empty;
let joined, size = 0;
for (let i = 0; i < array.length; i++) {
let node = array[i];
size += node.nodeSize;
if (i && node.isText && array[i - 1].sameMarkup(node)) {
if (!joined)
joined = array.slice(0, i);
joined[joined.length - 1] = node.withText(joined[joined.length - 1].text + node.text);
} else if (joined) {
joined.push(node);
}
}
return new Fragment(joined || array, size);
}
/**
Create a fragment from something that can be interpreted as a
set of nodes. For `null`, it returns the empty fragment. For a
fragment, the fragment itself. For a node or array of nodes, a
fragment containing those nodes.
*/
static from(nodes) {
if (!nodes)
return Fragment.empty;
if (nodes instanceof Fragment)
return nodes;
if (Array.isArray(nodes))
return this.fromArray(nodes);
if (nodes.attrs)
return new Fragment([nodes], nodes.nodeSize);
throw new RangeError("Can not convert " + nodes + " to a Fragment" + (nodes.nodesBetween ? " (looks like multiple versions of prosemirror-model were loaded)" : ""));
}
}
Fragment.empty = new Fragment([], 0);
const found = { index: 0, offset: 0 };
function retIndex(index, offset) {
found.index = index;
found.offset = offset;
return found;
}
function compareDeep(a, b) {
if (a === b)
return true;
if (!(a && typeof a == "object") || !(b && typeof b == "object"))
return false;
let array = Array.isArray(a);
if (Array.isArray(b) != array)
return false;
if (array) {
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; i++)
if (!compareDeep(a[i], b[i]))
return false;
} else {
for (let p in a)
if (!(p in b) || !compareDeep(a[p], b[p]))
return false;
for (let p in b)
if (!(p in a))
return false;
}
return true;
}
let Mark$1 = class Mark {
/**
@internal
*/
constructor(type, attrs) {
this.type = type;
this.attrs = attrs;
}
/**
Given a set of marks, create a new set which contains this one as
well, in the right position. If this mark is already in the set,
the set itself is returned. If any marks that are set to be
[exclusive](https://prosemirror.net/docs/ref/#model.MarkSpec.excludes) with this mark are present,
those are replaced by this one.
*/
addToSet(set) {
let copy2, placed = false;
for (let i = 0; i < set.length; i++) {
let other = set[i];
if (this.eq(other))
return set;
if (this.type.excludes(other.type)) {
if (!copy2)
copy2 = set.slice(0, i);
} else if (other.type.excludes(this.type)) {
return set;
} else {
if (!placed && other.type.rank > this.type.rank) {
if (!copy2)
copy2 = set.slice(0, i);
copy2.push(this);
placed = true;
}
if (copy2)
copy2.push(other);
}
}
if (!copy2)
copy2 = set.slice();
if (!placed)
copy2.push(this);
return copy2;
}
/**
Remove this mark from the given set, returning a new set. If this
mark is not in the set, the set itself is returned.
*/
removeFromSet(set) {
for (let i = 0; i < set.length; i++)
if (this.eq(set[i]))
return set.slice(0, i).concat(set.slice(i + 1));
return set;
}
/**
Test whether this mark is in the given set of marks.
*/
isInSet(set) {
for (let i = 0; i < set.length; i++)
if (this.eq(set[i]))
return true;
return false;
}
/**
Test whether this mark has the same type and attributes as
another mark.
*/
eq(other) {
return this == other || this.type == other.type && compareDeep(this.attrs, other.attrs);
}
/**
Convert this mark to a JSON-serializeable representation.
*/
toJSON() {
let obj = { type: this.type.name };
for (let _ in this.attrs) {
obj.attrs = this.attrs;
break;
}
return obj;
}
/**
Deserialize a mark from JSON.
*/
static fromJSON(schema, json) {
if (!json)
throw new RangeError("Invalid input for Mark.fromJSON");
let type = schema.marks[json.type];
if (!type)
throw new RangeError(`There is no mark type ${json.type} in this schema`);
return type.create(json.attrs);
}
/**
Test whether two sets of marks are identical.
*/
static sameSet(a, b) {
if (a == b)
return true;
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; i++)
if (!a[i].eq(b[i]))
return false;
return true;
}
/**
Create a properly sorted mark set from null, a single mark, or an
unsorted array of marks.
*/
static setFrom(marks) {
if (!marks || Array.isArray(marks) && marks.length == 0)
return Mark.none;
if (marks instanceof Mark)
return [marks];
let copy2 = marks.slice();
copy2.sort((a, b) => a.type.rank - b.type.rank);
return copy2;
}
};
Mark$1.none = [];
class ReplaceError extends Error {
}
class Slice {
/**
Create a slice. When specifying a non-zero open depth, you must
make sure that there are nodes of at least that depth at the
appropriate side of the fragment—i.e. if the fragment is an
empty paragraph node, `openStart` and `openEnd` can't be greater
than 1.
It is not necessary for the content of open nodes to conform to
the schema's content constraints, though it should be a valid
start/end/middle for such a node, depending on which sides are
open.
*/
constructor(content, openStart, openEnd) {
this.content = content;
this.openStart = openStart;
this.openEnd = openEnd;
}
/**
The size this slice would add when inserted into a document.
*/
get size() {
return this.content.size - this.openStart - this.openEnd;
}
/**
@internal
*/
insertAt(pos, fragment) {
let content = insertInto(this.content, pos + this.openStart, fragment);
return content && new Slice(content, this.openStart, this.openEnd);
}
/**
@internal
*/
removeBetween(from2, to) {
return new Slice(removeRange(this.content, from2 + this.openStart, to + this.openStart), this.openStart, this.openEnd);
}
/**
Tests whether this slice is equal to another slice.
*/
eq(other) {
return this.content.eq(other.content) && this.openStart == other.openStart && this.openEnd == other.openEnd;
}
/**
@internal
*/
toString() {
return this.content + "(" + this.openStart + "," + this.openEnd + ")";
}
/**
Convert a slice to a JSON-serializable representation.
*/
toJSON() {
if (!this.content.size)
return null;
let json = { content: this.content.toJSON() };
if (this.openStart > 0)
json.openStart = this.openStart;
if (this.openEnd > 0)
json.openEnd = this.openEnd;
return json;
}
/**
Deserialize a slice from its JSON representation.
*/
static fromJSON(schema, json) {
if (!json)
return Slice.empty;
let openStart = json.openStart || 0, openEnd = json.openEnd || 0;
if (typeof openStart != "number" || typeof openEnd != "number")
throw new RangeError("Invalid input for Slice.fromJSON");
return new Slice(Fragment.fromJSON(schema, json.content), openStart, openEnd);
}
/**
Create a slice from a fragment by taking the maximum possible
open value on both side of the fragment.
*/
static maxOpen(fragment, openIsolating = true) {
let openStart = 0, openEnd = 0;
for (let n = fragment.firstChild; n && !n.isLeaf && (openIsolating || !n.type.spec.isolating); n = n.firstChild)
openStart++;
for (let n = fragment.lastChild; n && !n.isLeaf && (openIsolating || !n.type.spec.isolating); n = n.lastChild)
openEnd++;
return new Slice(fragment, openStart, openEnd);
}
}
Slice.empty = new Slice(Fragment.empty, 0, 0);
function removeRange(content, from2, to) {
let { index, offset } = content.findIndex(from2), child = content.maybeChild(index);
let { index: indexTo, offset: offsetTo } = content.findIndex(to);
if (offset == from2 || child.isText) {
if (offsetTo != to && !content.child(indexTo).isText)
throw new RangeError("Removing non-flat range");
return content.cut(0, from2).append(content.cut(to));
}
if (index != indexTo)
throw new RangeError("Removing non-flat range");
return content.replaceChild(index, child.copy(removeRange(child.content, from2 - offset - 1, to - offset - 1)));
}
function insertInto(content, dist, insert, parent) {
let { index, offset } = content.findIndex(dist), child = content.maybeChild(index);
if (offset == dist || child.isText) {
if (parent && !parent.canReplace(index, index, insert))
return null;
return content.cut(0, dist).append(insert).append(content.cut(dist));
}
let inner = insertInto(child.content, dist - offset - 1, insert);
return inner && content.replaceChild(index, child.copy(inner));
}
function replace($from, $to, slice2) {
if (slice2.openStart > $from.depth)
throw new ReplaceError("Inserted content deeper than insertion position");
if ($from.depth - slice2.openStart != $to.depth - slice2.openEnd)
throw new ReplaceError("Inconsistent open depths");
return replaceOuter($from, $to, slice2, 0);
}
function replaceOuter($from, $to, slice2, depth) {
let index = $from.index(depth), node = $from.node(depth);
if (index == $to.index(depth) && depth < $from.depth - slice2.openStart) {
let inner = replaceOuter($from, $to, slice2, depth + 1);
return node.copy(node.content.replaceChild(index, inner));
} else if (!slice2.content.size) {
return close(node, replaceTwoWay($from, $to, depth));
} else if (!slice2.openStart && !slice2.openEnd && $from.depth == depth && $to.depth == depth) {
let parent = $from.parent, content = parent.content;
return close(parent, content.cut(0, $from.parentOffset).append(slice2.content).append(content.cut($to.parentOffset)));
} else {
let { start, end } = prepareSliceForReplace(slice2, $from);
return close(node, replaceThreeWay($from, start, end, $to, depth));
}
}
function checkJoin(main, sub) {
if (!sub.type.compatibleContent(main.type))
throw new ReplaceError("Cannot join " + sub.type.name + " onto " + main.type.name);
}
function joinable$1($before, $after, depth) {
let node = $before.node(depth);
checkJoin(node, $after.node(depth));
return node;
}
function addNode(child, target) {
let last = target.length - 1;
if (last >= 0 && child.isText && child.sameMarkup(target[last]))
target[last] = child.withText(target[last].text + child.text);
else
target.push(child);
}
function addRange($start, $end, depth, target) {
let node = ($end || $start).node(depth);
let startIndex = 0, endIndex = $end ? $end.index(depth) : node.childCount;
if ($start) {
startIndex = $start.index(depth);
if ($start.depth > depth) {
startIndex++;
} else if ($start.textOffset) {
addNode($start.nodeAfter, target);
startIndex++;
}
}
for (let i = startIndex; i < endIndex; i++)
addNode(node.child(i), target);
if ($end && $end.depth == depth && $end.textOffset)
addNode($end.nodeBefore, target);
}
function close(node, content) {
node.type.checkContent(content);
return node.copy(content);
}
function replaceThreeWay($from, $start, $end, $to, depth) {
let openStart = $from.depth > depth && joinable$1($from, $start, depth + 1);
let openEnd = $to.depth > depth && joinable$1($end, $to, depth + 1);
let content = [];
addRange(null, $from, depth, content);
if (openStart && openEnd && $start.index(depth) == $end.index(depth)) {
checkJoin(openStart, openEnd);
addNode(close(openStart, replaceThreeWay($from, $start, $end, $to, depth + 1)), content);
} else {
if (openStart)
addNode(close(openStart, replaceTwoWay($from, $start, depth + 1)), content);
addRange($start, $end, depth, content);
if (openEnd)
addNode(close(openEnd, replaceTwoWay($end, $to, depth + 1)), content);
}
addRange($to, null, depth, content);
return new Fragment(content);
}
function replaceTwoWay($from, $to, depth) {
let content = [];
addRange(null, $from, depth, content);
if ($from.depth > depth) {
let type = joinable$1($from, $to, depth + 1);
addNode(close(type, replaceTwoWay($from, $to, depth + 1)), content);
}
addRange($to, null, depth, content);
return new Fragment(content);
}
function prepareSliceForReplace(slice2, $along) {
let extra = $along.depth - slice2.openStart, parent = $along.node(extra);
let node = parent.copy(slice2.content);
for (let i = extra - 1; i >= 0; i--)
node = $along.node(i).copy(Fragment.from(node));
return {
start: node.resolveNoCache(slice2.openStart + extra),
end: node.resolveNoCache(node.content.size - slice2.openEnd - extra)
};
}
class ResolvedPos {
/**
@internal
*/
constructor(pos, path, parentOffset) {
this.pos = pos;
this.path = path;
this.parentOffset = parentOffset;
this.depth = path.length / 3 - 1;
}
/**
@internal
*/
resolveDepth(val) {
if (val == null)
return this.depth;
if (val < 0)
return this.depth + val;
return val;
}
/**
The parent node that the position points into. Note that even if
a position points into a text node, that node is not considered
the parent—text nodes are ‘flat’ in this model, and have no content.
*/
get parent() {
return this.node(this.depth);
}
/**
The root node in which the position was resolved.
*/
get doc() {
return this.node(0);
}
/**
The ancestor node at the given level. `p.node(p.depth)` is the
same as `p.parent`.
*/
node(depth) {
return this.path[this.resolveDepth(depth) * 3];
}
/**
The index into the ancestor at the given level. If this points
at the 3rd node in the 2nd paragraph on the top level, for
example, `p.index(0)` is 1 and `p.index(1)` is 2.
*/
index(depth) {
return this.path[this.resolveDepth(depth) * 3 + 1];
}
/**
The index pointing after this position into the ancestor at the
given level.
*/
indexAfter(depth) {
depth = this.resolveDepth(depth);
return this.index(depth) + (depth == this.depth && !this.textOffset ? 0 : 1);
}
/**
The (absolute) position at the start of the node at the given
level.
*/
start(depth) {
depth = this.resolveDepth(depth);
return depth == 0 ? 0 : this.path[depth * 3 - 1] + 1;
}
/**
The (absolute) position at the end of the node at the given
level.
*/
end(depth) {
depth = this.resolveDepth(depth);
return this.start(depth) + this.node(depth).content.size;
}
/**
The (absolute) position directly before the wrapping node at the
given level, or, when `depth` is `this.depth + 1`, the original
position.
*/
before(depth) {
depth = this.resolveDepth(depth);
if (!depth)
throw new RangeError("There is no position before the top-level node");
return depth == this.depth + 1 ? this.pos : this.path[depth * 3 - 1];
}
/**
The (absolute) position directly after the wrapping node at the
given level, or the original position when `depth` is `this.depth + 1`.
*/
after(depth) {
depth = this.resolveDepth(depth);
if (!depth)
throw new RangeError("There is no position after the top-level node");
return depth == this.depth + 1 ? this.pos : this.path[depth * 3 - 1] + this.path[depth * 3].nodeSize;
}
/**
When this position points into a text node, this returns the
distance between the position and the start of the text node.
Will be zero for positions that point between nodes.
*/
get textOffset() {
return this.pos - this.path[this.path.length - 1];
}
/**
Get the node directly after the position, if any. If the position
points into a text node, only the part of that node after the
position is returned.
*/
get nodeAfter() {
let parent = this.parent, index = this.index(this.depth);
if (index == parent.childCount)
return null;
let dOff = this.pos - this.path[this.path.length - 1], child = parent.child(index);
return dOff ? parent.child(index).cut(dOff) : child;
}
/**
Get the node directly before the position, if any. If the
position points into a text node, only the part of that node
before the position is returned.
*/
get nodeBefore() {
let index = this.index(this.depth);
let dOff = this.pos - this.path[this.path.length - 1];
if (dOff)
return this.parent.child(index).cut(0, dOff);
return index == 0 ? null : this.parent.child(index - 1);
}
/**
Get the position at the given index in the parent node at the
given depth (which defaults to `this.depth`).
*/
posAtIndex(index, depth) {
depth = this.resolveDepth(depth);
let node = this.path[depth * 3], pos = depth == 0 ? 0 : this.path[depth * 3 - 1] + 1;
for (let i = 0; i < index; i++)
pos += node.child(i).nodeSize;
return pos;
}
/**
Get the marks at this position, factoring in the surrounding
marks' [`inclusive`](https://prosemirror.net/docs/ref/#model.MarkSpec.inclusive) property. If the
position is at the start of a non-empty node, the marks of the
node after it (if any) are returned.
*/
marks() {
let parent = this.parent, index = this.index();
if (parent.content.size == 0)
return Mark$1.none;
if (this.textOffset)
return parent.child(index).marks;
let main = parent.maybeChild(index - 1), other = parent.maybeChild(index);
if (!main) {
let tmp = main;
main = other;
other = tmp;
}
let marks = main.marks;
for (var i = 0; i < marks.length; i++)
if (marks[i].type.spec.inclusive === false && (!other || !marks[i].isInSet(other.marks)))
marks = marks[i--].removeFromSet(marks);
return marks;
}
/**
Get the marks after the current position, if any, except those
that are non-inclusive and not present at position `$end`. This
is mostly useful for getting the set of marks to preserve after a
deletion. Will return `null` if this position is at the end of
its parent node or its parent node isn't a textblock (in which
case no marks should be preserved).
*/
marksAcross($end) {
let after = this.parent.maybeChild(this.index());
if (!after || !after.isInline)
return null;
let marks = after.marks, next = $end.parent.maybeChild($end.index());
for (var i = 0; i < marks.length; i++)
if (marks[i].type.spec.inclusive === false && (!next || !marks[i].isInSet(next.marks)))
marks = marks[i--].removeFromSet(marks);
return marks;
}
/**
The depth up to which this position and the given (non-resolved)
position share the same parent nodes.
*/
sharedDepth(pos) {
for (let depth = this.depth; depth > 0; depth--)
if (this.start(depth) <= pos && this.end(depth) >= pos)
return depth;
return 0;
}
/**
Returns a range based on the place where this position and the
given position diverge around block content. If both point into
the same textblock, for example, a range around that textblock
will be returned. If they point into different blocks, the range
around those blocks in their shared ancestor is returned. You can
pass in an optional predicate that will be called with a parent
node to see if a range into that parent is acceptable.
*/
blockRange(other = this, pred) {
if (other.pos < this.pos)
return other.blockRange(this);
for (let d = this.depth - (this.parent.inlineContent || this.pos == other.pos ? 1 : 0); d >= 0; d--)
if (other.pos <= this.end(d) && (!pred || pred(this.node(d))))
return new NodeRange(this, other, d);
return null;
}
/**
Query whether the given position shares the same parent node.
*/
sameParent(other) {
return this.pos - this.parentOffset == other.pos - other.parentOffset;
}
/**
Return the greater of this and the given position.
*/
max(other) {
return other.pos > this.pos ? other : this;
}
/**
Return the smaller of this and the given position.
*/
min(other) {
return other.pos < this.pos ? other : this;
}
/**
@internal
*/
toString() {
let str = "";
for (let i = 1; i <= this.depth; i++)
str += (str ? "/" : "") + this.node(i).type.name + "_" + this.index(i - 1);
return str + ":" + this.parentOffset;
}
/**
@internal
*/
static resolve(doc2, pos) {
if (!(pos >= 0 && pos <= doc2.content.size))
throw new RangeError("Position " + pos + " out of range");
let path = [];
let start = 0, parentOffset = pos;
for (let node = doc2; ; ) {
let { index, offset } = node.content.findIndex(parentOffset);
let rem = parentOffset - offset;
path.push(node, index, start + offset);
if (!rem)
break;
node = node.child(index);
if (node.isText)
break;
parentOffset = rem - 1;
start += offset + 1;
}
return new ResolvedPos(pos, path, parentOffset);
}
/**
@internal
*/
static resolveCached(doc2, pos) {
for (let i = 0; i < resolveCache.length; i++) {
let cached = resolveCache[i];
if (cached.pos == pos && cached.doc == doc2)
return cached;
}
let result = resolveCache[resolveCachePos] = ResolvedPos.resolve(doc2, pos);
resolveCachePos = (resolveCachePos + 1) % resolveCacheSize;
return result;
}
}
let resolveCache = [], resolveCachePos = 0, resolveCacheSize = 12;
class NodeRange {
/**
Construct a node range. `$from` and `$to` should point into the
same node until at least the given `depth`, since a node range
denotes an adjacent set of nodes in a single parent node.
*/
constructor($from, $to, depth) {
this.$from = $from;
this.$to = $to;
this.depth = depth;
}
/**
The position at the start of the range.
*/
get start() {
return this.$from.before(this.depth + 1);
}
/**
The position at the end of the range.
*/
get end() {
return this.$to.after(this.depth + 1);
}
/**
The parent node that the range points into.
*/
get parent() {
return this.$from.node(this.depth);
}
/**
The start index of the range in the parent node.
*/
get startIndex() {
return this.$from.index(this.depth);
}
/**
The end index of the range in the parent node.
*/
get endIndex() {
return this.$to.indexAfter(this.depth);
}
}
const emptyAttrs = /* @__PURE__ */ Object.create(null);
let Node$1 = class Node {
/**
@internal
*/
constructor(type, attrs, content, marks = Mark$1.none) {
this.type = type;
this.attrs = attrs;
this.marks = marks;
this.content = content || Fragment.empty;
}
/**
The size of this node, as defined by the integer-based [indexing
scheme](/docs/guide/#doc.indexing). For text nodes, this is the
amount of characters. For other leaf nodes, it is one. For
non-leaf nodes, it is the size of the content plus two (the
start and end token).
*/
get nodeSize() {
return this.isLeaf ? 1 : 2 + this.content.size;
}
/**
The number of children that the node has.
*/
get childCount() {
return this.content.childCount;
}
/**
Get the child node at the given index. Raises an error when the
index is out of range.
*/
child(index) {
return this.content.child(index);
}
/**
Get the child node at the given index, if it exists.
*/
maybeChild(index) {
return this.content.maybeChild(index);
}
/**
Call `f` for every child node, passing the node, its offset
into this parent node, and its index.
*/
forEach(f) {
this.content.forEach(f);
}
/**
Invoke a callback for all descendant nodes recursively between
the given two positions that are relative to start of this
node's content. The callback is invoked with the node, its
parent-relative position, its parent node, and its child index.
When the callback returns false for a given node, that node's
children will not be recursed over. The last parameter can be
used to specify a starting position to count from.
*/
nodesBetween(from2, to, f, startPos = 0) {
this.content.nodesBetween(from2, to, f, startPos, this);
}
/**
Call the given callback for every descendant node. Doesn't
descend into a node when the callback returns `false`.
*/
descendants(f) {
this.nodesBetween(0, this.content.size, f);
}
/**
Concatenates all the text nodes found in this fragment and its
children.
*/
get textContent() {
return this.isLeaf && this.type.spec.leafText ? this.type.spec.leafText(this) : this.textBetween(0, this.content.size, "");
}
/**
Get all text between positions `from` and `to`. When
`blockSeparator` is given, it will be inserted to separate text
from different block nodes. If `leafText` is given, it'll be
inserted for every non-text leaf node encountered, otherwise
*/
textBetween(from2, to, blockSeparator, leafText) {
return this.content.textBetween(from2, to, blockSeparator, leafText);
}
/**
Returns this node's first child, or `null` if there are no
children.
*/
get firstChild() {
return this.content.firstChild;
}
/**
Returns this node's last child, or `null` if there are no
children.
*/
get lastChild() {
return this.content.lastChild;
}
/**
Test whether two nodes represent the same piece of document.
*/
eq(other) {
return this == other || this.sameMarkup(other) && this.content.eq(other.content);
}
/**
Compare the markup (type, attributes, and marks) of this node to
those of another. Returns `true` if both have the same markup.
*/
sameMarkup(other) {
return this.hasMarkup(other.type, other.attrs, other.marks);
}
/**
Check whether this node's markup correspond to the given type,
attributes, and marks.
*/
hasMarkup(type, attrs, marks) {
return this.type == type && compareDeep(this.attrs, attrs || type.defaultAttrs || emptyAttrs) && Mark$1.sameSet(this.marks, marks || Mark$1.none);
}
/**
Create a new node with the same markup as this node, containing
the given content (or empty, if no content is given).
*/
copy(content = null) {
if (content == this.content)
return this;
return new Node(this.type, this.attrs, content, this.marks);
}
/**
Create a copy of this node, with the given set of marks instead
of the node's own marks.
*/
mark(marks) {
return marks == this.marks ? this : new Node(this.type, this.attrs, this.content, marks);
}
/**
Create a copy of this node with only the content between the
given positions. If `to` is not given, it defaults to the end of
the node.
*/
cut(from2, to = this.content.size) {
if (from2 == 0 && to == this.content.size)
return this;
return this.copy(this.content.cut(from2, to));
}
/**
Cut out the part of the document between the given positions, and
return it as a `Slice` object.
*/
slice(from2, to = this.content.size, includeParents = false) {
if (from2 == to)
return Slice.empty;
let $from = this.resolve(from2), $to = this.resolve(to);
let depth = includeParents ? 0 : $from.sharedDepth(to);
let start = $from.start(depth), node = $from.node(depth);
let content = node.content.cut($from.pos - start, $to.pos - start);
return new Slice(content, $from.depth - depth, $to.depth - depth);
}
/**
Replace the part of the document between the given positions with
the given slice. The slice must 'fit', meaning its open sides
must be able to connect to the surrounding content, and its
content nodes must be valid children for the node they are placed
into. If any of this is violated, an error of type
*/
replace(from2, to, slice2) {
return replace(this.resolve(from2), this.resolve(to), slice2);
}
/**
Find the node directly after the given position.
*/
nodeAt(pos) {
for (let node = this; ; ) {
let { index, offset } = node.content.findIndex(pos);
node = node.maybeChild(index);
if (!node)
return null;
if (offset == pos || node.isText)
return node;
pos -= offset + 1;
}
}
/**
Find the (direct) child node after the given offset, if any,
and return it along with its index and offset relative to this
node.
*/
childAfter(pos) {
let { index, offset } = this.content.findIndex(pos);
return { node: this.content.maybeChild(index), index, offset };
}
/**
Find the (direct) child node before the given offset, if any,
and return it along with its index and offset relative to this
node.
*/
childBefore(pos) {
if (pos == 0)
return { node: null, index: 0, offset: 0 };
let { index, offset } = this.content.findIndex(pos);
if (offset < pos)
return { node: this.content.child(index), index, offset };
let node = this.content.child(index - 1);
return { node, index: index - 1, offset: offset - node.nodeSize };
}
/**
Resolve the given position in the document, returning an
[object](https://prosemirror.net/docs/ref/#model.ResolvedPos) with information about its context.
*/
resolve(pos) {
return ResolvedPos.resolveCached(this, pos);
}
/**
@internal
*/
resolveNoCache(pos) {
return ResolvedPos.resolve(this, pos);
}
/**
Test whether a given mark or mark type occurs in this document
between the two given positions.
*/
rangeHasMark(from2, to, type) {
let found2 = false;
if (to > from2)
this.nodesBetween(from2, to, (node) => {
if (type.isInSet(node.marks))
found2 = true;
return !found2;
});
return found2;
}
/**
True when this is a block (non-inline node)
*/
get isBlock() {
return this.type.isBlock;
}
/**
True when this is a textblock node, a block node with inline
content.
*/
get isTextblock() {
return this.type.isTextblock;
}
/**
True when this node allows inline content.
*/
get inlineContent() {
return this.type.inlineContent;
}
/**
True when this is an inline node (a text node or a node that can
appear among text).
*/
get isInline() {
return this.type.isInline;
}
/**
True when this is a text node.
*/
get isText() {
return this.type.isText;
}
/**
True when this is a leaf node.
*/
get isLeaf() {
return this.type.isLeaf;
}
/**
True when this is an atom, i.e. when it does not have directly
editable content. This is usually the same as `isLeaf`, but can
be configured with the [`atom` property](https://prosemirror.net/docs/ref/#model.NodeSpec.atom)
on a node's spec (typically used when the node is displayed as
*/
get isAtom() {
return this.type.isAtom;
}
/**
Return a string representation of this node for debugging
purposes.
*/
toString() {
if (this.type.spec.toDebugString)
return this.type.spec.toDebugString(this);
let name = this.type.name;
if (this.content.size)
name += "(" + this.content.toStringInner() + ")";
return wrapMarks(this.marks, name);
}
/**
Get the content match in this node at the given index.
*/
contentMatchAt(index) {
let match = this.type.contentMatch.matchFragment(this.content, 0, index);
if (!match)
throw new Error("Called contentMatchAt on a node with invalid content");
return match;
}
/**
Test whether replacing the range between `from` and `to` (by
child index) with the given replacement fragment (which defaults
to the empty fragment) would leave the node's content valid. You
can optionally pass `start` and `end` indices into the
replacement fragment.
*/
canReplace(from2, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
let one = this.contentMatchAt(from2).matchFragment(replacement, start, end);
let two = one && one.matchFragment(this.content, to);
if (!two || !two.validEnd)
return false;
for (let i = start; i < end; i++)
if (!this.type.allowsMarks(replacement.child(i).marks))
return false;
return true;
}
/**
Test whether replacing the range `from` to `to` (by index) with
a node of the given type would leave the node's content valid.
*/
canReplaceWith(from2, to, type, marks) {
if (marks && !this.type.allowsMarks(marks))
return false;
let start = this.contentMatchAt(from2).matchType(type);
let end = start && start.matchFragment(this.content, to);
return end ? end.validEnd : false;
}
/**
Test whether the given node's content could be appended to this
node. If that node is empty, this will only return true if there
is at least one node type that can appear in both nodes (to avoid
merging completely incompatible nodes).
*/
canAppend(other) {
if (other.content.size)
return this.canReplace(this.childCount, this.childCount, other.content);
else
return this.type.compatibleContent(other.type);
}
/**
Check whether this node and its descendants conform to the
schema, and raise error when they do not.
*/
check() {
this.type.checkContent(this.content);
let copy2 = Mark$1.none;
for (let i = 0; i < this.marks.length; i++)
copy2 = this.marks[i].addToSet(copy2);
if (!Mark$1.sameSet(copy2, this.marks))
throw new RangeError(`Invalid collection of marks for node ${this.type.name}: ${this.marks.map((m) => m.type.name)}`);
this.content.forEach((node) => node.check());
}
/**
Return a JSON-serializeable representation of this node.
*/
toJSON() {
let obj = { type: this.type.name };
for (let _ in this.attrs) {
obj.attrs = this.attrs;
break;
}
if (this.content.size)
obj.content = this.content.toJSON();
if (this.marks.length)
obj.marks = this.marks.map((n) => n.toJSON());
return obj;
}
/**
Deserialize a node from its JSON representation.
*/
static fromJSON(schema, json) {
if (!json)
throw new RangeError("Invalid input for Node.fromJSON");
let marks = null;
if (json.marks) {
if (!Array.isArray(json.marks))
throw new RangeError("Invalid mark data for Node.fromJSON");
marks = json.marks.map(schema.markFromJSON);
}
if (json.type == "text") {
if (typeof json.text != "string")
throw new RangeError("Invalid text node in JSON");
return schema.text(json.text, marks);
}
let content = Fragment.fromJSON(schema, json.content);
return schema.nodeType(json.type).create(json.attrs, content, marks);
}
};
Node$1.prototype.text = void 0;
class TextNode extends Node$1 {
/**
@internal
*/
constructor(type, attrs, content, marks) {
super(type, attrs, null, marks);
if (!content)
throw new RangeError("Empty text nodes are not allowed");
this.text = content;
}
toString() {
if (this.type.spec.toDebugString)
return this.type.spec.toDebugString(this);
return wrapMarks(this.marks, JSON.stringify(this.text));
}
get textContent() {
return this.text;
}
textBetween(from2, to) {
return this.text.slice(from2, to);
}
get nodeSize() {
return this.text.length;
}
mark(marks) {
return marks == this.marks ? this : new TextNode(this.type, this.attrs, this.text, marks);
}
withText(text2) {
if (text2 == this.text)
return this;
return new TextNode(this.type, this.attrs, text2, this.marks);
}
cut(from2 = 0, to = this.text.length) {
if (from2 == 0 && to == this.text.length)
return this;
return this.withText(this.text.slice(from2, to));
}
eq(other) {
return this.sameMarkup(other) && this.text == other.text;
}
toJSON() {
let base2 = super.toJSON();
base2.text = this.text;
return base2;
}
}
function wrapMarks(marks, str) {
for (let i = marks.length - 1; i >= 0; i--)
str = marks[i].type.name + "(" + str + ")";
return str;
}
class ContentMatch {
/**
@internal
*/
constructor(validEnd) {
this.validEnd = validEnd;
this.next = [];
this.wrapCache = [];
}
/**
@internal
*/
static parse(string, nodeTypes) {
let stream = new TokenStream(string, nodeTypes);
if (stream.next == null)
return ContentMatch.empty;
let expr = parseExpr(stream);
if (stream.next)
stream.err("Unexpected trailing text");
let match = dfa(nfa(expr));
checkForDeadEnds(match, stream);
return match;
}
/**
Match a node type, returning a match after that node if
successful.
*/
matchType(type) {
for (let i = 0; i < this.next.length; i++)
if (this.next[i].type == type)
return this.next[i].next;
return null;
}
/**
Try to match a fragment. Returns the resulting match when
successful.
*/
matchFragment(frag, start = 0, end = frag.childCount) {
let cur = this;
for (let i = start; cur && i < end; i++)
cur = cur.matchType(frag.child(i).type);
return cur;
}
/**
@internal
*/
get inlineContent() {
return this.next.length != 0 && this.next[0].type.isInline;
}
/**
Get the first matching node type at this match position that can
be generated.
*/
get defaultType() {
for (let i = 0; i < this.next.length; i++) {
let { type } = this.next[i];
if (!(type.isText || type.hasRequiredAttrs()))
return type;
}
return null;
}
/**
@internal
*/
compatible(other) {
for (let i = 0; i < this.next.length; i++)
for (let j = 0; j < other.next.length; j++)
if (this.next[i].type == other.next[j].type)
return true;
return false;
}
/**
Try to match the given fragment, and if that fails, see if it can
be made to match by inserting nodes in front of it. When
successful, return a fragment of inserted nodes (which may be
empty if nothing had to be inserted). When `toEnd` is true, only
return a fragment if the resulting match goes to the end of the
content expression.
*/
fillBefore(after, toEnd = false, startIndex = 0) {
let seen = [this];
function search(match, types) {
let finished = match.matchFragment(after, startIndex);
if (finished && (!toEnd || finished.validEnd))
return Fragment.from(types.map((tp) => tp.createAndFill()));
for (let i = 0; i < match.next.length; i++) {
let { type, next } = match.next[i];
if (!(type.isText || type.hasRequiredAttrs()) && seen.indexOf(next) == -1) {
seen.push(next);
let found2 = search(next, types.concat(type));
if (found2)
return found2;
}
}
return null;
}
return search(this, []);
}
/**
Find a set of wrapping node types that would allow a node of the
given type to appear at this position. The result may be empty
(when it fits directly) and will be null when no such wrapping
exists.
*/
findWrapping(target) {
for (let i = 0; i < this.wrapCache.length; i += 2)
if (this.wrapCache[i] == target)
return this.wrapCache[i + 1];
let computed = this.computeWrapping(target);
this.wrapCache.push(target, computed);
return computed;
}
/**
@internal
*/
computeWrapping(target) {
let seen = /* @__PURE__ */ Object.create(null), active = [{ match: this, type: null, via: null }];
while (active.length) {
let current = active.shift(), match = current.match;
if (match.matchType(target)) {
let result = [];
for (let obj = current; obj.type; obj = obj.via)
result.push(obj.type);
return result.reverse();
}
for (let i = 0; i < match.next.length; i++) {
let { type, next } = match.next[i];
if (!type.isLeaf && !type.hasRequiredAttrs() && !(type.name in seen) && (!current.type || next.validEnd)) {
active.push({ match: type.contentMatch, type, via: current });
seen[type.name] = true;
}
}
}
return null;
}
/**
The number of outgoing edges this node has in the finite
automaton that describes the content expression.
*/
get edgeCount() {
return this.next.length;
}
/**
Get the _n_​th outgoing edge from this node in the finite
automaton that describes the content expression.
*/
edge(n) {
if (n >= this.next.length)
throw new RangeError(`There's no ${n}th edge in this content match`);
return this.next[n];
}
/**
@internal
*/
toString() {
let seen = [];
function scan(m) {
seen.push(m);
for (let i = 0; i < m.next.length; i++)
if (seen.indexOf(m.next[i].next) == -1)
scan(m.next[i].next);
}
scan(this);
return seen.map((m, i) => {
let out = i + (m.validEnd ? "*" : " ") + " ";
for (let i2 = 0; i2 < m.next.length; i2++)
out += (i2 ? ", " : "") + m.next[i2].type.name + "->" + seen.indexOf(m.next[i2].next);
return out;
}).join("\n");
}
}
ContentMatch.empty = new ContentMatch(true);
class TokenStream {
constructor(string, nodeTypes) {
this.string = string;
this.nodeTypes = nodeTypes;
this.inline = null;
this.pos = 0;
this.tokens = string.split(/\s*(?=\b|\W|$)/);
if (this.tokens[this.tokens.length - 1] == "")
this.tokens.pop();
if (this.tokens[0] == "")
this.tokens.shift();
}
get next() {
return this.tokens[this.pos];
}
eat(tok) {
return this.next == tok && (this.pos++ || true);
}
err(str) {
throw new SyntaxError(str + " (in content expression '" + this.string + "')");
}
}
function parseExpr(stream) {
let exprs = [];
do {
exprs.push(parseExprSeq(stream));
} while (stream.eat("|"));
return exprs.length == 1 ? exprs[0] : { type: "choice", exprs };
}
function parseExprSeq(stream) {
let exprs = [];
do {
exprs.push(parseExprSubscript(stream));
} while (stream.next && stream.next != ")" && stream.next != "|");
return exprs.length == 1 ? exprs[0] : { type: "seq", exprs };
}
function parseExprSubscript(stream) {
let expr = parseExprAtom(stream);
for (; ; ) {
if (stream.eat("+"))
expr = { type: "plus", expr };
else if (stream.eat("*"))
expr = { type: "star", expr };
else if (stream.eat("?"))
expr = { type: "opt", expr };
else if (stream.eat("{"))
expr = parseExprRange(stream, expr);
else
break;
}
return expr;
}
function parseNum(stream) {
if (/\D/.test(stream.next))
stream.err("Expected number, got '" + stream.next + "'");
let result = Number(stream.next);
stream.pos++;
return result;
}
function parseExprRange(stream, expr) {
let min = parseNum(stream), max = min;
if (stream.eat(",")) {
if (stream.next != "}")
max = parseNum(stream);
else
max = -1;
}
if (!stream.eat("}"))
stream.err("Unclosed braced range");
return { type: "range", min, max, expr };
}
function resolveName(stream, name) {
let types = stream.nodeTypes, type = types[name];
if (type)
return [type];
let result = [];
for (let typeName in types) {
let type2 = types[typeName];
if (type2.groups.indexOf(name) > -1)
result.push(type2);
}
if (result.length == 0)
stream.err("No node type or group '" + name + "' found");
return result;
}
function parseExprAtom(stream) {
if (stream.eat("(")) {
let expr = parseExpr(stream);
if (!stream.eat(")"))
stream.err("Missing closing paren");
return expr;
} else if (!/\W/.test(stream.next)) {
let exprs = resolveName(stream, stream.next).map((type) => {
if (stream.inline == null)
stream.inline = type.isInline;
else if (stream.inline != type.isInline)
stream.err("Mixing inline and block content");
return { type: "name", value: type };
});
stream.pos++;
return exprs.length == 1 ? exprs[0] : { type: "choice", exprs };
} else {
stream.err("Unexpected token '" + stream.next + "'");
}
}
function nfa(expr) {
let nfa2 = [[]];
connect(compile(expr, 0), node());
return nfa2;
function node() {
return nfa2.push([]) - 1;
}
function edge(from2, to, term) {
let edge2 = { term, to };
nfa2[from2].push(edge2);
return edge2;
}
function connect(edges, to) {
edges.forEach((edge2) => edge2.to = to);
}
function compile(expr2, from2) {
if (expr2.type == "choice") {
return expr2.exprs.reduce((out, expr3) => out.concat(compile(expr3, from2)), []);
} else if (expr2.type == "seq") {
for (let i = 0; ; i++) {
let next = compile(expr2.exprs[i], from2);
if (i == expr2.exprs.length - 1)
return next;
connect(next, from2 = node());
}
} else if (expr2.type == "star") {
let loop = node();
edge(from2, loop);
connect(compile(expr2.expr, loop), loop);
return [edge(loop)];
} else if (expr2.type == "plus") {
let loop = node();
connect(compile(expr2.expr, from2), loop);
connect(compile(expr2.expr, loop), loop);
return [edge(loop)];
} else if (expr2.type == "opt") {
return [edge(from2)].concat(compile(expr2.expr, from2));
} else if (expr2.type == "range") {
let cur = from2;
for (let i = 0; i < expr2.min; i++) {
let next = node();
connect(compile(expr2.expr, cur), next);
cur = next;
}
if (expr2.max == -1) {
connect(compile(expr2.expr, cur), cur);
} else {
for (let i = expr2.min; i < expr2.max; i++) {
let next = node();
edge(cur, next);
connect(compile(expr2.expr, cur), next);
cur = next;
}
}
return [edge(cur)];
} else if (expr2.type == "name") {
return [edge(from2, void 0, expr2.value)];
} else {
throw new Error("Unknown expr type");
}
}
}
function cmp(a, b) {
return b - a;
}
function nullFrom(nfa2, node) {
let result = [];
scan(node);
return result.sort(cmp);
function scan(node2) {
let edges = nfa2[node2];
if (edges.length == 1 && !edges[0].term)
return scan(edges[0].to);
result.push(node2);
for (let i = 0; i < edges.length; i++) {
let { term, to } = edges[i];
if (!term && result.indexOf(to) == -1)
scan(to);
}
}
}
function dfa(nfa2) {
let labeled = /* @__PURE__ */ Object.create(null);
return explore(nullFrom(nfa2, 0));
function explore(states) {
let out = [];
states.forEach((node) => {
nfa2[node].forEach(({ term, to }) => {
if (!term)
return;
let set;
for (let i = 0; i < out.length; i++)
if (out[i][0] == term)
set = out[i][1];
nullFrom(nfa2, to).forEach((node2) => {
if (!set)
out.push([term, set = []]);
if (set.indexOf(node2) == -1)
set.push(node2);
});
});
});
let state = labeled[states.join(",")] = new ContentMatch(states.indexOf(nfa2.length - 1) > -1);
for (let i = 0; i < out.length; i++) {
let states2 = out[i][1].sort(cmp);
state.next.push({ type: out[i][0], next: labeled[states2.join(",")] || explore(states2) });
}
return state;
}
}
function checkForDeadEnds(match, stream) {
for (let i = 0, work = [match]; i < work.length; i++) {
let state = work[i], dead = !state.validEnd, nodes = [];
for (let j = 0; j < state.next.length; j++) {
let { type, next } = state.next[j];
nodes.push(type.name);
if (dead && !(type.isText || type.hasRequiredAttrs()))
dead = false;
if (work.indexOf(next) == -1)
work.push(next);
}
if (dead)
stream.err("Only non-generatable nodes (" + nodes.join(", ") + ") in a required position (see https://prosemirror.net/docs/guide/#generatable)");
}
}
function defaultAttrs(attrs) {
let defaults = /* @__PURE__ */ Object.create(null);
for (let attrName in attrs) {
let attr = attrs[attrName];
if (!attr.hasDefault)
return null;
defaults[attrName] = attr.default;
}
return defaults;
}
function computeAttrs(attrs, value) {
let built = /* @__PURE__ */ Object.create(null);
for (let name in attrs) {
let given = value && value[name];
if (given === void 0) {
let attr = attrs[name];
if (attr.hasDefault)
given = attr.default;
else
throw new RangeError("No value supplied for attribute " + name);
}
built[name] = given;
}
return built;
}
function initAttrs(attrs) {
let result = /* @__PURE__ */ Object.create(null);
if (attrs)
for (let name in attrs)
result[name] = new Attribute(attrs[name]);
return result;
}
let NodeType$1 = class NodeType {
/**
@internal
*/
constructor(name, schema, spec) {
this.name = name;
this.schema = schema;
this.spec = spec;
this.markSet = null;
this.groups = spec.group ? spec.group.split(" ") : [];
this.attrs = initAttrs(spec.attrs);
this.defaultAttrs = defaultAttrs(this.attrs);
this.contentMatch = null;
this.inlineContent = null;
this.isBlock = !(spec.inline || name == "text");
this.isText = name == "text";
}
/**
True if this is an inline type.
*/
get isInline() {
return !this.isBlock;
}
/**
True if this is a textblock type, a block that contains inline
content.
*/
get isTextblock() {
return this.isBlock && this.inlineContent;
}
/**
True for node types that allow no content.
*/
get isLeaf() {
return this.contentMatch == ContentMatch.empty;
}
/**
True when this node is an atom, i.e. when it does not have
directly editable content.
*/
get isAtom() {
return this.isLeaf || !!this.spec.atom;
}
/**
The node type's [whitespace](https://prosemirror.net/docs/ref/#model.NodeSpec.whitespace) option.
*/
get whitespace() {
return this.spec.whitespace || (this.spec.code ? "pre" : "normal");
}
/**
Tells you whether this node type has any required attributes.
*/
hasRequiredAttrs() {
for (let n in this.attrs)
if (this.attrs[n].isRequired)
return true;
return false;
}
/**
Indicates whether this node allows some of the same content as
the given node type.
*/
compatibleContent(other) {
return this == other || this.contentMatch.compatible(other.contentMatch);
}
/**
@internal
*/
computeAttrs(attrs) {
if (!attrs && this.defaultAttrs)
return this.defaultAttrs;
else
return computeAttrs(this.attrs, attrs);
}
/**
Create a `Node` of this type. The given attributes are
checked and defaulted (you can pass `null` to use the type's
defaults entirely, if no required attributes exist). `content`
may be a `Fragment`, a node, an array of nodes, or
`null`. Similarly `marks` may be `null` to default to the empty
set of marks.
*/
create(attrs = null, content, marks) {
if (this.isText)
throw new Error("NodeType.create can't construct text nodes");
return new Node$1(this, this.computeAttrs(attrs), Fragment.from(content), Mark$1.setFrom(marks));
}
/**
Like [`create`](https://prosemirror.net/docs/ref/#model.NodeType.create), but check the given content
against the node type's content restrictions, and throw an error
if it doesn't match.
*/
createChecked(attrs = null, content, marks) {
content = Fragment.from(content);
this.checkContent(content);
return new Node$1(this, this.computeAttrs(attrs), content, Mark$1.setFrom(marks));
}
/**
Like [`create`](https://prosemirror.net/docs/ref/#model.NodeType.create), but see if it is
necessary to add nodes to the start or end of the given fragment
to make it fit the node. If no fitting wrapping can be found,
return null. Note that, due to the fact that required nodes can
always be created, this will always succeed if you pass null or
`Fragment.empty` as content.
*/
createAndFill(attrs = null, content, marks) {
attrs = this.computeAttrs(attrs);
content = Fragment.from(content);
if (content.size) {
let before = this.contentMatch.fillBefore(content);
if (!before)
return null;
content = before.append(content);
}
let matched = this.contentMatch.matchFragment(content);
let after = matched && matched.fillBefore(Fragment.empty, true);
if (!after)
return null;
return new Node$1(this, attrs, content.append(after), Mark$1.setFrom(marks));
}
/**
Returns true if the given fragment is valid content for this node
type with the given attributes.
*/
validContent(content) {
let result = this.contentMatch.matchFragment(content);
if (!result || !result.validEnd)
return false;
for (let i = 0; i < content.childCount; i++)
if (!this.allowsMarks(content.child(i).marks))
return false;
return true;
}
/**
Throws a RangeError if the given fragment is not valid content for this
node type.
@internal
*/
checkContent(content) {
if (!this.validContent(content))
throw new RangeError(`Invalid content for node ${this.name}: ${content.toString().slice(0, 50)}`);
}
/**
Check whether the given mark type is allowed in this node.
*/
allowsMarkType(markType) {
return this.markSet == null || this.markSet.indexOf(markType) > -1;
}
/**
Test whether the given set of marks are allowed in this node.
*/
allowsMarks(marks) {
if (this.markSet == null)
return true;
for (let i = 0; i < marks.length; i++)
if (!this.allowsMarkType(marks[i].type))
return false;
return true;
}
/**
Removes the marks that are not allowed in this node from the given set.
*/
allowedMarks(marks) {
if (this.markSet == null)
return marks;
let copy2;
for (let i = 0; i < marks.length; i++) {
if (!this.allowsMarkType(marks[i].type)) {
if (!copy2)
copy2 = marks.slice(0, i);
} else if (copy2) {
copy2.push(marks[i]);
}
}
return !copy2 ? marks : copy2.length ? copy2 : Mark$1.none;
}
/**
@internal
*/
static compile(nodes, schema) {
let result = /* @__PURE__ */ Object.create(null);
nodes.forEach((name, spec) => result[name] = new NodeType(name, schema, spec));
let topType = schema.spec.topNode || "doc";
if (!result[topType])
throw new RangeError("Schema is missing its top node type ('" + topType + "')");
if (!result.text)
throw new RangeError("Every schema needs a 'text' type");
for (let _ in result.text.attrs)
throw new RangeError("The text node type should not have attributes");
return result;
}
};
class Attribute {
constructor(options) {
this.hasDefault = Object.prototype.hasOwnProperty.call(options, "default");
this.default = options.default;
}
get isRequired() {
return !this.hasDefault;
}
}
class MarkType {
/**
@internal
*/
constructor(name, rank, schema, spec) {
this.name = name;
this.rank = rank;
this.schema = schema;
this.spec = spec;
this.attrs = initAttrs(spec.attrs);
this.excluded = null;
let defaults = defaultAttrs(this.attrs);
this.instance = defaults ? new Mark$1(this, defaults) : null;
}
/**
Create a mark of this type. `attrs` may be `null` or an object
containing only some of the mark's attributes. The others, if
they have defaults, will be added.
*/
create(attrs = null) {
if (!attrs && this.instance)
return this.instance;
return new Mark$1(this, computeAttrs(this.attrs, attrs));
}
/**
@internal
*/
static compile(marks, schema) {
let result = /* @__PURE__ */ Object.create(null), rank = 0;
marks.forEach((name, spec) => result[name] = new MarkType(name, rank++, schema, spec));
return result;
}
/**
When there is a mark of this type in the given set, a new set
without it is returned. Otherwise, the input set is returned.
*/
removeFromSet(set) {
for (var i = 0; i < set.length; i++)
if (set[i].type == this) {
set = set.slice(0, i).concat(set.slice(i + 1));
i--;
}
return set;
}
/**
Tests whether there is a mark of this type in the given set.
*/
isInSet(set) {
for (let i = 0; i < set.length; i++)
if (set[i].type == this)
return set[i];
}
/**
Queries whether a given mark type is
*/
excludes(other) {
return this.excluded.indexOf(other) > -1;
}
}
class Schema {
/**
Construct a schema from a schema [specification](https://prosemirror.net/docs/ref/#model.SchemaSpec).
*/
constructor(spec) {
this.cached = /* @__PURE__ */ Object.create(null);
let instanceSpec = this.spec = {};
for (let prop in spec)
instanceSpec[prop] = spec[prop];
instanceSpec.nodes = OrderedMap.from(spec.nodes), instanceSpec.marks = OrderedMap.from(spec.marks || {}), this.nodes = NodeType$1.compile(this.spec.nodes, this);
this.marks = MarkType.compile(this.spec.marks, this);
let contentExprCache = /* @__PURE__ */ Object.create(null);
for (let prop in this.nodes) {
if (prop in this.marks)
throw new RangeError(prop + " can not be both a node and a mark");
let type = this.nodes[prop], contentExpr = type.spec.content || "", markExpr = type.spec.marks;
type.contentMatch = contentExprCache[contentExpr] || (contentExprCache[contentExpr] = ContentMatch.parse(contentExpr, this.nodes));
type.inlineContent = type.contentMatch.inlineContent;
type.markSet = markExpr == "_" ? null : markExpr ? gatherMarks(this, markExpr.split(" ")) : markExpr == "" || !type.inlineContent ? [] : null;
}
for (let prop in this.marks) {
let type = this.marks[prop], excl = type.spec.excludes;
type.excluded = excl == null ? [type] : excl == "" ? [] : gatherMarks(this, excl.split(" "));
}
this.nodeFromJSON = this.nodeFromJSON.bind(this);
this.markFromJSON = this.markFromJSON.bind(this);
this.topNodeType = this.nodes[this.spec.topNode || "doc"];
this.cached.wrappings = /* @__PURE__ */ Object.create(null);
}
/**
Create a node in this schema. The `type` may be a string or a
`NodeType` instance. Attributes will be extended with defaults,
`content` may be a `Fragment`, `null`, a `Node`, or an array of
nodes.
*/
node(type, attrs = null, content, marks) {
if (typeof type == "string")
type = this.nodeType(type);
else if (!(type instanceof NodeType$1))
throw new RangeError("Invalid node type: " + type);
else if (type.schema != this)
throw new RangeError("Node type from different schema used (" + type.name + ")");
return type.createChecked(attrs, content, marks);
}
/**
Create a text node in the schema. Empty text nodes are not
allowed.
*/
text(text2, marks) {
let type = this.nodes.text;
return new TextNode(type, type.defaultAttrs, text2, Mark$1.setFrom(marks));
}
/**
Create a mark with the given type and attributes.
*/
mark(type, attrs) {
if (typeof type == "string")
type = this.marks[type];
return type.create(attrs);
}
/**
Deserialize a node from its JSON representation. This method is
bound.
*/
nodeFromJSON(json) {
return Node$1.fromJSON(this, json);
}
/**
Deserialize a mark from its JSON representation. This method is
bound.
*/
markFromJSON(json) {
return Mark$1.fromJSON(this, json);
}
/**
@internal
*/
nodeType(name) {
let found2 = this.nodes[name];
if (!found2)
throw new RangeError("Unknown node type: " + name);
return found2;
}
}
function gatherMarks(schema, marks) {
let found2 = [];
for (let i = 0; i < marks.length; i++) {
let name = marks[i], mark = schema.marks[name], ok = mark;
if (mark) {
found2.push(mark);
} else {
for (let prop in schema.marks) {
let mark2 = schema.marks[prop];
if (name == "_" || mark2.spec.group && mark2.spec.group.split(" ").indexOf(name) > -1)
found2.push(ok = mark2);
}
}
if (!ok)
throw new SyntaxError("Unknown mark type: '" + marks[i] + "'");
}
return found2;
}
class DOMParser {
/**
Create a parser that targets the given schema, using the given
parsing rules.
*/
constructor(schema, rules) {
this.schema = schema;
this.rules = rules;
this.tags = [];
this.styles = [];
rules.forEach((rule) => {
if (rule.tag)
this.tags.push(rule);
else if (rule.style)
this.styles.push(rule);
});
this.normalizeLists = !this.tags.some((r) => {
if (!/^(ul|ol)\b/.test(r.tag) || !r.node)
return false;
let node = schema.nodes[r.node];
return node.contentMatch.matchType(node);
});
}
/**
Parse a document from the content of a DOM node.
*/
parse(dom, options = {}) {
let context = new ParseContext(this, options, false);
context.addAll(dom, options.from, options.to);
return context.finish();
}
/**
Parses the content of the given DOM node, like
[`parse`](https://prosemirror.net/docs/ref/#model.DOMParser.parse), and takes the same set of
options. But unlike that method, which produces a whole node,
this one returns a slice that is open at the sides, meaning that
the schema constraints aren't applied to the start of nodes to
the left of the input and the end of nodes at the end.
*/
parseSlice(dom, options = {}) {
let context = new ParseContext(this, options, true);
context.addAll(dom, options.from, options.to);
return Slice.maxOpen(context.finish());
}
/**
@internal
*/
matchTag(dom, context, after) {
for (let i = after ? this.tags.indexOf(after) + 1 : 0; i < this.tags.length; i++) {
let rule = this.tags[i];
if (matches(dom, rule.tag) && (rule.namespace === void 0 || dom.namespaceURI == rule.namespace) && (!rule.context || context.matchesContext(rule.context))) {
if (rule.getAttrs) {
let result = rule.getAttrs(dom);
if (result === false)
continue;
rule.attrs = result || void 0;
}
return rule;
}
}
}
/**
@internal
*/
matchStyle(prop, value, context, after) {
for (let i = after ? this.styles.indexOf(after) + 1 : 0; i < this.styles.length; i++) {
let rule = this.styles[i], style2 = rule.style;
if (style2.indexOf(prop) != 0 || rule.context && !context.matchesContext(rule.context) || // Test that the style string either precisely matches the prop,
// or has an '=' sign after the prop, followed by the given
// value.
style2.length > prop.length && (style2.charCodeAt(prop.length) != 61 || style2.slice(prop.length + 1) != value))
continue;
if (rule.getAttrs) {
let result = rule.getAttrs(value);
if (result === false)
continue;
rule.attrs = result || void 0;
}
return rule;
}
}
/**
@internal
*/
static schemaRules(schema) {
let result = [];
function insert(rule) {
let priority = rule.priority == null ? 50 : rule.priority, i = 0;
for (; i < result.length; i++) {
let next = result[i], nextPriority = next.priority == null ? 50 : next.priority;
if (nextPriority < priority)
break;
}
result.splice(i, 0, rule);
}
for (let name in schema.marks) {
let rules = schema.marks[name].spec.parseDOM;
if (rules)
rules.forEach((rule) => {
insert(rule = copy(rule));
if (!(rule.mark || rule.ignore || rule.clearMark))
rule.mark = name;
});
}
for (let name in schema.nodes) {
let rules = schema.nodes[name].spec.parseDOM;
if (rules)
rules.forEach((rule) => {
insert(rule = copy(rule));
if (!(rule.node || rule.ignore || rule.mark))
rule.node = name;
});
}
return result;
}
/**
Construct a DOM parser using the parsing rules listed in a
schema's [node specs](https://prosemirror.net/docs/ref/#model.NodeSpec.parseDOM), reordered by
*/
static fromSchema(schema) {
return schema.cached.domParser || (schema.cached.domParser = new DOMParser(schema, DOMParser.schemaRules(schema)));
}
}
const blockTags = {
address: true,
article: true,
aside: true,
blockquote: true,
canvas: true,
dd: true,
div: true,
dl: true,
fieldset: true,
figcaption: true,
figure: true,
footer: true,
form: true,
h1: true,
h2: true,
h3: true,
h4: true,
h5: true,
h6: true,
header: true,
hgroup: true,
hr: true,
li: true,
noscript: true,
ol: true,
output: true,
p: true,
pre: true,
section: true,
table: true,
tfoot: true,
ul: true
};
const ignoreTags = {
head: true,
noscript: true,
object: true,
script: true,
style: true,
title: true
};
const listTags = { ol: true, ul: true };
const OPT_PRESERVE_WS = 1, OPT_PRESERVE_WS_FULL = 2, OPT_OPEN_LEFT = 4;
function wsOptionsFor(type, preserveWhitespace, base2) {
if (preserveWhitespace != null)
return (preserveWhitespace ? OPT_PRESERVE_WS : 0) | (preserveWhitespace === "full" ? OPT_PRESERVE_WS_FULL : 0);
return type && type.whitespace == "pre" ? OPT_PRESERVE_WS | OPT_PRESERVE_WS_FULL : base2 & ~OPT_OPEN_LEFT;
}
class NodeContext {
constructor(type, attrs, marks, pendingMarks, solid, match, options) {
this.type = type;
this.attrs = attrs;
this.marks = marks;
this.pendingMarks = pendingMarks;
this.solid = solid;
this.options = options;
this.content = [];
this.activeMarks = Mark$1.none;
this.stashMarks = [];
this.match = match || (options & OPT_OPEN_LEFT ? null : type.contentMatch);
}
findWrapping(node) {
if (!this.match) {
if (!this.type)
return [];
let fill = this.type.contentMatch.fillBefore(Fragment.from(node));
if (fill) {
this.match = this.type.contentMatch.matchFragment(fill);
} else {
let start = this.type.contentMatch, wrap2;
if (wrap2 = start.findWrapping(node.type)) {
this.match = start;
return wrap2;
} else {
return null;
}
}
}
return this.match.findWrapping(node.type);
}
finish(openEnd) {
if (!(this.options & OPT_PRESERVE_WS)) {
let last = this.content[this.content.length - 1], m;
if (last && last.isText && (m = /[ \t\r\n\u000c]+$/.exec(last.text))) {
let text2 = last;
if (last.text.length == m[0].length)
this.content.pop();
else
this.content[this.content.length - 1] = text2.withText(text2.text.slice(0, text2.text.length - m[0].length));
}
}
let content = Fragment.from(this.content);
if (!openEnd && this.match)
content = content.append(this.match.fillBefore(Fragment.empty, true));
return this.type ? this.type.create(this.attrs, content, this.marks) : content;
}
popFromStashMark(mark) {
for (let i = this.stashMarks.length - 1; i >= 0; i--)
if (mark.eq(this.stashMarks[i]))
return this.stashMarks.splice(i, 1)[0];
}
applyPending(nextType) {
for (let i = 0, pending = this.pendingMarks; i < pending.length; i++) {
let mark = pending[i];
if ((this.type ? this.type.allowsMarkType(mark.type) : markMayApply(mark.type, nextType)) && !mark.isInSet(this.activeMarks)) {
this.activeMarks = mark.addToSet(this.activeMarks);
this.pendingMarks = mark.removeFromSet(this.pendingMarks);
}
}
}
inlineContext(node) {
if (this.type)
return this.type.inlineContent;
if (this.content.length)
return this.content[0].isInline;
return node.parentNode && !blockTags.hasOwnProperty(node.parentNode.nodeName.toLowerCase());
}
}
class ParseContext {
constructor(parser, options, isOpen) {
this.parser = parser;
this.options = options;
this.isOpen = isOpen;
this.open = 0;
let topNode = options.topNode, topContext;
let topOptions = wsOptionsFor(null, options.preserveWhitespace, 0) | (isOpen ? OPT_OPEN_LEFT : 0);
if (topNode)
topContext = new NodeContext(topNode.type, topNode.attrs, Mark$1.none, Mark$1.none, true, options.topMatch || topNode.type.contentMatch, topOptions);
else if (isOpen)
topContext = new NodeContext(null, null, Mark$1.none, Mark$1.none, true, null, topOptions);
else
topContext = new NodeContext(parser.schema.topNodeType, null, Mark$1.none, Mark$1.none, true, null, topOptions);
this.nodes = [topContext];
this.find = options.findPositions;
this.needsBlock = false;
}
get top() {
return this.nodes[this.open];
}
// Add a DOM node to the content. Text is inserted as text node,
// otherwise, the node is passed to `addElement` or, if it has a
// `style` attribute, `addElementWithStyles`.
addDOM(dom) {
if (dom.nodeType == 3) {
this.addTextNode(dom);
} else if (dom.nodeType == 1) {
let style2 = dom.getAttribute("style");
if (!style2) {
this.addElement(dom);
} else {
let marks = this.readStyles(parseStyles(style2));
if (!marks)
return;
let [addMarks, removeMarks] = marks, top = this.top;
for (let i = 0; i < removeMarks.length; i++)
this.removePendingMark(removeMarks[i], top);
for (let i = 0; i < addMarks.length; i++)
this.addPendingMark(addMarks[i]);
this.addElement(dom);
for (let i = 0; i < addMarks.length; i++)
this.removePendingMark(addMarks[i], top);
for (let i = 0; i < removeMarks.length; i++)
this.addPendingMark(removeMarks[i]);
}
}
}
addTextNode(dom) {
let value = dom.nodeValue;
let top = this.top;
if (top.options & OPT_PRESERVE_WS_FULL || top.inlineContext(dom) || /[^ \t\r\n\u000c]/.test(value)) {
if (!(top.options & OPT_PRESERVE_WS)) {
value = value.replace(/[ \t\r\n\u000c]+/g, " ");
if (/^[ \t\r\n\u000c]/.test(value) && this.open == this.nodes.length - 1) {
let nodeBefore = top.content[top.content.length - 1];
let domNodeBefore = dom.previousSibling;
if (!nodeBefore || domNodeBefore && domNodeBefore.nodeName == "BR" || nodeBefore.isText && /[ \t\r\n\u000c]$/.test(nodeBefore.text))
value = value.slice(1);
}
} else if (!(top.options & OPT_PRESERVE_WS_FULL)) {
value = value.replace(/\r?\n|\r/g, " ");
} else {
value = value.replace(/\r\n?/g, "\n");
}
if (value)
this.insertNode(this.parser.schema.text(value));
this.findInText(dom);
} else {
this.findInside(dom);
}
}
// Try to find a handler for the given tag and use that to parse. If
// none is found, the element's content nodes are added directly.
addElement(dom, matchAfter) {
let name = dom.nodeName.toLowerCase(), ruleID;
if (listTags.hasOwnProperty(name) && this.parser.normalizeLists)
normalizeList(dom);
let rule = this.options.ruleFromNode && this.options.ruleFromNode(dom) || (ruleID = this.parser.matchTag(dom, this, matchAfter));
if (rule ? rule.ignore : ignoreTags.hasOwnProperty(name)) {
this.findInside(dom);
this.ignoreFallback(dom);
} else if (!rule || rule.skip || rule.closeParent) {
if (rule && rule.closeParent)
this.open = Math.max(0, this.open - 1);
else if (rule && rule.skip.nodeType)
dom = rule.skip;
let sync, top = this.top, oldNeedsBlock = this.needsBlock;
if (blockTags.hasOwnProperty(name)) {
if (top.content.length && top.content[0].isInline && this.open) {
this.open--;
top = this.top;
}
sync = true;
if (!top.type)
this.needsBlock = true;
} else if (!dom.firstChild) {
this.leafFallback(dom);
return;
}
this.addAll(dom);
if (sync)
this.sync(top);
this.needsBlock = oldNeedsBlock;
} else {
this.addElementByRule(dom, rule, rule.consuming === false ? ruleID : void 0);
}
}
// Called for leaf DOM nodes that would otherwise be ignored
leafFallback(dom) {
if (dom.nodeName == "BR" && this.top.type && this.top.type.inlineContent)
this.addTextNode(dom.ownerDocument.createTextNode("\n"));
}
// Called for ignored nodes
ignoreFallback(dom) {
if (dom.nodeName == "BR" && (!this.top.type || !this.top.type.inlineContent))
this.findPlace(this.parser.schema.text("-"));
}
// Run any style parser associated with the node's styles. Either
// return an array of marks, or null to indicate some of the styles
// had a rule with `ignore` set.
readStyles(styles) {
let add = Mark$1.none, remove = Mark$1.none;
style:
for (let i = 0; i < styles.length; i += 2) {
for (let after = void 0; ; ) {
let rule = this.parser.matchStyle(styles[i], styles[i + 1], this, after);
if (!rule)
continue style;
if (rule.ignore)
return null;
if (rule.clearMark) {
this.top.pendingMarks.forEach((m) => {
if (rule.clearMark(m))
remove = m.addToSet(remove);
});
} else {
add = this.parser.schema.marks[rule.mark].create(rule.attrs).addToSet(add);
}
if (rule.consuming === false)
after = rule;
else
break;
}
}
return [add, remove];
}
// Look up a handler for the given node. If none are found, return
// false. Otherwise, apply it, use its return value to drive the way
// the node's content is wrapped, and return true.
addElementByRule(dom, rule, continueAfter) {
let sync, nodeType, mark;
if (rule.node) {
nodeType = this.parser.schema.nodes[rule.node];
if (!nodeType.isLeaf) {
sync = this.enter(nodeType, rule.attrs || null, rule.preserveWhitespace);
} else if (!this.insertNode(nodeType.create(rule.attrs))) {
this.leafFallback(dom);
}
} else {
let markType = this.parser.schema.marks[rule.mark];
mark = markType.create(rule.attrs);
this.addPendingMark(mark);
}
let startIn = this.top;
if (nodeType && nodeType.isLeaf) {
this.findInside(dom);
} else if (continueAfter) {
this.addElement(dom, continueAfter);
} else if (rule.getContent) {
this.findInside(dom);
rule.getContent(dom, this.parser.schema).forEach((node) => this.insertNode(node));
} else {
let contentDOM = dom;
if (typeof rule.contentElement == "string")
contentDOM = dom.querySelector(rule.contentElement);
else if (typeof rule.contentElement == "function")
contentDOM = rule.contentElement(dom);
else if (rule.contentElement)
contentDOM = rule.contentElement;
this.findAround(dom, contentDOM, true);
this.addAll(contentDOM);
}
if (sync && this.sync(startIn))
this.open--;
if (mark)
this.removePendingMark(mark, startIn);
}
// Add all child nodes between `startIndex` and `endIndex` (or the
// whole node, if not given). If `sync` is passed, use it to
// synchronize after every block element.
addAll(parent, startIndex, endIndex) {
let index = startIndex || 0;
for (let dom = startIndex ? parent.childNodes[startIndex] : parent.firstChild, end = endIndex == null ? null : parent.childNodes[endIndex]; dom != end; dom = dom.nextSibling, ++index) {
this.findAtPoint(parent, index);
this.addDOM(dom);
}
this.findAtPoint(parent, index);
}
// Try to find a way to fit the given node type into the current
// context. May add intermediate wrappers and/or leave non-solid
// nodes that we're in.
findPlace(node) {
let route, sync;
for (let depth = this.open; depth >= 0; depth--) {
let cx = this.nodes[depth];
let found2 = cx.findWrapping(node);
if (found2 && (!route || route.length > found2.length)) {
route = found2;
sync = cx;
if (!found2.length)
break;
}
if (cx.solid)
break;
}
if (!route)
return false;
this.sync(sync);
for (let i = 0; i < route.length; i++)
this.enterInner(route[i], null, false);
return true;
}
// Try to insert the given node, adjusting the context when needed.
insertNode(node) {
if (node.isInline && this.needsBlock && !this.top.type) {
let block = this.textblockFromContext();
if (block)
this.enterInner(block);
}
if (this.findPlace(node)) {
this.closeExtra();
let top = this.top;
top.applyPending(node.type);
if (top.match)
top.match = top.match.matchType(node.type);
let marks = top.activeMarks;
for (let i = 0; i < node.marks.length; i++)
if (!top.type || top.type.allowsMarkType(node.marks[i].type))
marks = node.marks[i].addToSet(marks);
top.content.push(node.mark(marks));
return true;
}
return false;
}
// Try to start a node of the given type, adjusting the context when
// necessary.
enter(type, attrs, preserveWS) {
let ok = this.findPlace(type.create(attrs));
if (ok)
this.enterInner(type, attrs, true, preserveWS);
return ok;
}
// Open a node of the given type
enterInner(type, attrs = null, solid = false, preserveWS) {
this.closeExtra();
let top = this.top;
top.applyPending(type);
top.match = top.match && top.match.matchType(type);
let options = wsOptionsFor(type, preserveWS, top.options);
if (top.options & OPT_OPEN_LEFT && top.content.length == 0)
options |= OPT_OPEN_LEFT;
this.nodes.push(new NodeContext(type, attrs, top.activeMarks, top.pendingMarks, solid, null, options));
this.open++;
}
// Make sure all nodes above this.open are finished and added to
// their parents
closeExtra(openEnd = false) {
let i = this.nodes.length - 1;
if (i > this.open) {
for (; i > this.open; i--)
this.nodes[i - 1].content.push(this.nodes[i].finish(openEnd));
this.nodes.length = this.open + 1;
}
}
finish() {
this.open = 0;
this.closeExtra(this.isOpen);
return this.nodes[0].finish(this.isOpen || this.options.topOpen);
}
sync(to) {
for (let i = this.open; i >= 0; i--)
if (this.nodes[i] == to) {
this.open = i;
return true;
}
return false;
}
get currentPos() {
this.closeExtra();
let pos = 0;
for (let i = this.open; i >= 0; i--) {
let content = this.nodes[i].content;
for (let j = content.length - 1; j >= 0; j--)
pos += content[j].nodeSize;
if (i)
pos++;
}
return pos;
}
findAtPoint(parent, offset) {
if (this.find)
for (let i = 0; i < this.find.length; i++) {
if (this.find[i].node == parent && this.find[i].offset == offset)
this.find[i].pos = this.currentPos;
}
}
findInside(parent) {
if (this.find)
for (let i = 0; i < this.find.length; i++) {
if (this.find[i].pos == null && parent.nodeType == 1 && parent.contains(this.find[i].node))
this.find[i].pos = this.currentPos;
}
}
findAround(parent, content, before) {
if (parent != content && this.find)
for (let i = 0; i < this.find.length; i++) {
if (this.find[i].pos == null && parent.nodeType == 1 && parent.contains(this.find[i].node)) {
let pos = content.compareDocumentPosition(this.find[i].node);
if (pos & (before ? 2 : 4))
this.find[i].pos = this.currentPos;
}
}
}
findInText(textNode) {
if (this.find)
for (let i = 0; i < this.find.length; i++) {
if (this.find[i].node == textNode)
this.find[i].pos = this.currentPos - (textNode.nodeValue.length - this.find[i].offset);
}
}
// Determines whether the given context string matches this context.
matchesContext(context) {
if (context.indexOf("|") > -1)
return context.split(/\s*\|\s*/).some(this.matchesContext, this);
let parts = context.split("/");
let option = this.options.context;
let useRoot = !this.isOpen && (!option || option.parent.type == this.nodes[0].type);
let minDepth = -(option ? option.depth + 1 : 0) + (useRoot ? 0 : 1);
let match = (i, depth) => {
for (; i >= 0; i--) {
let part = parts[i];
if (part == "") {
if (i == parts.length - 1 || i == 0)
continue;
for (; depth >= minDepth; depth--)
if (match(i - 1, depth))
return true;
return false;
} else {
let next = depth > 0 || depth == 0 && useRoot ? this.nodes[depth].type : option && depth >= minDepth ? option.node(depth - minDepth).type : null;
if (!next || next.name != part && next.groups.indexOf(part) == -1)
return false;
depth--;
}
}
return true;
};
return match(parts.length - 1, this.open);
}
textblockFromContext() {
let $context = this.options.context;
if ($context)
for (let d = $context.depth; d >= 0; d--) {
let deflt = $context.node(d).contentMatchAt($context.indexAfter(d)).defaultType;
if (deflt && deflt.isTextblock && deflt.defaultAttrs)
return deflt;
}
for (let name in this.parser.schema.nodes) {
let type = this.parser.schema.nodes[name];
if (type.isTextblock && type.defaultAttrs)
return type;
}
}
addPendingMark(mark) {
let found2 = findSameMarkInSet(mark, this.top.pendingMarks);
if (found2)
this.top.stashMarks.push(found2);
this.top.pendingMarks = mark.addToSet(this.top.pendingMarks);
}
removePendingMark(mark, upto) {
for (let depth = this.open; depth >= 0; depth--) {
let level = this.nodes[depth];
let found2 = level.pendingMarks.lastIndexOf(mark);
if (found2 > -1) {
level.pendingMarks = mark.removeFromSet(level.pendingMarks);
} else {
level.activeMarks = mark.removeFromSet(level.activeMarks);
let stashMark = level.popFromStashMark(mark);
if (stashMark && level.type && level.type.allowsMarkType(stashMark.type))
level.activeMarks = stashMark.addToSet(level.activeMarks);
}
if (level == upto)
break;
}
}
}
function normalizeList(dom) {
for (let child = dom.firstChild, prevItem = null; child; child = child.nextSibling) {
let name = child.nodeType == 1 ? child.nodeName.toLowerCase() : null;
if (name && listTags.hasOwnProperty(name) && prevItem) {
prevItem.appendChild(child);
child = prevItem;
} else if (name == "li") {
prevItem = child;
} else if (name) {
prevItem = null;
}
}
}
function matches(dom, selector) {
return (dom.matches || dom.msMatchesSelector || dom.webkitMatchesSelector || dom.mozMatchesSelector).call(dom, selector);
}
function parseStyles(style2) {
let re = /\s*([\w-]+)\s*:\s*([^;]+)/g, m, result = [];
while (m = re.exec(style2))
result.push(m[1], m[2].trim());
return result;
}
function copy(obj) {
let copy2 = {};
for (let prop in obj)
copy2[prop] = obj[prop];
return copy2;
}
function markMayApply(markType, nodeType) {
let nodes = nodeType.schema.nodes;
for (let name in nodes) {
let parent = nodes[name];
if (!parent.allowsMarkType(markType))
continue;
let seen = [], scan = (match) => {
seen.push(match);
for (let i = 0; i < match.edgeCount; i++) {
let { type, next } = match.edge(i);
if (type == nodeType)
return true;
if (seen.indexOf(next) < 0 && scan(next))
return true;
}
};
if (scan(parent.contentMatch))
return true;
}
}
function findSameMarkInSet(mark, set) {
for (let i = 0; i < set.length; i++) {
if (mark.eq(set[i]))
return set[i];
}
}
class DOMSerializer {
/**
Create a serializer. `nodes` should map node names to functions
that take a node and return a description of the corresponding
DOM. `marks` does the same for mark names, but also gets an
argument that tells it whether the mark's content is block or
inline content (for typical use, it'll always be inline). A mark
serializer may be `null` to indicate that marks of that type
should not be serialized.
*/
constructor(nodes, marks) {
this.nodes = nodes;
this.marks = marks;
}
/**
Serialize the content of this fragment to a DOM fragment. When
not in the browser, the `document` option, containing a DOM
document, should be passed so that the serializer can create
nodes.
*/
serializeFragment(fragment, options = {}, target) {
if (!target)
target = doc$1(options).createDocumentFragment();
let top = target, active = [];
fragment.forEach((node) => {
if (active.length || node.marks.length) {
let keep = 0, rendered = 0;
while (keep < active.length && rendered < node.marks.length) {
let next = node.marks[rendered];
if (!this.marks[next.type.name]) {
rendered++;
continue;
}
if (!next.eq(active[keep][0]) || next.type.spec.spanning === false)
break;
keep++;
rendered++;
}
while (keep < active.length)
top = active.pop()[1];
while (rendered < node.marks.length) {
let add = node.marks[rendered++];
let markDOM = this.serializeMark(add, node.isInline, options);
if (markDOM) {
active.push([add, top]);
top.appendChild(markDOM.dom);
top = markDOM.contentDOM || markDOM.dom;
}
}
}
top.appendChild(this.serializeNodeInner(node, options));
});
return target;
}
/**
@internal
*/
serializeNodeInner(node, options) {
let { dom, contentDOM } = DOMSerializer.renderSpec(doc$1(options), this.nodes[node.type.name](node));
if (contentDOM) {
if (node.isLeaf)
throw new RangeError("Content hole not allowed in a leaf node spec");
this.serializeFragment(node.content, options, contentDOM);
}
return dom;
}
/**
Serialize this node to a DOM node. This can be useful when you
need to serialize a part of a document, as opposed to the whole
document. To serialize a whole document, use
*/
serializeNode(node, options = {}) {
let dom = this.serializeNodeInner(node, options);
for (let i = node.marks.length - 1; i >= 0; i--) {
let wrap2 = this.serializeMark(node.marks[i], node.isInline, options);
if (wrap2) {
(wrap2.contentDOM || wrap2.dom).appendChild(dom);
dom = wrap2.dom;
}
}
return dom;
}
/**
@internal
*/
serializeMark(mark, inline, options = {}) {
let toDOM = this.marks[mark.type.name];
return toDOM && DOMSerializer.renderSpec(doc$1(options), toDOM(mark, inline));
}
/**
Render an [output spec](https://prosemirror.net/docs/ref/#model.DOMOutputSpec) to a DOM node. If
the spec has a hole (zero) in it, `contentDOM` will point at the
node with the hole.
*/
static renderSpec(doc2, structure, xmlNS = null) {
if (typeof structure == "string")
return { dom: doc2.createTextNode(structure) };
if (structure.nodeType != null)
return { dom: structure };
if (structure.dom && structure.dom.nodeType != null)
return structure;
let tagName = structure[0], space = tagName.indexOf(" ");
if (space > 0) {
xmlNS = tagName.slice(0, space);
tagName = tagName.slice(space + 1);
}
let contentDOM;
let dom = xmlNS ? doc2.createElementNS(xmlNS, tagName) : doc2.createElement(tagName);
let attrs = structure[1], start = 1;
if (attrs && typeof attrs == "object" && attrs.nodeType == null && !Array.isArray(attrs)) {
start = 2;
for (let name in attrs)
if (attrs[name] != null) {
let space2 = name.indexOf(" ");
if (space2 > 0)
dom.setAttributeNS(name.slice(0, space2), name.slice(space2 + 1), attrs[name]);
else
dom.setAttribute(name, attrs[name]);
}
}
for (let i = start; i < structure.length; i++) {
let child = structure[i];
if (child === 0) {
if (i < structure.length - 1 || i > start)
throw new RangeError("Content hole must be the only child of its parent node");
return { dom, contentDOM: dom };
} else {
let { dom: inner, contentDOM: innerContent } = DOMSerializer.renderSpec(doc2, child, xmlNS);
dom.appendChild(inner);
if (innerContent) {
if (contentDOM)
throw new RangeError("Multiple content holes");
contentDOM = innerContent;
}
}
}
return { dom, contentDOM };
}
/**
Build a serializer using the [`toDOM`](https://prosemirror.net/docs/ref/#model.NodeSpec.toDOM)
properties in a schema's node and mark specs.
*/
static fromSchema(schema) {
return schema.cached.domSerializer || (schema.cached.domSerializer = new DOMSerializer(this.nodesFromSchema(schema), this.marksFromSchema(schema)));
}
/**
Gather the serializers in a schema's node specs into an object.
This can be useful as a base to build a custom serializer from.
*/
static nodesFromSchema(schema) {
let result = gatherToDOM(schema.nodes);
if (!result.text)
result.text = (node) => node.text;
return result;
}
/**
Gather the serializers in a schema's mark specs into an object.
*/
static marksFromSchema(schema) {
return gatherToDOM(schema.marks);
}
}
function gatherToDOM(obj) {
let result = {};
for (let name in obj) {
let toDOM = obj[name].spec.toDOM;
if (toDOM)
result[name] = toDOM;
}
return result;
}
function doc$1(options) {
return options.document || window.document;
}
const lower16 = 65535;
const factor16 = Math.pow(2, 16);
function makeRecover(index, offset) {
return index + offset * factor16;
}
function recoverIndex(value) {
return value & lower16;
}
function recoverOffset(value) {
return (value - (value & lower16)) / factor16;
}
const DEL_BEFORE = 1, DEL_AFTER = 2, DEL_ACROSS = 4, DEL_SIDE = 8;
class MapResult {
/**
@internal
*/
constructor(pos, delInfo, recover) {
this.pos = pos;
this.delInfo = delInfo;
this.recover = recover;
}
/**
Tells you whether the position was deleted, that is, whether the
step removed the token on the side queried (via the `assoc`)
argument from the document.
*/
get deleted() {
return (this.delInfo & DEL_SIDE) > 0;
}
/**
Tells you whether the token before the mapped position was deleted.
*/
get deletedBefore() {
return (this.delInfo & (DEL_BEFORE | DEL_ACROSS)) > 0;
}
/**
True when the token after the mapped position was deleted.
*/
get deletedAfter() {
return (this.delInfo & (DEL_AFTER | DEL_ACROSS)) > 0;
}
/**
Tells whether any of the steps mapped through deletes across the
position (including both the token before and after the
position).
*/
get deletedAcross() {
return (this.delInfo & DEL_ACROSS) > 0;
}
}
class StepMap {
/**
Create a position map. The modifications to the document are
represented as an array of numbers, in which each group of three
represents a modified chunk as `[start, oldSize, newSize]`.
*/
constructor(ranges, inverted = false) {
this.ranges = ranges;
this.inverted = inverted;
if (!ranges.length && StepMap.empty)
return StepMap.empty;
}
/**
@internal
*/
recover(value) {
let diff = 0, index = recoverIndex(value);
if (!this.inverted)
for (let i = 0; i < index; i++)
diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1];
return this.ranges[index * 3] + diff + recoverOffset(value);
}
mapResult(pos, assoc = 1) {
return this._map(pos, assoc, false);
}
map(pos, assoc = 1) {
return this._map(pos, assoc, true);
}
/**
@internal
*/
_map(pos, assoc, simple) {
let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2;
for (let i = 0; i < this.ranges.length; i += 3) {
let start = this.ranges[i] - (this.inverted ? diff : 0);
if (start > pos)
break;
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize;
if (pos <= end) {
let side = !oldSize ? assoc : pos == start ? -1 : pos == end ? 1 : assoc;
let result = start + diff + (side < 0 ? 0 : newSize);
if (simple)
return result;
let recover = pos == (assoc < 0 ? start : end) ? null : makeRecover(i / 3, pos - start);
let del = pos == start ? DEL_AFTER : pos == end ? DEL_BEFORE : DEL_ACROSS;
if (assoc < 0 ? pos != start : pos != end)
del |= DEL_SIDE;
return new MapResult(result, del, recover);
}
diff += newSize - oldSize;
}
return simple ? pos + diff : new MapResult(pos + diff, 0, null);
}
/**
@internal
*/
touches(pos, recover) {
let diff = 0, index = recoverIndex(recover);
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2;
for (let i = 0; i < this.ranges.length; i += 3) {
let start = this.ranges[i] - (this.inverted ? diff : 0);
if (start > pos)
break;
let oldSize = this.ranges[i + oldIndex], end = start + oldSize;
if (pos <= end && i == index * 3)
return true;
diff += this.ranges[i + newIndex] - oldSize;
}
return false;
}
/**
Calls the given function on each of the changed ranges included in
this map.
*/
forEach(f) {
let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2;
for (let i = 0, diff = 0; i < this.ranges.length; i += 3) {
let start = this.ranges[i], oldStart = start - (this.inverted ? diff : 0), newStart = start + (this.inverted ? 0 : diff);
let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex];
f(oldStart, oldStart + oldSize, newStart, newStart + newSize);
diff += newSize - oldSize;
}
}
/**
Create an inverted version of this map. The result can be used to
map positions in the post-step document to the pre-step document.
*/
invert() {
return new StepMap(this.ranges, !this.inverted);
}
/**
@internal
*/
toString() {
return (this.inverted ? "-" : "") + JSON.stringify(this.ranges);
}
/**
Create a map that moves all positions by offset `n` (which may be
negative). This can be useful when applying steps meant for a
sub-document to a larger document, or vice-versa.
*/
static offset(n) {
return n == 0 ? StepMap.empty : new StepMap(n < 0 ? [0, -n, 0] : [0, 0, n]);
}
}
StepMap.empty = new StepMap([]);
class Mapping {
/**
Create a new mapping with the given position maps.
*/
constructor(maps = [], mirror, from2 = 0, to = maps.length) {
this.maps = maps;
this.mirror = mirror;
this.from = from2;
this.to = to;
}
/**
Create a mapping that maps only through a part of this one.
*/
slice(from2 = 0, to = this.maps.length) {
return new Mapping(this.maps, this.mirror, from2, to);
}
/**
@internal
*/
copy() {
return new Mapping(this.maps.slice(), this.mirror && this.mirror.slice(), this.from, this.to);
}
/**
Add a step map to the end of this mapping. If `mirrors` is
given, it should be the index of the step map that is the mirror
image of this one.
*/
appendMap(map2, mirrors) {
this.to = this.maps.push(map2);
if (mirrors != null)
this.setMirror(this.maps.length - 1, mirrors);
}
/**
Add all the step maps in a given mapping to this one (preserving
mirroring information).
*/
appendMapping(mapping) {
for (let i = 0, startSize = this.maps.length; i < mapping.maps.length; i++) {
let mirr = mapping.getMirror(i);
this.appendMap(mapping.maps[i], mirr != null && mirr < i ? startSize + mirr : void 0);
}
}
/**
Finds the offset of the step map that mirrors the map at the
given offset, in this mapping (as per the second argument to
`appendMap`).
*/
getMirror(n) {
if (this.mirror) {
for (let i = 0; i < this.mirror.length; i++)
if (this.mirror[i] == n)
return this.mirror[i + (i % 2 ? -1 : 1)];
}
}
/**
@internal
*/
setMirror(n, m) {
if (!this.mirror)
this.mirror = [];
this.mirror.push(n, m);
}
/**
Append the inverse of the given mapping to this one.
*/
appendMappingInverted(mapping) {
for (let i = mapping.maps.length - 1, totalSize = this.maps.length + mapping.maps.length; i >= 0; i--) {
let mirr = mapping.getMirror(i);
this.appendMap(mapping.maps[i].invert(), mirr != null && mirr > i ? totalSize - mirr - 1 : void 0);
}
}
/**
Create an inverted version of this mapping.
*/
invert() {
let inverse = new Mapping();
inverse.appendMappingInverted(this);
return inverse;
}
/**
Map a position through this mapping.
*/
map(pos, assoc = 1) {
if (this.mirror)
return this._map(pos, assoc, true);
for (let i = this.from; i < this.to; i++)
pos = this.maps[i].map(pos, assoc);
return pos;
}
/**
Map a position through this mapping, returning a mapping
result.
*/
mapResult(pos, assoc = 1) {
return this._map(pos, assoc, false);
}
/**
@internal
*/
_map(pos, assoc, simple) {
let delInfo = 0;
for (let i = this.from; i < this.to; i++) {
let map2 = this.maps[i], result = map2.mapResult(pos, assoc);
if (result.recover != null) {
let corr = this.getMirror(i);
if (corr != null && corr > i && corr < this.to) {
i = corr;
pos = this.maps[corr].recover(result.recover);
continue;
}
}
delInfo |= result.delInfo;
pos = result.pos;
}
return simple ? pos : new MapResult(pos, delInfo, null);
}
}
const stepsByID = /* @__PURE__ */ Object.create(null);
class Step {
/**
Get the step map that represents the changes made by this step,
and which can be used to transform between positions in the old
and the new document.
*/
getMap() {
return StepMap.empty;
}
/**
Try to merge this step with another one, to be applied directly
after it. Returns the merged step when possible, null if the
steps can't be merged.
*/
merge(other) {
return null;
}
/**
Deserialize a step from its JSON representation. Will call
through to the step class' own implementation of this method.
*/
static fromJSON(schema, json) {
if (!json || !json.stepType)
throw new RangeError("Invalid input for Step.fromJSON");
let type = stepsByID[json.stepType];
if (!type)
throw new RangeError(`No step type ${json.stepType} defined`);
return type.fromJSON(schema, json);
}
/**
To be able to serialize steps to JSON, each step needs a string
ID to attach to its JSON representation. Use this method to
register an ID for your step classes. Try to pick something
that's unlikely to clash with steps from other modules.
*/
static jsonID(id, stepClass) {
if (id in stepsByID)
throw new RangeError("Duplicate use of step JSON ID " + id);
stepsByID[id] = stepClass;
stepClass.prototype.jsonID = id;
return stepClass;
}
}
class StepResult {
/**
@internal
*/
constructor(doc2, failed) {
this.doc = doc2;
this.failed = failed;
}
/**
Create a successful step result.
*/
static ok(doc2) {
return new StepResult(doc2, null);
}
/**
Create a failed step result.
*/
static fail(message) {
return new StepResult(null, message);
}
/**
Call [`Node.replace`](https://prosemirror.net/docs/ref/#model.Node.replace) with the given
arguments. Create a successful result if it succeeds, and a
failed one if it throws a `ReplaceError`.
*/
static fromReplace(doc2, from2, to, slice2) {
try {
return StepResult.ok(doc2.replace(from2, to, slice2));
} catch (e) {
if (e instanceof ReplaceError)
return StepResult.fail(e.message);
throw e;
}
}
}
function mapFragment(fragment, f, parent) {
let mapped = [];
for (let i = 0; i < fragment.childCount; i++) {
let child = fragment.child(i);
if (child.content.size)
child = child.copy(mapFragment(child.content, f, child));
if (child.isInline)
child = f(child, parent, i);
mapped.push(child);
}
return Fragment.fromArray(mapped);
}
class AddMarkStep extends Step {
/**
Create a mark step.
*/
constructor(from2, to, mark) {
super();
this.from = from2;
this.to = to;
this.mark = mark;
}
apply(doc2) {
let oldSlice = doc2.slice(this.from, this.to), $from = doc2.resolve(this.from);
let parent = $from.node($from.sharedDepth(this.to));
let slice2 = new Slice(mapFragment(oldSlice.content, (node, parent2) => {
if (!node.isAtom || !parent2.type.allowsMarkType(this.mark.type))
return node;
return node.mark(this.mark.addToSet(node.marks));
}, parent), oldSlice.openStart, oldSlice.openEnd);
return StepResult.fromReplace(doc2, this.from, this.to, slice2);
}
invert() {
return new RemoveMarkStep(this.from, this.to, this.mark);
}
map(mapping) {
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
if (from2.deleted && to.deleted || from2.pos >= to.pos)
return null;
return new AddMarkStep(from2.pos, to.pos, this.mark);
}
merge(other) {
if (other instanceof AddMarkStep && other.mark.eq(this.mark) && this.from <= other.to && this.to >= other.from)
return new AddMarkStep(Math.min(this.from, other.from), Math.max(this.to, other.to), this.mark);
return null;
}
toJSON() {
return {
stepType: "addMark",
mark: this.mark.toJSON(),
from: this.from,
to: this.to
};
}
/**
@internal
*/
static fromJSON(schema, json) {
if (typeof json.from != "number" || typeof json.to != "number")
throw new RangeError("Invalid input for AddMarkStep.fromJSON");
return new AddMarkStep(json.from, json.to, schema.markFromJSON(json.mark));
}
}
Step.jsonID("addMark", AddMarkStep);
class RemoveMarkStep extends Step {
/**
Create a mark-removing step.
*/
constructor(from2, to, mark) {
super();
this.from = from2;
this.to = to;
this.mark = mark;
}
apply(doc2) {
let oldSlice = doc2.slice(this.from, this.to);
let slice2 = new Slice(mapFragment(oldSlice.content, (node) => {
return node.mark(this.mark.removeFromSet(node.marks));
}, doc2), oldSlice.openStart, oldSlice.openEnd);
return StepResult.fromReplace(doc2, this.from, this.to, slice2);
}
invert() {
return new AddMarkStep(this.from, this.to, this.mark);
}
map(mapping) {
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
if (from2.deleted && to.deleted || from2.pos >= to.pos)
return null;
return new RemoveMarkStep(from2.pos, to.pos, this.mark);
}
merge(other) {
if (other instanceof RemoveMarkStep && other.mark.eq(this.mark) && this.from <= other.to && this.to >= other.from)
return new RemoveMarkStep(Math.min(this.from, other.from), Math.max(this.to, other.to), this.mark);
return null;
}
toJSON() {
return {
stepType: "removeMark",
mark: this.mark.toJSON(),
from: this.from,
to: this.to
};
}
/**
@internal
*/
static fromJSON(schema, json) {
if (typeof json.from != "number" || typeof json.to != "number")
throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");
return new RemoveMarkStep(json.from, json.to, schema.markFromJSON(json.mark));
}
}
Step.jsonID("removeMark", RemoveMarkStep);
class AddNodeMarkStep extends Step {
/**
Create a node mark step.
*/
constructor(pos, mark) {
super();
this.pos = pos;
this.mark = mark;
}
apply(doc2) {
let node = doc2.nodeAt(this.pos);
if (!node)
return StepResult.fail("No node at mark step's position");
let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks));
return StepResult.fromReplace(doc2, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1));
}
invert(doc2) {
let node = doc2.nodeAt(this.pos);
if (node) {
let newSet = this.mark.addToSet(node.marks);
if (newSet.length == node.marks.length) {
for (let i = 0; i < node.marks.length; i++)
if (!node.marks[i].isInSet(newSet))
return new AddNodeMarkStep(this.pos, node.marks[i]);
return new AddNodeMarkStep(this.pos, this.mark);
}
}
return new RemoveNodeMarkStep(this.pos, this.mark);
}
map(mapping) {
let pos = mapping.mapResult(this.pos, 1);
return pos.deletedAfter ? null : new AddNodeMarkStep(pos.pos, this.mark);
}
toJSON() {
return { stepType: "addNodeMark", pos: this.pos, mark: this.mark.toJSON() };
}
/**
@internal
*/
static fromJSON(schema, json) {
if (typeof json.pos != "number")
throw new RangeError("Invalid input for AddNodeMarkStep.fromJSON");
return new AddNodeMarkStep(json.pos, schema.markFromJSON(json.mark));
}
}
Step.jsonID("addNodeMark", AddNodeMarkStep);
class RemoveNodeMarkStep extends Step {
/**
Create a mark-removing step.
*/
constructor(pos, mark) {
super();
this.pos = pos;
this.mark = mark;
}
apply(doc2) {
let node = doc2.nodeAt(this.pos);
if (!node)
return StepResult.fail("No node at mark step's position");
let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks));
return StepResult.fromReplace(doc2, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1));
}
invert(doc2) {
let node = doc2.nodeAt(this.pos);
if (!node || !this.mark.isInSet(node.marks))
return this;
return new AddNodeMarkStep(this.pos, this.mark);
}
map(mapping) {
let pos = mapping.mapResult(this.pos, 1);
return pos.deletedAfter ? null : new RemoveNodeMarkStep(pos.pos, this.mark);
}
toJSON() {
return { stepType: "removeNodeMark", pos: this.pos, mark: this.mark.toJSON() };
}
/**
@internal
*/
static fromJSON(schema, json) {
if (typeof json.pos != "number")
throw new RangeError("Invalid input for RemoveNodeMarkStep.fromJSON");
return new RemoveNodeMarkStep(json.pos, schema.markFromJSON(json.mark));
}
}
Step.jsonID("removeNodeMark", RemoveNodeMarkStep);
class ReplaceStep extends Step {
/**
The given `slice` should fit the 'gap' between `from` and
`to`—the depths must line up, and the surrounding nodes must be
able to be joined with the open sides of the slice. When
`structure` is true, the step will fail if the content between
from and to is not just a sequence of closing and then opening
tokens (this is to guard against rebased replace steps
overwriting something they weren't supposed to).
*/
constructor(from2, to, slice2, structure = false) {
super();
this.from = from2;
this.to = to;
this.slice = slice2;
this.structure = structure;
}
apply(doc2) {
if (this.structure && contentBetween(doc2, this.from, this.to))
return StepResult.fail("Structure replace would overwrite content");
return StepResult.fromReplace(doc2, this.from, this.to, this.slice);
}
getMap() {
return new StepMap([this.from, this.to - this.from, this.slice.size]);
}
invert(doc2) {
return new ReplaceStep(this.from, this.from + this.slice.size, doc2.slice(this.from, this.to));
}
map(mapping) {
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
if (from2.deletedAcross && to.deletedAcross)
return null;
return new ReplaceStep(from2.pos, Math.max(from2.pos, to.pos), this.slice);
}
merge(other) {
if (!(other instanceof ReplaceStep) || other.structure || this.structure)
return null;
if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
let slice2 = this.slice.size + other.slice.size == 0 ? Slice.empty : new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd);
return new ReplaceStep(this.from, this.to + (other.to - other.from), slice2, this.structure);
} else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) {
let slice2 = this.slice.size + other.slice.size == 0 ? Slice.empty : new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd);
return new ReplaceStep(other.from, this.to, slice2, this.structure);
} else {
return null;
}
}
toJSON() {
let json = { stepType: "replace", from: this.from, to: this.to };
if (this.slice.size)
json.slice = this.slice.toJSON();
if (this.structure)
json.structure = true;
return json;
}
/**
@internal
*/
static fromJSON(schema, json) {
if (typeof json.from != "number" || typeof json.to != "number")
throw new RangeError("Invalid input for ReplaceStep.fromJSON");
return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure);
}
}
Step.jsonID("replace", ReplaceStep);
class ReplaceAroundStep extends Step {
/**
Create a replace-around step with the given range and gap.
`insert` should be the point in the slice into which the content
of the gap should be moved. `structure` has the same meaning as
it has in the [`ReplaceStep`](https://prosemirror.net/docs/ref/#transform.ReplaceStep) class.
*/
constructor(from2, to, gapFrom, gapTo, slice2, insert, structure = false) {
super();
this.from = from2;
this.to = to;
this.gapFrom = gapFrom;
this.gapTo = gapTo;
this.slice = slice2;
this.insert = insert;
this.structure = structure;
}
apply(doc2) {
if (this.structure && (contentBetween(doc2, this.from, this.gapFrom) || contentBetween(doc2, this.gapTo, this.to)))
return StepResult.fail("Structure gap-replace would overwrite content");
let gap = doc2.slice(this.gapFrom, this.gapTo);
if (gap.openStart || gap.openEnd)
return StepResult.fail("Gap is not a flat range");
let inserted = this.slice.insertAt(this.insert, gap.content);
if (!inserted)
return StepResult.fail("Content does not fit in gap");
return StepResult.fromReplace(doc2, this.from, this.to, inserted);
}
getMap() {
return new StepMap([
this.from,
this.gapFrom - this.from,
this.insert,
this.gapTo,
this.to - this.gapTo,
this.slice.size - this.insert
]);
}
invert(doc2) {
let gap = this.gapTo - this.gapFrom;
return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap, this.from + this.insert, this.from + this.insert + gap, doc2.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from), this.gapFrom - this.from, this.structure);
}
map(mapping) {
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
let gapFrom = mapping.map(this.gapFrom, -1), gapTo = mapping.map(this.gapTo, 1);
if (from2.deletedAcross && to.deletedAcross || gapFrom < from2.pos || gapTo > to.pos)
return null;
return new ReplaceAroundStep(from2.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure);
}
toJSON() {
let json = {
stepType: "replaceAround",
from: this.from,
to: this.to,
gapFrom: this.gapFrom,
gapTo: this.gapTo,
insert: this.insert
};
if (this.slice.size)
json.slice = this.slice.toJSON();
if (this.structure)
json.structure = true;
return json;
}
/**
@internal
*/
static fromJSON(schema, json) {
if (typeof json.from != "number" || typeof json.to != "number" || typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");
return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo, Slice.fromJSON(schema, json.slice), json.insert, !!json.structure);
}
}
Step.jsonID("replaceAround", ReplaceAroundStep);
function contentBetween(doc2, from2, to) {
let $from = doc2.resolve(from2), dist = to - from2, depth = $from.depth;
while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
depth--;
dist--;
}
if (dist > 0) {
let next = $from.node(depth).maybeChild($from.indexAfter(depth));
while (dist > 0) {
if (!next || next.isLeaf)
return true;
next = next.firstChild;
dist--;
}
}
return false;
}
function addMark(tr, from2, to, mark) {
let removed = [], added = [];
let removing, adding;
tr.doc.nodesBetween(from2, to, (node, pos, parent) => {
if (!node.isInline)
return;
let marks = node.marks;
if (!mark.isInSet(marks) && parent.type.allowsMarkType(mark.type)) {
let start = Math.max(pos, from2), end = Math.min(pos + node.nodeSize, to);
let newSet = mark.addToSet(marks);
for (let i = 0; i < marks.length; i++) {
if (!marks[i].isInSet(newSet)) {
if (removing && removing.to == start && removing.mark.eq(marks[i]))
removing.to = end;
else
removed.push(removing = new RemoveMarkStep(start, end, marks[i]));
}
}
if (adding && adding.to == start)
adding.to = end;
else
added.push(adding = new AddMarkStep(start, end, mark));
}
});
removed.forEach((s) => tr.step(s));
added.forEach((s) => tr.step(s));
}
function removeMark(tr, from2, to, mark) {
let matched = [], step = 0;
tr.doc.nodesBetween(from2, to, (node, pos) => {
if (!node.isInline)
return;
step++;
let toRemove = null;
if (mark instanceof MarkType) {
let set = node.marks, found2;
while (found2 = mark.isInSet(set)) {
(toRemove || (toRemove = [])).push(found2);
set = found2.removeFromSet(set);
}
} else if (mark) {
if (mark.isInSet(node.marks))
toRemove = [mark];
} else {
toRemove = node.marks;
}
if (toRemove && toRemove.length) {
let end = Math.min(pos + node.nodeSize, to);
for (let i = 0; i < toRemove.length; i++) {
let style2 = toRemove[i], found2;
for (let j = 0; j < matched.length; j++) {
let m = matched[j];
if (m.step == step - 1 && style2.eq(matched[j].style))
found2 = m;
}
if (found2) {
found2.to = end;
found2.step = step;
} else {
matched.push({ style: style2, from: Math.max(pos, from2), to: end, step });
}
}
}
});
matched.forEach((m) => tr.step(new RemoveMarkStep(m.from, m.to, m.style)));
}
function clearIncompatible(tr, pos, parentType, match = parentType.contentMatch) {
let node = tr.doc.nodeAt(pos);
let delSteps = [], cur = pos + 1;
for (let i = 0; i < node.childCount; i++) {
let child = node.child(i), end = cur + child.nodeSize;
let allowed = match.matchType(child.type);
if (!allowed) {
delSteps.push(new ReplaceStep(cur, end, Slice.empty));
} else {
match = allowed;
for (let j = 0; j < child.marks.length; j++)
if (!parentType.allowsMarkType(child.marks[j].type))
tr.step(new RemoveMarkStep(cur, end, child.marks[j]));
}
cur = end;
}
if (!match.validEnd) {
let fill = match.fillBefore(Fragment.empty, true);
tr.replace(cur, cur, new Slice(fill, 0, 0));
}
for (let i = delSteps.length - 1; i >= 0; i--)
tr.step(delSteps[i]);
}
function canCut(node, start, end) {
return (start == 0 || node.canReplace(start, node.childCount)) && (end == node.childCount || node.canReplace(0, end));
}
function liftTarget(range) {
let parent = range.parent;
let content = parent.content.cutByIndex(range.startIndex, range.endIndex);
for (let depth = range.depth; ; --depth) {
let node = range.$from.node(depth);
let index = range.$from.index(depth), endIndex = range.$to.indexAfter(depth);
if (depth < range.depth && node.canReplace(index, endIndex, content))
return depth;
if (depth == 0 || node.type.spec.isolating || !canCut(node, index, endIndex))
break;
}
return null;
}
function lift$2(tr, range, target) {
let { $from, $to, depth } = range;
let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1);
let start = gapStart, end = gapEnd;
let before = Fragment.empty, openStart = 0;
for (let d = depth, splitting = false; d > target; d--)
if (splitting || $from.index(d) > 0) {
splitting = true;
before = Fragment.from($from.node(d).copy(before));
openStart++;
} else {
start--;
}
let after = Fragment.empty, openEnd = 0;
for (let d = depth, splitting = false; d > target; d--)
if (splitting || $to.after(d + 1) < $to.end(d)) {
splitting = true;
after = Fragment.from($to.node(d).copy(after));
openEnd++;
} else {
end++;
}
tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd, new Slice(before.append(after), openStart, openEnd), before.size - openStart, true));
}
function findWrapping(range, nodeType, attrs = null, innerRange = range) {
let around = findWrappingOutside(range, nodeType);
let inner = around && findWrappingInside(innerRange, nodeType);
if (!inner)
return null;
return around.map(withAttrs).concat({ type: nodeType, attrs }).concat(inner.map(withAttrs));
}
function withAttrs(type) {
return { type, attrs: null };
}
function findWrappingOutside(range, type) {
let { parent, startIndex, endIndex } = range;
let around = parent.contentMatchAt(startIndex).findWrapping(type);
if (!around)
return null;
let outer = around.length ? around[0] : type;
return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null;
}
function findWrappingInside(range, type) {
let { parent, startIndex, endIndex } = range;
let inner = parent.child(startIndex);
let inside = type.contentMatch.findWrapping(inner.type);
if (!inside)
return null;
let lastType = inside.length ? inside[inside.length - 1] : type;
let innerMatch = lastType.contentMatch;
for (let i = startIndex; innerMatch && i < endIndex; i++)
innerMatch = innerMatch.matchType(parent.child(i).type);
if (!innerMatch || !innerMatch.validEnd)
return null;
return inside;
}
function wrap(tr, range, wrappers) {
let content = Fragment.empty;
for (let i = wrappers.length - 1; i >= 0; i--) {
if (content.size) {
let match = wrappers[i].type.contentMatch.matchFragment(content);
if (!match || !match.validEnd)
throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper");
}
content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
}
let start = range.start, end = range.end;
tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true));
}
function setBlockType$1(tr, from2, to, type, attrs) {
if (!type.isTextblock)
throw new RangeError("Type given to setBlockType should be a textblock");
let mapFrom = tr.steps.length;
tr.doc.nodesBetween(from2, to, (node, pos) => {
if (node.isTextblock && !node.hasMarkup(type, attrs) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
tr.clearIncompatible(tr.mapping.slice(mapFrom).map(pos, 1), type);
let mapping = tr.mapping.slice(mapFrom);
let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1);
tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment.from(type.create(attrs, null, node.marks)), 0, 0), 1, true));
return false;
}
});
}
function canChangeType(doc2, pos, type) {
let $pos = doc2.resolve(pos), index = $pos.index();
return $pos.parent.canReplaceWith(index, index + 1, type);
}
function setNodeMarkup(tr, pos, type, attrs, marks) {
let node = tr.doc.nodeAt(pos);
if (!node)
throw new RangeError("No node at given position");
if (!type)
type = node.type;
let newNode = type.create(attrs, null, marks || node.marks);
if (node.isLeaf)
return tr.replaceWith(pos, pos + node.nodeSize, newNode);
if (!type.validContent(node.content))
throw new RangeError("Invalid content for node type " + type.name);
tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1, new Slice(Fragment.from(newNode), 0, 0), 1, true));
}
function canSplit(doc2, pos, depth = 1, typesAfter) {
let $pos = doc2.resolve(pos), base2 = $pos.depth - depth;
let innerType = typesAfter && typesAfter[typesAfter.length - 1] || $pos.parent;
if (base2 < 0 || $pos.parent.type.spec.isolating || !$pos.parent.canReplace($pos.index(), $pos.parent.childCount) || !innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount)))
return false;
for (let d = $pos.depth - 1, i = depth - 2; d > base2; d--, i--) {
let node = $pos.node(d), index2 = $pos.index(d);
if (node.type.spec.isolating)
return false;
let rest = node.content.cutByIndex(index2, node.childCount);
let after = typesAfter && typesAfter[i] || node;
if (after != node)
rest = rest.replaceChild(0, after.type.create(after.attrs));
if (!node.canReplace(index2 + 1, node.childCount) || !after.type.validContent(rest))
return false;
}
let index = $pos.indexAfter(base2);
let baseType = typesAfter && typesAfter[0];
return $pos.node(base2).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base2 + 1).type);
}
function split(tr, pos, depth = 1, typesAfter) {
let $pos = tr.doc.resolve(pos), before = Fragment.empty, after = Fragment.empty;
for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
before = Fragment.from($pos.node(d).copy(before));
let typeAfter = typesAfter && typesAfter[i];
after = Fragment.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after));
}
tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true));
}
function canJoin(doc2, pos) {
let $pos = doc2.resolve(pos), index = $pos.index();
return joinable($pos.nodeBefore, $pos.nodeAfter) && $pos.parent.canReplace(index, index + 1);
}
function joinable(a, b) {
return !!(a && b && !a.isLeaf && a.canAppend(b));
}
function joinPoint(doc2, pos, dir = -1) {
let $pos = doc2.resolve(pos);
for (let d = $pos.depth; ; d--) {
let before, after, index = $pos.index(d);
if (d == $pos.depth) {
before = $pos.nodeBefore;
after = $pos.nodeAfter;
} else if (dir > 0) {
before = $pos.node(d + 1);
index++;
after = $pos.node(d).maybeChild(index);
} else {
before = $pos.node(d).maybeChild(index - 1);
after = $pos.node(d + 1);
}
if (before && !before.isTextblock && joinable(before, after) && $pos.node(d).canReplace(index, index + 1))
return pos;
if (d == 0)
break;
pos = dir < 0 ? $pos.before(d) : $pos.after(d);
}
}
function join(tr, pos, depth) {
let step = new ReplaceStep(pos - depth, pos + depth, Slice.empty, true);
tr.step(step);
}
function insertPoint(doc2, pos, nodeType) {
let $pos = doc2.resolve(pos);
if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType))
return pos;
if ($pos.parentOffset == 0)
for (let d = $pos.depth - 1; d >= 0; d--) {
let index = $pos.index(d);
if ($pos.node(d).canReplaceWith(index, index, nodeType))
return $pos.before(d + 1);
if (index > 0)
return null;
}
if ($pos.parentOffset == $pos.parent.content.size)
for (let d = $pos.depth - 1; d >= 0; d--) {
let index = $pos.indexAfter(d);
if ($pos.node(d).canReplaceWith(index, index, nodeType))
return $pos.after(d + 1);
if (index < $pos.node(d).childCount)
return null;
}
return null;
}
function dropPoint(doc2, pos, slice2) {
let $pos = doc2.resolve(pos);
if (!slice2.content.size)
return pos;
let content = slice2.content;
for (let i = 0; i < slice2.openStart; i++)
content = content.firstChild.content;
for (let pass = 1; pass <= (slice2.openStart == 0 && slice2.size ? 2 : 1); pass++) {
for (let d = $pos.depth; d >= 0; d--) {
let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1;
let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0);
let parent = $pos.node(d), fits = false;
if (pass == 1) {
fits = parent.canReplace(insertPos, insertPos, content);
} else {
let wrapping = parent.contentMatchAt(insertPos).findWrapping(content.firstChild.type);
fits = wrapping && parent.canReplaceWith(insertPos, insertPos, wrapping[0]);
}
if (fits)
return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1);
}
}
return null;
}
function replaceStep(doc2, from2, to = from2, slice2 = Slice.empty) {
if (from2 == to && !slice2.size)
return null;
let $from = doc2.resolve(from2), $to = doc2.resolve(to);
if (fitsTrivially($from, $to, slice2))
return new ReplaceStep(from2, to, slice2);
return new Fitter($from, $to, slice2).fit();
}
function fitsTrivially($from, $to, slice2) {
return !slice2.openStart && !slice2.openEnd && $from.start() == $to.start() && $from.parent.canReplace($from.index(), $to.index(), slice2.content);
}
class Fitter {
constructor($from, $to, unplaced) {
this.$from = $from;
this.$to = $to;
this.unplaced = unplaced;
this.frontier = [];
this.placed = Fragment.empty;
for (let i = 0; i <= $from.depth; i++) {
let node = $from.node(i);
this.frontier.push({
type: node.type,
match: node.contentMatchAt($from.indexAfter(i))
});
}
for (let i = $from.depth; i > 0; i--)
this.placed = Fragment.from($from.node(i).copy(this.placed));
}
get depth() {
return this.frontier.length - 1;
}
fit() {
while (this.unplaced.size) {
let fit = this.findFittable();
if (fit)
this.placeNodes(fit);
else
this.openMore() || this.dropNode();
}
let moveInline = this.mustMoveInline(), placedSize = this.placed.size - this.depth - this.$from.depth;
let $from = this.$from, $to = this.close(moveInline < 0 ? this.$to : $from.doc.resolve(moveInline));
if (!$to)
return null;
let content = this.placed, openStart = $from.depth, openEnd = $to.depth;
while (openStart && openEnd && content.childCount == 1) {
content = content.firstChild.content;
openStart--;
openEnd--;
}
let slice2 = new Slice(content, openStart, openEnd);
if (moveInline > -1)
return new ReplaceAroundStep($from.pos, moveInline, this.$to.pos, this.$to.end(), slice2, placedSize);
if (slice2.size || $from.pos != this.$to.pos)
return new ReplaceStep($from.pos, $to.pos, slice2);
return null;
}
// Find a position on the start spine of `this.unplaced` that has
// content that can be moved somewhere on the frontier. Returns two
// depths, one for the slice and one for the frontier.
findFittable() {
let startDepth = this.unplaced.openStart;
for (let cur = this.unplaced.content, d = 0, openEnd = this.unplaced.openEnd; d < startDepth; d++) {
let node = cur.firstChild;
if (cur.childCount > 1)
openEnd = 0;
if (node.type.spec.isolating && openEnd <= d) {
startDepth = d;
break;
}
cur = node.content;
}
for (let pass = 1; pass <= 2; pass++) {
for (let sliceDepth = pass == 1 ? startDepth : this.unplaced.openStart; sliceDepth >= 0; sliceDepth--) {
let fragment, parent = null;
if (sliceDepth) {
parent = contentAt(this.unplaced.content, sliceDepth - 1).firstChild;
fragment = parent.content;
} else {
fragment = this.unplaced.content;
}
let first2 = fragment.firstChild;
for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) {
let { type, match } = this.frontier[frontierDepth], wrap2, inject = null;
if (pass == 1 && (first2 ? match.matchType(first2.type) || (inject = match.fillBefore(Fragment.from(first2), false)) : parent && type.compatibleContent(parent.type)))
return { sliceDepth, frontierDepth, parent, inject };
else if (pass == 2 && first2 && (wrap2 = match.findWrapping(first2.type)))
return { sliceDepth, frontierDepth, parent, wrap: wrap2 };
if (parent && match.matchType(parent.type))
break;
}
}
}
}
openMore() {
let { content, openStart, openEnd } = this.unplaced;
let inner = contentAt(content, openStart);
if (!inner.childCount || inner.firstChild.isLeaf)
return false;
this.unplaced = new Slice(content, openStart + 1, Math.max(openEnd, inner.size + openStart >= content.size - openEnd ? openStart + 1 : 0));
return true;
}
dropNode() {
let { content, openStart, openEnd } = this.unplaced;
let inner = contentAt(content, openStart);
if (inner.childCount <= 1 && openStart > 0) {
let openAtEnd = content.size - openStart <= openStart + inner.size;
this.unplaced = new Slice(dropFromFragment(content, openStart - 1, 1), openStart - 1, openAtEnd ? openStart - 1 : openEnd);
} else {
this.unplaced = new Slice(dropFromFragment(content, openStart, 1), openStart, openEnd);
}
}
// Move content from the unplaced slice at `sliceDepth` to the
// frontier node at `frontierDepth`. Close that frontier node when
// applicable.
placeNodes({ sliceDepth, frontierDepth, parent, inject, wrap: wrap2 }) {
while (this.depth > frontierDepth)
this.closeFrontierNode();
if (wrap2)
for (let i = 0; i < wrap2.length; i++)
this.openFrontierNode(wrap2[i]);
let slice2 = this.unplaced, fragment = parent ? parent.content : slice2.content;
let openStart = slice2.openStart - sliceDepth;
let taken = 0, add = [];
let { match, type } = this.frontier[frontierDepth];
if (inject) {
for (let i = 0; i < inject.childCount; i++)
add.push(inject.child(i));
match = match.matchFragment(inject);
}
let openEndCount = fragment.size + sliceDepth - (slice2.content.size - slice2.openEnd);
while (taken < fragment.childCount) {
let next = fragment.child(taken), matches2 = match.matchType(next.type);
if (!matches2)
break;
taken++;
if (taken > 1 || openStart == 0 || next.content.size) {
match = matches2;
add.push(closeNodeStart(next.mark(type.allowedMarks(next.marks)), taken == 1 ? openStart : 0, taken == fragment.childCount ? openEndCount : -1));
}
}
let toEnd = taken == fragment.childCount;
if (!toEnd)
openEndCount = -1;
this.placed = addToFragment(this.placed, frontierDepth, Fragment.from(add));
this.frontier[frontierDepth].match = match;
if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type && this.frontier.length > 1)
this.closeFrontierNode();
for (let i = 0, cur = fragment; i < openEndCount; i++) {
let node = cur.lastChild;
this.frontier.push({ type: node.type, match: node.contentMatchAt(node.childCount) });
cur = node.content;
}
this.unplaced = !toEnd ? new Slice(dropFromFragment(slice2.content, sliceDepth, taken), slice2.openStart, slice2.openEnd) : sliceDepth == 0 ? Slice.empty : new Slice(dropFromFragment(slice2.content, sliceDepth - 1, 1), sliceDepth - 1, openEndCount < 0 ? slice2.openEnd : sliceDepth - 1);
}
mustMoveInline() {
if (!this.$to.parent.isTextblock)
return -1;
let top = this.frontier[this.depth], level;
if (!top.type.isTextblock || !contentAfterFits(this.$to, this.$to.depth, top.type, top.match, false) || this.$to.depth == this.depth && (level = this.findCloseLevel(this.$to)) && level.depth == this.depth)
return -1;
let { depth } = this.$to, after = this.$to.after(depth);
while (depth > 1 && after == this.$to.end(--depth))
++after;
return after;
}
findCloseLevel($to) {
scan:
for (let i = Math.min(this.depth, $to.depth); i >= 0; i--) {
let { match, type } = this.frontier[i];
let dropInner = i < $to.depth && $to.end(i + 1) == $to.pos + ($to.depth - (i + 1));
let fit = contentAfterFits($to, i, type, match, dropInner);
if (!fit)
continue;
for (let d = i - 1; d >= 0; d--) {
let { match: match2, type: type2 } = this.frontier[d];
let matches2 = contentAfterFits($to, d, type2, match2, true);
if (!matches2 || matches2.childCount)
continue scan;
}
return { depth: i, fit, move: dropInner ? $to.doc.resolve($to.after(i + 1)) : $to };
}
}
close($to) {
let close2 = this.findCloseLevel($to);
if (!close2)
return null;
while (this.depth > close2.depth)
this.closeFrontierNode();
if (close2.fit.childCount)
this.placed = addToFragment(this.placed, close2.depth, close2.fit);
$to = close2.move;
for (let d = close2.depth + 1; d <= $to.depth; d++) {
let node = $to.node(d), add = node.type.contentMatch.fillBefore(node.content, true, $to.index(d));
this.openFrontierNode(node.type, node.attrs, add);
}
return $to;
}
openFrontierNode(type, attrs = null, content) {
let top = this.frontier[this.depth];
top.match = top.match.matchType(type);
this.placed = addToFragment(this.placed, this.depth, Fragment.from(type.create(attrs, content)));
this.frontier.push({ type, match: type.contentMatch });
}
closeFrontierNode() {
let open = this.frontier.pop();
let add = open.match.fillBefore(Fragment.empty, true);
if (add.childCount)
this.placed = addToFragment(this.placed, this.frontier.length, add);
}
}
function dropFromFragment(fragment, depth, count) {
if (depth == 0)
return fragment.cutByIndex(count, fragment.childCount);
return fragment.replaceChild(0, fragment.firstChild.copy(dropFromFragment(fragment.firstChild.content, depth - 1, count)));
}
function addToFragment(fragment, depth, content) {
if (depth == 0)
return fragment.append(content);
return fragment.replaceChild(fragment.childCount - 1, fragment.lastChild.copy(addToFragment(fragment.lastChild.content, depth - 1, content)));
}
function contentAt(fragment, depth) {
for (let i = 0; i < depth; i++)
fragment = fragment.firstChild.content;
return fragment;
}
function closeNodeStart(node, openStart, openEnd) {
if (openStart <= 0)
return node;
let frag = node.content;
if (openStart > 1)
frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0));
if (openStart > 0) {
frag = node.type.contentMatch.fillBefore(frag).append(frag);
if (openEnd <= 0)
frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(Fragment.empty, true));
}
return node.copy(frag);
}
function contentAfterFits($to, depth, type, match, open) {
let node = $to.node(depth), index = open ? $to.indexAfter(depth) : $to.index(depth);
if (index == node.childCount && !type.compatibleContent(node.type))
return null;
let fit = match.fillBefore(node.content, true, index);
return fit && !invalidMarks(type, node.content, index) ? fit : null;
}
function invalidMarks(type, fragment, start) {
for (let i = start; i < fragment.childCount; i++)
if (!type.allowsMarks(fragment.child(i).marks))
return true;
return false;
}
function definesContent(type) {
return type.spec.defining || type.spec.definingForContent;
}
function replaceRange(tr, from2, to, slice2) {
if (!slice2.size)
return tr.deleteRange(from2, to);
let $from = tr.doc.resolve(from2), $to = tr.doc.resolve(to);
if (fitsTrivially($from, $to, slice2))
return tr.step(new ReplaceStep(from2, to, slice2));
let targetDepths = coveredDepths($from, tr.doc.resolve(to));
if (targetDepths[targetDepths.length - 1] == 0)
targetDepths.pop();
let preferredTarget = -($from.depth + 1);
targetDepths.unshift(preferredTarget);
for (let d = $from.depth, pos = $from.pos - 1; d > 0; d--, pos--) {
let spec = $from.node(d).type.spec;
if (spec.defining || spec.definingAsContext || spec.isolating)
break;
if (targetDepths.indexOf(d) > -1)
preferredTarget = d;
else if ($from.before(d) == pos)
targetDepths.splice(1, 0, -d);
}
let preferredTargetIndex = targetDepths.indexOf(preferredTarget);
let leftNodes = [], preferredDepth = slice2.openStart;
for (let content = slice2.content, i = 0; ; i++) {
let node = content.firstChild;
leftNodes.push(node);
if (i == slice2.openStart)
break;
content = node.content;
}
for (let d = preferredDepth - 1; d >= 0; d--) {
let type = leftNodes[d].type, def = definesContent(type);
if (def && $from.node(preferredTargetIndex).type != type)
preferredDepth = d;
else if (def || !type.isTextblock)
break;
}
for (let j = slice2.openStart; j >= 0; j--) {
let openDepth = (j + preferredDepth + 1) % (slice2.openStart + 1);
let insert = leftNodes[openDepth];
if (!insert)
continue;
for (let i = 0; i < targetDepths.length; i++) {
let targetDepth = targetDepths[(i + preferredTargetIndex) % targetDepths.length], expand = true;
if (targetDepth < 0) {
expand = false;
targetDepth = -targetDepth;
}
let parent = $from.node(targetDepth - 1), index = $from.index(targetDepth - 1);
if (parent.canReplaceWith(index, index, insert.type, insert.marks))
return tr.replace($from.before(targetDepth), expand ? $to.after(targetDepth) : to, new Slice(closeFragment(slice2.content, 0, slice2.openStart, openDepth), openDepth, slice2.openEnd));
}
}
let startSteps = tr.steps.length;
for (let i = targetDepths.length - 1; i >= 0; i--) {
tr.replace(from2, to, slice2);
if (tr.steps.length > startSteps)
break;
let depth = targetDepths[i];
if (depth < 0)
continue;
from2 = $from.before(depth);
to = $to.after(depth);
}
}
function closeFragment(fragment, depth, oldOpen, newOpen, parent) {
if (depth < oldOpen) {
let first2 = fragment.firstChild;
fragment = fragment.replaceChild(0, first2.copy(closeFragment(first2.content, depth + 1, oldOpen, newOpen, first2)));
}
if (depth > newOpen) {
let match = parent.contentMatchAt(0);
let start = match.fillBefore(fragment).append(fragment);
fragment = start.append(match.matchFragment(start).fillBefore(Fragment.empty, true));
}
return fragment;
}
function replaceRangeWith(tr, from2, to, node) {
if (!node.isInline && from2 == to && tr.doc.resolve(from2).parent.content.size) {
let point = insertPoint(tr.doc, from2, node.type);
if (point != null)
from2 = to = point;
}
tr.replaceRange(from2, to, new Slice(Fragment.from(node), 0, 0));
}
function deleteRange$1(tr, from2, to) {
let $from = tr.doc.resolve(from2), $to = tr.doc.resolve(to);
let covered = coveredDepths($from, $to);
for (let i = 0; i < covered.length; i++) {
let depth = covered[i], last = i == covered.length - 1;
if (last && depth == 0 || $from.node(depth).type.contentMatch.validEnd)
return tr.delete($from.start(depth), $to.end(depth));
if (depth > 0 && (last || $from.node(depth - 1).canReplace($from.index(depth - 1), $to.indexAfter(depth - 1))))
return tr.delete($from.before(depth), $to.after(depth));
}
for (let d = 1; d <= $from.depth && d <= $to.depth; d++) {
if (from2 - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d)
return tr.delete($from.before(d), to);
}
tr.delete(from2, to);
}
function coveredDepths($from, $to) {
let result = [], minDepth = Math.min($from.depth, $to.depth);
for (let d = minDepth; d >= 0; d--) {
let start = $from.start(d);
if (start < $from.pos - ($from.depth - d) || $to.end(d) > $to.pos + ($to.depth - d) || $from.node(d).type.spec.isolating || $to.node(d).type.spec.isolating)
break;
if (start == $to.start(d) || d == $from.depth && d == $to.depth && $from.parent.inlineContent && $to.parent.inlineContent && d && $to.start(d - 1) == start - 1)
result.push(d);
}
return result;
}
class AttrStep extends Step {
/**
Construct an attribute step.
*/
constructor(pos, attr, value) {
super();
this.pos = pos;
this.attr = attr;
this.value = value;
}
apply(doc2) {
let node = doc2.nodeAt(this.pos);
if (!node)
return StepResult.fail("No node at attribute step's position");
let attrs = /* @__PURE__ */ Object.create(null);
for (let name in node.attrs)
attrs[name] = node.attrs[name];
attrs[this.attr] = this.value;
let updated = node.type.create(attrs, null, node.marks);
return StepResult.fromReplace(doc2, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1));
}
getMap() {
return StepMap.empty;
}
invert(doc2) {
return new AttrStep(this.pos, this.attr, doc2.nodeAt(this.pos).attrs[this.attr]);
}
map(mapping) {
let pos = mapping.mapResult(this.pos, 1);
return pos.deletedAfter ? null : new AttrStep(pos.pos, this.attr, this.value);
}
toJSON() {
return { stepType: "attr", pos: this.pos, attr: this.attr, value: this.value };
}
static fromJSON(schema, json) {
if (typeof json.pos != "number" || typeof json.attr != "string")
throw new RangeError("Invalid input for AttrStep.fromJSON");
return new AttrStep(json.pos, json.attr, json.value);
}
}
Step.jsonID("attr", AttrStep);
let TransformError = class extends Error {
};
TransformError = function TransformError2(message) {
let err = Error.call(this, message);
err.__proto__ = TransformError2.prototype;
return err;
};
TransformError.prototype = Object.create(Error.prototype);
TransformError.prototype.constructor = TransformError;
TransformError.prototype.name = "TransformError";
class Transform {
/**
Create a transform that starts with the given document.
*/
constructor(doc2) {
this.doc = doc2;
this.steps = [];
this.docs = [];
this.mapping = new Mapping();
}
/**
The starting document.
*/
get before() {
return this.docs.length ? this.docs[0] : this.doc;
}
/**
Apply a new step in this transform, saving the result. Throws an
error when the step fails.
*/
step(step) {
let result = this.maybeStep(step);
if (result.failed)
throw new TransformError(result.failed);
return this;
}
/**
Try to apply a step in this transformation, ignoring it if it
fails. Returns the step result.
*/
maybeStep(step) {
let result = step.apply(this.doc);
if (!result.failed)
this.addStep(step, result.doc);
return result;
}
/**
True when the document has been changed (when there are any
steps).
*/
get docChanged() {
return this.steps.length > 0;
}
/**
@internal
*/
addStep(step, doc2) {
this.docs.push(this.doc);
this.steps.push(step);
this.mapping.appendMap(step.getMap());
this.doc = doc2;
}
/**
Replace the part of the document between `from` and `to` with the
given `slice`.
*/
replace(from2, to = from2, slice2 = Slice.empty) {
let step = replaceStep(this.doc, from2, to, slice2);
if (step)
this.step(step);
return this;
}
/**
Replace the given range with the given content, which may be a
fragment, node, or array of nodes.
*/
replaceWith(from2, to, content) {
return this.replace(from2, to, new Slice(Fragment.from(content), 0, 0));
}
/**
Delete the content between the given positions.
*/
delete(from2, to) {
return this.replace(from2, to, Slice.empty);
}
/**
Insert the given content at the given position.
*/
insert(pos, content) {
return this.replaceWith(pos, pos, content);
}
/**
Replace a range of the document with a given slice, using
`from`, `to`, and the slice's
[`openStart`](https://prosemirror.net/docs/ref/#model.Slice.openStart) property as hints, rather
than fixed start and end points. This method may grow the
replaced area or close open nodes in the slice in order to get a
fit that is more in line with WYSIWYG expectations, by dropping
fully covered parent nodes of the replaced region when they are
marked [non-defining as
open parent node from the slice that _is_ marked as [defining
This is the method, for example, to handle paste. The similar
primitive tool which will _not_ move the start and end of its given
range, and is useful in situations where you need more precise
control over what happens.
*/
replaceRange(from2, to, slice2) {
replaceRange(this, from2, to, slice2);
return this;
}
/**
Replace the given range with a node, but use `from` and `to` as
hints, rather than precise positions. When from and to are the same
and are at the start or end of a parent node in which the given
node doesn't fit, this method may _move_ them out towards a parent
that does allow the given node to be placed. When the given range
completely covers a parent node, this method may completely replace
that parent node.
*/
replaceRangeWith(from2, to, node) {
replaceRangeWith(this, from2, to, node);
return this;
}
/**
Delete the given range, expanding it to cover fully covered
parent nodes until a valid replace is found.
*/
deleteRange(from2, to) {
deleteRange$1(this, from2, to);
return this;
}
/**
Split the content in the given range off from its parent, if there
is sibling content before or after it, and move it up the tree to
the depth specified by `target`. You'll probably want to use
[`liftTarget`](https://prosemirror.net/docs/ref/#transform.liftTarget) to compute `target`, to make
sure the lift is valid.
*/
lift(range, target) {
lift$2(this, range, target);
return this;
}
/**
Join the blocks around the given position. If depth is 2, their
last and first siblings are also joined, and so on.
*/
join(pos, depth = 1) {
join(this, pos, depth);
return this;
}
/**
Wrap the given [range](https://prosemirror.net/docs/ref/#model.NodeRange) in the given set of wrappers.
The wrappers are assumed to be valid in this position, and should
probably be computed with [`findWrapping`](https://prosemirror.net/docs/ref/#transform.findWrapping).
*/
wrap(range, wrappers) {
wrap(this, range, wrappers);
return this;
}
/**
Set the type of all textblocks (partly) between `from` and `to` to
the given node type with the given attributes.
*/
setBlockType(from2, to = from2, type, attrs = null) {
setBlockType$1(this, from2, to, type, attrs);
return this;
}
/**
Change the type, attributes, and/or marks of the node at `pos`.
When `type` isn't given, the existing node type is preserved,
*/
setNodeMarkup(pos, type, attrs = null, marks) {
setNodeMarkup(this, pos, type, attrs, marks);
return this;
}
/**
Set a single attribute on a given node to a new value.
*/
setNodeAttribute(pos, attr, value) {
this.step(new AttrStep(pos, attr, value));
return this;
}
/**
Add a mark to the node at position `pos`.
*/
addNodeMark(pos, mark) {
this.step(new AddNodeMarkStep(pos, mark));
return this;
}
/**
Remove a mark (or a mark of the given type) from the node at
position `pos`.
*/
removeNodeMark(pos, mark) {
if (!(mark instanceof Mark$1)) {
let node = this.doc.nodeAt(pos);
if (!node)
throw new RangeError("No node at position " + pos);
mark = mark.isInSet(node.marks);
if (!mark)
return this;
}
this.step(new RemoveNodeMarkStep(pos, mark));
return this;
}
/**
Split the node at the given position, and optionally, if `depth` is
greater than one, any number of nodes above that. By default, the
parts split off will inherit the node type of the original node.
This can be changed by passing an array of types and attributes to
use after the split.
*/
split(pos, depth = 1, typesAfter) {
split(this, pos, depth, typesAfter);
return this;
}
/**
Add the given mark to the inline content between `from` and `to`.
*/
addMark(from2, to, mark) {
addMark(this, from2, to, mark);
return this;
}
/**
Remove marks from inline nodes between `from` and `to`. When
`mark` is a single mark, remove precisely that mark. When it is
a mark type, remove all marks of that type. When it is null,
remove all marks of any type.
*/
removeMark(from2, to, mark) {
removeMark(this, from2, to, mark);
return this;
}
/**
Removes all marks and nodes from the content of the node at
`pos` that don't match the given new parent node type. Accepts
an optional starting [content match](https://prosemirror.net/docs/ref/#model.ContentMatch) as
third argument.
*/
clearIncompatible(pos, parentType, match) {
clearIncompatible(this, pos, parentType, match);
return this;
}
}
const classesById = /* @__PURE__ */ Object.create(null);
class Selection {
/**
Initialize a selection with the head and anchor and ranges. If no
ranges are given, constructs a single range across `$anchor` and
`$head`.
*/
constructor($anchor, $head, ranges) {
this.$anchor = $anchor;
this.$head = $head;
this.ranges = ranges || [new SelectionRange($anchor.min($head), $anchor.max($head))];
}
/**
The selection's anchor, as an unresolved position.
*/
get anchor() {
return this.$anchor.pos;
}
/**
The selection's head.
*/
get head() {
return this.$head.pos;
}
/**
The lower bound of the selection's main range.
*/
get from() {
return this.$from.pos;
}
/**
The upper bound of the selection's main range.
*/
get to() {
return this.$to.pos;
}
/**
The resolved lower bound of the selection's main range.
*/
get $from() {
return this.ranges[0].$from;
}
/**
The resolved upper bound of the selection's main range.
*/
get $to() {
return this.ranges[0].$to;
}
/**
Indicates whether the selection contains any content.
*/
get empty() {
let ranges = this.ranges;
for (let i = 0; i < ranges.length; i++)
if (ranges[i].$from.pos != ranges[i].$to.pos)
return false;
return true;
}
/**
Get the content of this selection as a slice.
*/
content() {
return this.$from.doc.slice(this.from, this.to, true);
}
/**
Replace the selection with a slice or, if no slice is given,
delete the selection. Will append to the given transaction.
*/
replace(tr, content = Slice.empty) {
let lastNode = content.content.lastChild, lastParent = null;
for (let i = 0; i < content.openEnd; i++) {
lastParent = lastNode;
lastNode = lastNode.lastChild;
}
let mapFrom = tr.steps.length, ranges = this.ranges;
for (let i = 0; i < ranges.length; i++) {
let { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom);
tr.replaceRange(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content);
if (i == 0)
selectionToInsertionEnd$1(tr, mapFrom, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1);
}
}
/**
Replace the selection with the given node, appending the changes
to the given transaction.
*/
replaceWith(tr, node) {
let mapFrom = tr.steps.length, ranges = this.ranges;
for (let i = 0; i < ranges.length; i++) {
let { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom);
let from2 = mapping.map($from.pos), to = mapping.map($to.pos);
if (i) {
tr.deleteRange(from2, to);
} else {
tr.replaceRangeWith(from2, to, node);
selectionToInsertionEnd$1(tr, mapFrom, node.isInline ? -1 : 1);
}
}
}
/**
Find a valid cursor or leaf node selection starting at the given
position and searching back if `dir` is negative, and forward if
positive. When `textOnly` is true, only consider cursor
selections. Will return null when no valid selection position is
found.
*/
static findFrom($pos, dir, textOnly = false) {
let inner = $pos.parent.inlineContent ? new TextSelection($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
if (inner)
return inner;
for (let depth = $pos.depth - 1; depth >= 0; depth--) {
let found2 = dir < 0 ? findSelectionIn($pos.node(0), $pos.node(depth), $pos.before(depth + 1), $pos.index(depth), dir, textOnly) : findSelectionIn($pos.node(0), $pos.node(depth), $pos.after(depth + 1), $pos.index(depth) + 1, dir, textOnly);
if (found2)
return found2;
}
return null;
}
/**
Find a valid cursor or leaf node selection near the given
position. Searches forward first by default, but if `bias` is
negative, it will search backwards first.
*/
static near($pos, bias = 1) {
return this.findFrom($pos, bias) || this.findFrom($pos, -bias) || new AllSelection($pos.node(0));
}
/**
Find the cursor or leaf node selection closest to the start of
the given document. Will return an
[`AllSelection`](https://prosemirror.net/docs/ref/#state.AllSelection) if no valid position
exists.
*/
static atStart(doc2) {
return findSelectionIn(doc2, doc2, 0, 0, 1) || new AllSelection(doc2);
}
/**
Find the cursor or leaf node selection closest to the end of the
given document.
*/
static atEnd(doc2) {
return findSelectionIn(doc2, doc2, doc2.content.size, doc2.childCount, -1) || new AllSelection(doc2);
}
/**
Deserialize the JSON representation of a selection. Must be
implemented for custom classes (as a static class method).
*/
static fromJSON(doc2, json) {
if (!json || !json.type)
throw new RangeError("Invalid input for Selection.fromJSON");
let cls = classesById[json.type];
if (!cls)
throw new RangeError(`No selection type ${json.type} defined`);
return cls.fromJSON(doc2, json);
}
/**
To be able to deserialize selections from JSON, custom selection
classes must register themselves with an ID string, so that they
can be disambiguated. Try to pick something that's unlikely to
clash with classes from other modules.
*/
static jsonID(id, selectionClass) {
if (id in classesById)
throw new RangeError("Duplicate use of selection JSON ID " + id);
classesById[id] = selectionClass;
selectionClass.prototype.jsonID = id;
return selectionClass;
}
/**
Get a [bookmark](https://prosemirror.net/docs/ref/#state.SelectionBookmark) for this selection,
which is a value that can be mapped without having access to a
current document, and later resolved to a real selection for a
given document again. (This is used mostly by the history to
track and restore old selections.) The default implementation of
this method just converts the selection to a text selection and
returns the bookmark for that.
*/
getBookmark() {
return TextSelection.between(this.$anchor, this.$head).getBookmark();
}
}
Selection.prototype.visible = true;
class SelectionRange {
/**
Create a range.
*/
constructor($from, $to) {
this.$from = $from;
this.$to = $to;
}
}
let warnedAboutTextSelection = false;
function checkTextSelection($pos) {
if (!warnedAboutTextSelection && !$pos.parent.inlineContent) {
warnedAboutTextSelection = true;
console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
}
}
class TextSelection extends Selection {
/**
Construct a text selection between the given points.
*/
constructor($anchor, $head = $anchor) {
checkTextSelection($anchor);
checkTextSelection($head);
super($anchor, $head);
}
/**
Returns a resolved position if this is a cursor selection (an
empty text selection), and null otherwise.
*/
get $cursor() {
return this.$anchor.pos == this.$head.pos ? this.$head : null;
}
map(doc2, mapping) {
let $head = doc2.resolve(mapping.map(this.head));
if (!$head.parent.inlineContent)
return Selection.near($head);
let $anchor = doc2.resolve(mapping.map(this.anchor));
return new TextSelection($anchor.parent.inlineContent ? $anchor : $head, $head);
}
replace(tr, content = Slice.empty) {
super.replace(tr, content);
if (content == Slice.empty) {
let marks = this.$from.marksAcross(this.$to);
if (marks)
tr.ensureMarks(marks);
}
}
eq(other) {
return other instanceof TextSelection && other.anchor == this.anchor && other.head == this.head;
}
getBookmark() {
return new TextBookmark(this.anchor, this.head);
}
toJSON() {
return { type: "text", anchor: this.anchor, head: this.head };
}
/**
@internal
*/
static fromJSON(doc2, json) {
if (typeof json.anchor != "number" || typeof json.head != "number")
throw new RangeError("Invalid input for TextSelection.fromJSON");
return new TextSelection(doc2.resolve(json.anchor), doc2.resolve(json.head));
}
/**
Create a text selection from non-resolved positions.
*/
static create(doc2, anchor, head = anchor) {
let $anchor = doc2.resolve(anchor);
return new this($anchor, head == anchor ? $anchor : doc2.resolve(head));
}
/**
Return a text selection that spans the given positions or, if
they aren't text positions, find a text selection near them.
`bias` determines whether the method searches forward (default)
or backwards (negative number) first. Will fall back to calling
[`Selection.near`](https://prosemirror.net/docs/ref/#state.Selection^near) when the document
doesn't contain a valid text position.
*/
static between($anchor, $head, bias) {
let dPos = $anchor.pos - $head.pos;
if (!bias || dPos)
bias = dPos >= 0 ? 1 : -1;
if (!$head.parent.inlineContent) {
let found2 = Selection.findFrom($head, bias, true) || Selection.findFrom($head, -bias, true);
if (found2)
$head = found2.$head;
else
return Selection.near($head, bias);
}
if (!$anchor.parent.inlineContent) {
if (dPos == 0) {
$anchor = $head;
} else {
$anchor = (Selection.findFrom($anchor, -bias, true) || Selection.findFrom($anchor, bias, true)).$anchor;
if ($anchor.pos < $head.pos != dPos < 0)
$anchor = $head;
}
}
return new TextSelection($anchor, $head);
}
}
Selection.jsonID("text", TextSelection);
class TextBookmark {
constructor(anchor, head) {
this.anchor = anchor;
this.head = head;
}
map(mapping) {
return new TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
}
resolve(doc2) {
return TextSelection.between(doc2.resolve(this.anchor), doc2.resolve(this.head));
}
}
class NodeSelection extends Selection {
/**
Create a node selection. Does not verify the validity of its
argument.
*/
constructor($pos) {
let node = $pos.nodeAfter;
let $end = $pos.node(0).resolve($pos.pos + node.nodeSize);
super($pos, $end);
this.node = node;
}
map(doc2, mapping) {
let { deleted, pos } = mapping.mapResult(this.anchor);
let $pos = doc2.resolve(pos);
if (deleted)
return Selection.near($pos);
return new NodeSelection($pos);
}
content() {
return new Slice(Fragment.from(this.node), 0, 0);
}
eq(other) {
return other instanceof NodeSelection && other.anchor == this.anchor;
}
toJSON() {
return { type: "node", anchor: this.anchor };
}
getBookmark() {
return new NodeBookmark(this.anchor);
}
/**
@internal
*/
static fromJSON(doc2, json) {
if (typeof json.anchor != "number")
throw new RangeError("Invalid input for NodeSelection.fromJSON");
return new NodeSelection(doc2.resolve(json.anchor));
}
/**
Create a node selection from non-resolved positions.
*/
static create(doc2, from2) {
return new NodeSelection(doc2.resolve(from2));
}
/**
Determines whether the given node may be selected as a node
selection.
*/
static isSelectable(node) {
return !node.isText && node.type.spec.selectable !== false;
}
}
NodeSelection.prototype.visible = false;
Selection.jsonID("node", NodeSelection);
class NodeBookmark {
constructor(anchor) {
this.anchor = anchor;
}
map(mapping) {
let { deleted, pos } = mapping.mapResult(this.anchor);
return deleted ? new TextBookmark(pos, pos) : new NodeBookmark(pos);
}
resolve(doc2) {
let $pos = doc2.resolve(this.anchor), node = $pos.nodeAfter;
if (node && NodeSelection.isSelectable(node))
return new NodeSelection($pos);
return Selection.near($pos);
}
}
class AllSelection extends Selection {
/**
Create an all-selection over the given document.
*/
constructor(doc2) {
super(doc2.resolve(0), doc2.resolve(doc2.content.size));
}
replace(tr, content = Slice.empty) {
if (content == Slice.empty) {
tr.delete(0, tr.doc.content.size);
let sel = Selection.atStart(tr.doc);
if (!sel.eq(tr.selection))
tr.setSelection(sel);
} else {
super.replace(tr, content);
}
}
toJSON() {
return { type: "all" };
}
/**
@internal
*/
static fromJSON(doc2) {
return new AllSelection(doc2);
}
map(doc2) {
return new AllSelection(doc2);
}
eq(other) {
return other instanceof AllSelection;
}
getBookmark() {
return AllBookmark;
}
}
Selection.jsonID("all", AllSelection);
const AllBookmark = {
map() {
return this;
},
resolve(doc2) {
return new AllSelection(doc2);
}
};
function findSelectionIn(doc2, node, pos, index, dir, text2 = false) {
if (node.inlineContent)
return TextSelection.create(doc2, pos);
for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
let child = node.child(i);
if (!child.isAtom) {
let inner = findSelectionIn(doc2, child, pos + dir, dir < 0 ? child.childCount : 0, dir, text2);
if (inner)
return inner;
} else if (!text2 && NodeSelection.isSelectable(child)) {
return NodeSelection.create(doc2, pos - (dir < 0 ? child.nodeSize : 0));
}
pos += child.nodeSize * dir;
}
return null;
}
function selectionToInsertionEnd$1(tr, startLen, bias) {
let last = tr.steps.length - 1;
if (last < startLen)
return;
let step = tr.steps[last];
if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep))
return;
let map2 = tr.mapping.maps[last], end;
map2.forEach((_from, _to, _newFrom, newTo) => {
if (end == null)
end = newTo;
});
tr.setSelection(Selection.near(tr.doc.resolve(end), bias));
}
const UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4;
class Transaction extends Transform {
/**
@internal
*/
constructor(state) {
super(state.doc);
this.curSelectionFor = 0;
this.updated = 0;
this.meta = /* @__PURE__ */ Object.create(null);
this.time = Date.now();
this.curSelection = state.selection;
this.storedMarks = state.storedMarks;
}
/**
The transaction's current selection. This defaults to the editor
selection [mapped](https://prosemirror.net/docs/ref/#state.Selection.map) through the steps in the
transaction, but can be overwritten with
*/
get selection() {
if (this.curSelectionFor < this.steps.length) {
this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionFor));
this.curSelectionFor = this.steps.length;
}
return this.curSelection;
}
/**
Update the transaction's current selection. Will determine the
selection that the editor gets when the transaction is applied.
*/
setSelection(selection) {
if (selection.$from.doc != this.doc)
throw new RangeError("Selection passed to setSelection must point at the current document");
this.curSelection = selection;
this.curSelectionFor = this.steps.length;
this.updated = (this.updated | UPDATED_SEL) & ~UPDATED_MARKS;
this.storedMarks = null;
return this;
}
/**
Whether the selection was explicitly updated by this transaction.
*/
get selectionSet() {
return (this.updated & UPDATED_SEL) > 0;
}
/**
Set the current stored marks.
*/
setStoredMarks(marks) {
this.storedMarks = marks;
this.updated |= UPDATED_MARKS;
return this;
}
/**
Make sure the current stored marks or, if that is null, the marks
at the selection, match the given set of marks. Does nothing if
this is already the case.
*/
ensureMarks(marks) {
if (!Mark$1.sameSet(this.storedMarks || this.selection.$from.marks(), marks))
this.setStoredMarks(marks);
return this;
}
/**
Add a mark to the set of stored marks.
*/
addStoredMark(mark) {
return this.ensureMarks(mark.addToSet(this.storedMarks || this.selection.$head.marks()));
}
/**
Remove a mark or mark type from the set of stored marks.
*/
removeStoredMark(mark) {
return this.ensureMarks(mark.removeFromSet(this.storedMarks || this.selection.$head.marks()));
}
/**
Whether the stored marks were explicitly set for this transaction.
*/
get storedMarksSet() {
return (this.updated & UPDATED_MARKS) > 0;
}
/**
@internal
*/
addStep(step, doc2) {
super.addStep(step, doc2);
this.updated = this.updated & ~UPDATED_MARKS;
this.storedMarks = null;
}
/**
Update the timestamp for the transaction.
*/
setTime(time) {
this.time = time;
return this;
}
/**
Replace the current selection with the given slice.
*/
replaceSelection(slice2) {
this.selection.replace(this, slice2);
return this;
}
/**
Replace the selection with the given node. When `inheritMarks` is
true and the content is inline, it inherits the marks from the
place where it is inserted.
*/
replaceSelectionWith(node, inheritMarks = true) {
let selection = this.selection;
if (inheritMarks)
node = node.mark(this.storedMarks || (selection.empty ? selection.$from.marks() : selection.$from.marksAcross(selection.$to) || Mark$1.none));
selection.replaceWith(this, node);
return this;
}
/**
Delete the selection.
*/
deleteSelection() {
this.selection.replace(this);
return this;
}
/**
Replace the given range, or the selection if no range is given,
with a text node containing the given string.
*/
insertText(text2, from2, to) {
let schema = this.doc.type.schema;
if (from2 == null) {
if (!text2)
return this.deleteSelection();
return this.replaceSelectionWith(schema.text(text2), true);
} else {
if (to == null)
to = from2;
to = to == null ? from2 : to;
if (!text2)
return this.deleteRange(from2, to);
let marks = this.storedMarks;
if (!marks) {
let $from = this.doc.resolve(from2);
marks = to == from2 ? $from.marks() : $from.marksAcross(this.doc.resolve(to));
}
this.replaceRangeWith(from2, to, schema.text(text2, marks));
if (!this.selection.empty)
this.setSelection(Selection.near(this.selection.$to));
return this;
}
}
/**
Store a metadata property in this transaction, keyed either by
name or by plugin.
*/
setMeta(key, value) {
this.meta[typeof key == "string" ? key : key.key] = value;
return this;
}
/**
Retrieve a metadata property for a given name or plugin.
*/
getMeta(key) {
return this.meta[typeof key == "string" ? key : key.key];
}
/**
Returns true if this transaction doesn't contain any metadata,
and can thus safely be extended.
*/
get isGeneric() {
for (let _ in this.meta)
return false;
return true;
}
/**
Indicate that the editor should scroll the selection into view
when updated to the state produced by this transaction.
*/
scrollIntoView() {
this.updated |= UPDATED_SCROLL;
return this;
}
/**
True when this transaction has had `scrollIntoView` called on it.
*/
get scrolledIntoView() {
return (this.updated & UPDATED_SCROLL) > 0;
}
}
function bind(f, self) {
return !self || !f ? f : f.bind(self);
}
class FieldDesc {
constructor(name, desc, self) {
this.name = name;
this.init = bind(desc.init, self);
this.apply = bind(desc.apply, self);
}
}
const baseFields = [
new FieldDesc("doc", {
init(config) {
return config.doc || config.schema.topNodeType.createAndFill();
},
apply(tr) {
return tr.doc;
}
}),
new FieldDesc("selection", {
init(config, instance) {
return config.selection || Selection.atStart(instance.doc);
},
apply(tr) {
return tr.selection;
}
}),
new FieldDesc("storedMarks", {
init(config) {
return config.storedMarks || null;
},
apply(tr, _marks, _old, state) {
return state.selection.$cursor ? tr.storedMarks : null;
}
}),
new FieldDesc("scrollToSelection", {
init() {
return 0;
},
apply(tr, prev) {
return tr.scrolledIntoView ? prev + 1 : prev;
}
})
];
class Configuration {
constructor(schema, plugins) {
this.schema = schema;
this.plugins = [];
this.pluginsByKey = /* @__PURE__ */ Object.create(null);
this.fields = baseFields.slice();
if (plugins)
plugins.forEach((plugin) => {
if (this.pluginsByKey[plugin.key])
throw new RangeError("Adding different instances of a keyed plugin (" + plugin.key + ")");
this.plugins.push(plugin);
this.pluginsByKey[plugin.key] = plugin;
if (plugin.spec.state)
this.fields.push(new FieldDesc(plugin.key, plugin.spec.state, plugin));
});
}
}
class EditorState {
/**
@internal
*/
constructor(config) {
this.config = config;
}
/**
The schema of the state's document.
*/
get schema() {
return this.config.schema;
}
/**
The plugins that are active in this state.
*/
get plugins() {
return this.config.plugins;
}
/**
Apply the given transaction to produce a new state.
*/
apply(tr) {
return this.applyTransaction(tr).state;
}
/**
@internal
*/
filterTransaction(tr, ignore = -1) {
for (let i = 0; i < this.config.plugins.length; i++)
if (i != ignore) {
let plugin = this.config.plugins[i];
if (plugin.spec.filterTransaction && !plugin.spec.filterTransaction.call(plugin, tr, this))
return false;
}
return true;
}
/**
returns the precise transactions that were applied (which might
be influenced by the [transaction
plugins) along with the new state.
*/
applyTransaction(rootTr) {
if (!this.filterTransaction(rootTr))
return { state: this, transactions: [] };
let trs = [rootTr], newState = this.applyInner(rootTr), seen = null;
for (; ; ) {
let haveNew = false;
for (let i = 0; i < this.config.plugins.length; i++) {
let plugin = this.config.plugins[i];
if (plugin.spec.appendTransaction) {
let n = seen ? seen[i].n : 0, oldState = seen ? seen[i].state : this;
let tr = n < trs.length && plugin.spec.appendTransaction.call(plugin, n ? trs.slice(n) : trs, oldState, newState);
if (tr && newState.filterTransaction(tr, i)) {
tr.setMeta("appendedTransaction", rootTr);
if (!seen) {
seen = [];
for (let j = 0; j < this.config.plugins.length; j++)
seen.push(j < i ? { state: newState, n: trs.length } : { state: this, n: 0 });
}
trs.push(tr);
newState = newState.applyInner(tr);
haveNew = true;
}
if (seen)
seen[i] = { state: newState, n: trs.length };
}
}
if (!haveNew)
return { state: newState, transactions: trs };
}
}
/**
@internal
*/
applyInner(tr) {
if (!tr.before.eq(this.doc))
throw new RangeError("Applying a mismatched transaction");
let newInstance = new EditorState(this.config), fields = this.config.fields;
for (let i = 0; i < fields.length; i++) {
let field = fields[i];
newInstance[field.name] = field.apply(tr, this[field.name], this, newInstance);
}
return newInstance;
}
/**
Start a [transaction](https://prosemirror.net/docs/ref/#state.Transaction) from this state.
*/
get tr() {
return new Transaction(this);
}
/**
Create a new state.
*/
static create(config) {
let $config = new Configuration(config.doc ? config.doc.type.schema : config.schema, config.plugins);
let instance = new EditorState($config);
for (let i = 0; i < $config.fields.length; i++)
instance[$config.fields[i].name] = $config.fields[i].init(config, instance);
return instance;
}
/**
Create a new state based on this one, but with an adjusted set
of active plugins. State fields that exist in both sets of
plugins are kept unchanged. Those that no longer exist are
dropped, and those that are new are initialized using their
[`init`](https://prosemirror.net/docs/ref/#state.StateField.init) method, passing in the new
configuration object..
*/
reconfigure(config) {
let $config = new Configuration(this.schema, config.plugins);
let fields = $config.fields, instance = new EditorState($config);
for (let i = 0; i < fields.length; i++) {
let name = fields[i].name;
instance[name] = this.hasOwnProperty(name) ? this[name] : fields[i].init(config, instance);
}
return instance;
}
/**
Serialize this state to JSON. If you want to serialize the state
of plugins, pass an object mapping property names to use in the
resulting JSON object to plugin objects. The argument may also be
a string or number, in which case it is ignored, to support the
way `JSON.stringify` calls `toString` methods.
*/
toJSON(pluginFields) {
let result = { doc: this.doc.toJSON(), selection: this.selection.toJSON() };
if (this.storedMarks)
result.storedMarks = this.storedMarks.map((m) => m.toJSON());
if (pluginFields && typeof pluginFields == "object")
for (let prop in pluginFields) {
if (prop == "doc" || prop == "selection")
throw new RangeError("The JSON fields `doc` and `selection` are reserved");
let plugin = pluginFields[prop], state = plugin.spec.state;
if (state && state.toJSON)
result[prop] = state.toJSON.call(plugin, this[plugin.key]);
}
return result;
}
/**
Deserialize a JSON representation of a state. `config` should
have at least a `schema` field, and should contain array of
plugins to initialize the state with. `pluginFields` can be used
to deserialize the state of plugins, by associating plugin
instances with the property names they use in the JSON object.
*/
static fromJSON(config, json, pluginFields) {
if (!json)
throw new RangeError("Invalid input for EditorState.fromJSON");
if (!config.schema)
throw new RangeError("Required config field 'schema' missing");
let $config = new Configuration(config.schema, config.plugins);
let instance = new EditorState($config);
$config.fields.forEach((field) => {
if (field.name == "doc") {
instance.doc = Node$1.fromJSON(config.schema, json.doc);
} else if (field.name == "selection") {
instance.selection = Selection.fromJSON(instance.doc, json.selection);
} else if (field.name == "storedMarks") {
if (json.storedMarks)
instance.storedMarks = json.storedMarks.map(config.schema.markFromJSON);
} else {
if (pluginFields)
for (let prop in pluginFields) {
let plugin = pluginFields[prop], state = plugin.spec.state;
if (plugin.key == field.name && state && state.fromJSON && Object.prototype.hasOwnProperty.call(json, prop)) {
instance[field.name] = state.fromJSON.call(plugin, config, json[prop], instance);
return;
}
}
instance[field.name] = field.init(config, instance);
}
});
return instance;
}
}
function bindProps(obj, self, target) {
for (let prop in obj) {
let val = obj[prop];
if (val instanceof Function)
val = val.bind(self);
else if (prop == "handleDOMEvents")
val = bindProps(val, self, {});
target[prop] = val;
}
return target;
}
class Plugin {
/**
Create a plugin.
*/
constructor(spec) {
this.spec = spec;
this.props = {};
if (spec.props)
bindProps(spec.props, this, this.props);
this.key = spec.key ? spec.key.key : createKey("plugin");
}
/**
Extract the plugin's state field from an editor state.
*/
getState(state) {
return state[this.key];
}
}
const keys = /* @__PURE__ */ Object.create(null);
function createKey(name) {
if (name in keys)
return name + "$" + ++keys[name];
keys[name] = 0;
return name + "$";
}
class PluginKey {
/**
Create a plugin key.
*/
constructor(name = "key") {
this.key = createKey(name);
}
/**
Get the active plugin with this key, if any, from an editor
state.
*/
get(state) {
return state.config.pluginsByKey[this.key];
}
/**
Get the plugin's state from an editor state.
*/
getState(state) {
return state[this.key];
}
}
const domIndex = function(node) {
for (var index = 0; ; index++) {
node = node.previousSibling;
if (!node)
return index;
}
};
const parentNode = function(node) {
let parent = node.assignedSlot || node.parentNode;
return parent && parent.nodeType == 11 ? parent.host : parent;
};
let reusedRange = null;
const textRange = function(node, from2, to) {
let range = reusedRange || (reusedRange = document.createRange());
range.setEnd(node, to == null ? node.nodeValue.length : to);
range.setStart(node, from2 || 0);
return range;
};
const isEquivalentPosition = function(node, off, targetNode, targetOff) {
return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1));
};
const atomElements = /^(img|br|input|textarea|hr)$/i;
function scanFor(node, off, targetNode, targetOff, dir) {
for (; ; ) {
if (node == targetNode && off == targetOff)
return true;
if (off == (dir < 0 ? 0 : nodeSize(node))) {
let parent = node.parentNode;
if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) || node.contentEditable == "false")
return false;
off = domIndex(node) + (dir < 0 ? 0 : 1);
node = parent;
} else if (node.nodeType == 1) {
node = node.childNodes[off + (dir < 0 ? -1 : 0)];
if (node.contentEditable == "false")
return false;
off = dir < 0 ? nodeSize(node) : 0;
} else {
return false;
}
}
}
function nodeSize(node) {
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
}
function isOnEdge(node, offset, parent) {
for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd; ) {
if (node == parent)
return true;
let index = domIndex(node);
node = node.parentNode;
if (!node)
return false;
atStart = atStart && index == 0;
atEnd = atEnd && index == nodeSize(node);
}
}
function hasBlockDesc(dom) {
let desc;
for (let cur = dom; cur; cur = cur.parentNode)
if (desc = cur.pmViewDesc)
break;
return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom);
}
const selectionCollapsed = function(domSel) {
return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset, domSel.anchorNode, domSel.anchorOffset);
};
function keyEvent(keyCode, key) {
let event = document.createEvent("Event");
event.initEvent("keydown", true, true);
event.keyCode = keyCode;
event.key = event.code = key;
return event;
}
function deepActiveElement(doc2) {
let elt = doc2.activeElement;
while (elt && elt.shadowRoot)
elt = elt.shadowRoot.activeElement;
return elt;
}
const nav = typeof navigator != "undefined" ? navigator : null;
const doc = typeof document != "undefined" ? document : null;
const agent = nav && nav.userAgent || "";
const ie_edge = /Edge\/(\d+)/.exec(agent);
const ie_upto10 = /MSIE \d/.exec(agent);
const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent);
const ie = !!(ie_upto10 || ie_11up || ie_edge);
const ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0;
const gecko = !ie && /gecko\/(\d+)/i.test(agent);
gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1];
const _chrome = !ie && /Chrome\/(\d+)/.exec(agent);
const chrome = !!_chrome;
const chrome_version = _chrome ? +_chrome[1] : 0;
const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
const mac$1 = ios || (nav ? /Mac/.test(nav.platform) : false);
const android = /Android \d/.test(agent);
const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
function windowRect(doc2) {
return {
left: 0,
right: doc2.documentElement.clientWidth,
top: 0,
bottom: doc2.documentElement.clientHeight
};
}
function getSide(value, side) {
return typeof value == "number" ? value : value[side];
}
function clientRect(node) {
let rect = node.getBoundingClientRect();
let scaleX = rect.width / node.offsetWidth || 1;
let scaleY = rect.height / node.offsetHeight || 1;
return {
left: rect.left,
right: rect.left + node.clientWidth * scaleX,
top: rect.top,
bottom: rect.top + node.clientHeight * scaleY
};
}
function scrollRectIntoView(view, rect, startDOM) {
let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5;
let doc2 = view.dom.ownerDocument;
for (let parent = startDOM || view.dom; ; parent = parentNode(parent)) {
if (!parent)
break;
if (parent.nodeType != 1)
continue;
let elt = parent;
let atTop = elt == doc2.body;
let bounding = atTop ? windowRect(doc2) : clientRect(elt);
let moveX = 0, moveY = 0;
if (rect.top < bounding.top + getSide(scrollThreshold, "top"))
moveY = -(bounding.top - rect.top + getSide(scrollMargin, "top"));
else if (rect.bottom > bounding.bottom - getSide(scrollThreshold, "bottom"))
moveY = rect.bottom - bounding.bottom + getSide(scrollMargin, "bottom");
if (rect.left < bounding.left + getSide(scrollThreshold, "left"))
moveX = -(bounding.left - rect.left + getSide(scrollMargin, "left"));
else if (rect.right > bounding.right - getSide(scrollThreshold, "right"))
moveX = rect.right - bounding.right + getSide(scrollMargin, "right");
if (moveX || moveY) {
if (atTop) {
doc2.defaultView.scrollBy(moveX, moveY);
} else {
let startX = elt.scrollLeft, startY = elt.scrollTop;
if (moveY)
elt.scrollTop += moveY;
if (moveX)
elt.scrollLeft += moveX;
let dX = elt.scrollLeft - startX, dY = elt.scrollTop - startY;
rect = { left: rect.left - dX, top: rect.top - dY, right: rect.right - dX, bottom: rect.bottom - dY };
}
}
if (atTop)
break;
}
}
function storeScrollPos(view) {
let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top);
let refDOM, refTop;
for (let x = (rect.left + rect.right) / 2, y = startY + 1; y < Math.min(innerHeight, rect.bottom); y += 5) {
let dom = view.root.elementFromPoint(x, y);
if (!dom || dom == view.dom || !view.dom.contains(dom))
continue;
let localRect = dom.getBoundingClientRect();
if (localRect.top >= startY - 20) {
refDOM = dom;
refTop = localRect.top;
break;
}
}
return { refDOM, refTop, stack: scrollStack(view.dom) };
}
function scrollStack(dom) {
let stack = [], doc2 = dom.ownerDocument;
for (let cur = dom; cur; cur = parentNode(cur)) {
stack.push({ dom: cur, top: cur.scrollTop, left: cur.scrollLeft });
if (dom == doc2)
break;
}
return stack;
}
function resetScrollPos({ refDOM, refTop, stack }) {
let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0;
restoreScrollStack(stack, newRefTop == 0 ? 0 : newRefTop - refTop);
}
function restoreScrollStack(stack, dTop) {
for (let i = 0; i < stack.length; i++) {
let { dom, top, left } = stack[i];
if (dom.scrollTop != top + dTop)
dom.scrollTop = top + dTop;
if (dom.scrollLeft != left)
dom.scrollLeft = left;
}
}
let preventScrollSupported = null;
function focusPreventScroll(dom) {
if (dom.setActive)
return dom.setActive();
if (preventScrollSupported)
return dom.focus(preventScrollSupported);
let stored = scrollStack(dom);
dom.focus(preventScrollSupported == null ? {
get preventScroll() {
preventScrollSupported = { preventScroll: true };
return true;
}
} : void 0);
if (!preventScrollSupported) {
preventScrollSupported = false;
restoreScrollStack(stored, 0);
}
}
function findOffsetInNode(node, coords) {
let closest, dxClosest = 2e8, coordsClosest, offset = 0;
let rowBot = coords.top, rowTop = coords.top;
for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
let rects;
if (child.nodeType == 1)
rects = child.getClientRects();
else if (child.nodeType == 3)
rects = textRange(child).getClientRects();
else
continue;
for (let i = 0; i < rects.length; i++) {
let rect = rects[i];
if (rect.top <= rowBot && rect.bottom >= rowTop) {
rowBot = Math.max(rect.bottom, rowBot);
rowTop = Math.min(rect.top, rowTop);
let dx = rect.left > coords.left ? rect.left - coords.left : rect.right < coords.left ? coords.left - rect.right : 0;
if (dx < dxClosest) {
closest = child;
dxClosest = dx;
coordsClosest = dx && closest.nodeType == 3 ? {
left: rect.right < coords.left ? rect.right : rect.left,
top: coords.top
} : coords;
if (child.nodeType == 1 && dx)
offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0);
continue;
}
}
if (!closest && (coords.left >= rect.right && coords.top >= rect.top || coords.left >= rect.left && coords.top >= rect.bottom))
offset = childIndex + 1;
}
}
if (closest && closest.nodeType == 3)
return findOffsetInText(closest, coordsClosest);
if (!closest || dxClosest && closest.nodeType == 1)
return { node, offset };
return findOffsetInNode(closest, coordsClosest);
}
function findOffsetInText(node, coords) {
let len = node.nodeValue.length;
let range = document.createRange();
for (let i = 0; i < len; i++) {
range.setEnd(node, i + 1);
range.setStart(node, i);
let rect = singleRect(range, 1);
if (rect.top == rect.bottom)
continue;
if (inRect(coords, rect))
return { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) };
}
return { node, offset: 0 };
}
function inRect(coords, rect) {
return coords.left >= rect.left - 1 && coords.left <= rect.right + 1 && coords.top >= rect.top - 1 && coords.top <= rect.bottom + 1;
}
function targetKludge(dom, coords) {
let parent = dom.parentNode;
if (parent && /^li$/i.test(parent.nodeName) && coords.left < dom.getBoundingClientRect().left)
return parent;
return dom;
}
function posFromElement(view, elt, coords) {
let { node, offset } = findOffsetInNode(elt, coords), bias = -1;
if (node.nodeType == 1 && !node.firstChild) {
let rect = node.getBoundingClientRect();
bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1;
}
return view.docView.posFromDOM(node, offset, bias);
}
function posFromCaret(view, node, offset, coords) {
let outsideBlock = -1;
for (let cur = node; ; ) {
if (cur == view.dom)
break;
let desc = view.docView.nearestDesc(cur, true);
if (!desc)
return null;
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)) {
let rect = desc.dom.getBoundingClientRect();
if (desc.node.isBlock && desc.parent) {
if (rect.left > coords.left || rect.top > coords.top)
outsideBlock = desc.posBefore;
else if (rect.right < coords.left || rect.bottom < coords.top)
outsideBlock = desc.posAfter;
}
if (!desc.contentDOM && outsideBlock < 0) {
let before = desc.node.isBlock ? coords.top < (rect.top + rect.bottom) / 2 : coords.left < (rect.left + rect.right) / 2;
return before ? desc.posBefore : desc.posAfter;
}
}
cur = desc.dom.parentNode;
}
return outsideBlock > -1 ? outsideBlock : view.docView.posFromDOM(node, offset, -1);
}
function elementFromPoint(element, coords, box) {
let len = element.childNodes.length;
if (len && box.top < box.bottom) {
for (let startI = Math.max(0, Math.min(len - 1, Math.floor(len * (coords.top - box.top) / (box.bottom - box.top)) - 2)), i = startI; ; ) {
let child = element.childNodes[i];
if (child.nodeType == 1) {
let rects = child.getClientRects();
for (let j = 0; j < rects.length; j++) {
let rect = rects[j];
if (inRect(coords, rect))
return elementFromPoint(child, coords, rect);
}
}
if ((i = (i + 1) % len) == startI)
break;
}
}
return element;
}
function posAtCoords(view, coords) {
let doc2 = view.dom.ownerDocument, node, offset = 0;
if (doc2.caretPositionFromPoint) {
try {
let pos2 = doc2.caretPositionFromPoint(coords.left, coords.top);
if (pos2)
({ offsetNode: node, offset } = pos2);
} catch (_) {
}
}
if (!node && doc2.caretRangeFromPoint) {
let range = doc2.caretRangeFromPoint(coords.left, coords.top);
if (range)
({ startContainer: node, startOffset: offset } = range);
}
let elt = (view.root.elementFromPoint ? view.root : doc2).elementFromPoint(coords.left, coords.top);
let pos;
if (!elt || !view.dom.contains(elt.nodeType != 1 ? elt.parentNode : elt)) {
let box = view.dom.getBoundingClientRect();
if (!inRect(coords, box))
return null;
elt = elementFromPoint(view.dom, coords, box);
if (!elt)
return null;
}
if (safari) {
for (let p = elt; node && p; p = parentNode(p))
if (p.draggable)
node = void 0;
}
elt = targetKludge(elt, coords);
if (node) {
if (gecko && node.nodeType == 1) {
offset = Math.min(offset, node.childNodes.length);
if (offset < node.childNodes.length) {
let next = node.childNodes[offset], box;
if (next.nodeName == "IMG" && (box = next.getBoundingClientRect()).right <= coords.left && box.bottom > coords.top)
offset++;
}
}
if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild.nodeType == 1 && coords.top > node.lastChild.getBoundingClientRect().bottom)
pos = view.state.doc.content.size;
else if (offset == 0 || node.nodeType != 1 || node.childNodes[offset - 1].nodeName != "BR")
pos = posFromCaret(view, node, offset, coords);
}
if (pos == null)
pos = posFromElement(view, elt, coords);
let desc = view.docView.nearestDesc(elt, true);
return { pos, inside: desc ? desc.posAtStart - desc.border : -1 };
}
function singleRect(target, bias) {
let rects = target.getClientRects();
return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1];
}
const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
function coordsAtPos(view, pos, side) {
let { node, offset, atom } = view.docView.domFromPos(pos, side < 0 ? -1 : 1);
let supportEmptyRange = webkit || gecko;
if (node.nodeType == 3) {
if (supportEmptyRange && (BIDI.test(node.nodeValue) || (side < 0 ? !offset : offset == node.nodeValue.length))) {
let rect = singleRect(textRange(node, offset, offset), side);
if (gecko && offset && /\s/.test(node.nodeValue[offset - 1]) && offset < node.nodeValue.length) {
let rectBefore = singleRect(textRange(node, offset - 1, offset - 1), -1);
if (rectBefore.top == rect.top) {
let rectAfter = singleRect(textRange(node, offset, offset + 1), -1);
if (rectAfter.top != rect.top)
return flattenV(rectAfter, rectAfter.left < rectBefore.left);
}
}
return rect;
} else {
let from2 = offset, to = offset, takeSide = side < 0 ? 1 : -1;
if (side < 0 && !offset) {
to++;
takeSide = -1;
} else if (side >= 0 && offset == node.nodeValue.length) {
from2--;
takeSide = 1;
} else if (side < 0) {
from2--;
} else {
to++;
}
return flattenV(singleRect(textRange(node, from2, to), 1), takeSide < 0);
}
}
let $dom = view.state.doc.resolve(pos - (atom || 0));
if (!$dom.parent.inlineContent) {
if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
let before = node.childNodes[offset - 1];
if (before.nodeType == 1)
return flattenH(before.getBoundingClientRect(), false);
}
if (atom == null && offset < nodeSize(node)) {
let after = node.childNodes[offset];
if (after.nodeType == 1)
return flattenH(after.getBoundingClientRect(), true);
}
return flattenH(node.getBoundingClientRect(), side >= 0);
}
if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
let before = node.childNodes[offset - 1];
let target = before.nodeType == 3 ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1)) : before.nodeType == 1 && (before.nodeName != "BR" || !before.nextSibling) ? before : null;
if (target)
return flattenV(singleRect(target, 1), false);
}
if (atom == null && offset < nodeSize(node)) {
let after = node.childNodes[offset];
while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords)
after = after.nextSibling;
let target = !after ? null : after.nodeType == 3 ? textRange(after, 0, supportEmptyRange ? 0 : 1) : after.nodeType == 1 ? after : null;
if (target)
return flattenV(singleRect(target, -1), true);
}
return flattenV(singleRect(node.nodeType == 3 ? textRange(node) : node, -side), side >= 0);
}
function flattenV(rect, left) {
if (rect.width == 0)
return rect;
let x = left ? rect.left : rect.right;
return { top: rect.top, bottom: rect.bottom, left: x, right: x };
}
function flattenH(rect, top) {
if (rect.height == 0)
return rect;
let y = top ? rect.top : rect.bottom;
return { top: y, bottom: y, left: rect.left, right: rect.right };
}
function withFlushedState(view, state, f) {
let viewState = view.state, active = view.root.activeElement;
if (viewState != state)
view.updateState(state);
if (active != view.dom)
view.focus();
try {
return f();
} finally {
if (viewState != state)
view.updateState(viewState);
if (active != view.dom && active)
active.focus();
}
}
function endOfTextblockVertical(view, state, dir) {
let sel = state.selection;
let $pos = dir == "up" ? sel.$from : sel.$to;
return withFlushedState(view, state, () => {
let { node: dom } = view.docView.domFromPos($pos.pos, dir == "up" ? -1 : 1);
for (; ; ) {
let nearest = view.docView.nearestDesc(dom, true);
if (!nearest)
break;
if (nearest.node.isBlock) {
dom = nearest.contentDOM || nearest.dom;
break;
}
dom = nearest.dom.parentNode;
}
let coords = coordsAtPos(view, $pos.pos, 1);
for (let child = dom.firstChild; child; child = child.nextSibling) {
let boxes;
if (child.nodeType == 1)
boxes = child.getClientRects();
else if (child.nodeType == 3)
boxes = textRange(child, 0, child.nodeValue.length).getClientRects();
else
continue;
for (let i = 0; i < boxes.length; i++) {
let box = boxes[i];
if (box.bottom > box.top + 1 && (dir == "up" ? coords.top - box.top > (box.bottom - coords.top) * 2 : box.bottom - coords.bottom > (coords.bottom - box.top) * 2))
return false;
}
}
return true;
});
}
const maybeRTL = /[\u0590-\u08ac]/;
function endOfTextblockHorizontal(view, state, dir) {
let { $head } = state.selection;
if (!$head.parent.isTextblock)
return false;
let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size;
let sel = view.domSelection();
if (!maybeRTL.test($head.parent.textContent) || !sel.modify)
return dir == "left" || dir == "backward" ? atStart : atEnd;
return withFlushedState(view, state, () => {
let { focusNode: oldNode, focusOffset: oldOff, anchorNode, anchorOffset } = view.domSelectionRange();
let oldBidiLevel = sel.caretBidiLevel;
sel.modify("move", dir, "character");
let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom;
let { focusNode: newNode, focusOffset: newOff } = view.domSelectionRange();
let result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) || oldNode == newNode && oldOff == newOff;
try {
sel.collapse(anchorNode, anchorOffset);
if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend)
sel.extend(oldNode, oldOff);
} catch (_) {
}
if (oldBidiLevel != null)
sel.caretBidiLevel = oldBidiLevel;
return result;
});
}
let cachedState = null;
let cachedDir = null;
let cachedResult = false;
function endOfTextblock(view, state, dir) {
if (cachedState == state && cachedDir == dir)
return cachedResult;
cachedState = state;
cachedDir = dir;
return cachedResult = dir == "up" || dir == "down" ? endOfTextblockVertical(view, state, dir) : endOfTextblockHorizontal(view, state, dir);
}
const NOT_DIRTY = 0, CHILD_DIRTY = 1, CONTENT_DIRTY = 2, NODE_DIRTY = 3;
class ViewDesc {
constructor(parent, children, dom, contentDOM) {
this.parent = parent;
this.children = children;
this.dom = dom;
this.contentDOM = contentDOM;
this.dirty = NOT_DIRTY;
dom.pmViewDesc = this;
}
// Used to check whether a given description corresponds to a
// widget/mark/node.
matchesWidget(widget) {
return false;
}
matchesMark(mark) {
return false;
}
matchesNode(node, outerDeco, innerDeco) {
return false;
}
matchesHack(nodeName) {
return false;
}
// When parsing in-editor content (in domchange.js), we allow
// descriptions to determine the parse rules that should be used to
// parse them.
parseRule() {
return null;
}
// Used by the editor's event handler to ignore events that come
// from certain descs.
stopEvent(event) {
return false;
}
// The size of the content represented by this desc.
get size() {
let size = 0;
for (let i = 0; i < this.children.length; i++)
size += this.children[i].size;
return size;
}
// For block nodes, this represents the space taken up by their
// start/end tokens.
get border() {
return 0;
}
destroy() {
this.parent = void 0;
if (this.dom.pmViewDesc == this)
this.dom.pmViewDesc = void 0;
for (let i = 0; i < this.children.length; i++)
this.children[i].destroy();
}
posBeforeChild(child) {
for (let i = 0, pos = this.posAtStart; ; i++) {
let cur = this.children[i];
if (cur == child)
return pos;
pos += cur.size;
}
}
get posBefore() {
return this.parent.posBeforeChild(this);
}
get posAtStart() {
return this.parent ? this.parent.posBeforeChild(this) + this.border : 0;
}
get posAfter() {
return this.posBefore + this.size;
}
get posAtEnd() {
return this.posAtStart + this.size - 2 * this.border;
}
localPosFromDOM(dom, offset, bias) {
if (this.contentDOM && this.contentDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode)) {
if (bias < 0) {
let domBefore, desc;
if (dom == this.contentDOM) {
domBefore = dom.childNodes[offset - 1];
} else {
while (dom.parentNode != this.contentDOM)
dom = dom.parentNode;
domBefore = dom.previousSibling;
}
while (domBefore && !((desc = domBefore.pmViewDesc) && desc.parent == this))
domBefore = domBefore.previousSibling;
return domBefore ? this.posBeforeChild(desc) + desc.size : this.posAtStart;
} else {
let domAfter, desc;
if (dom == this.contentDOM) {
domAfter = dom.childNodes[offset];
} else {
while (dom.parentNode != this.contentDOM)
dom = dom.parentNode;
domAfter = dom.nextSibling;
}
while (domAfter && !((desc = domAfter.pmViewDesc) && desc.parent == this))
domAfter = domAfter.nextSibling;
return domAfter ? this.posBeforeChild(desc) : this.posAtEnd;
}
}
let atEnd;
if (dom == this.dom && this.contentDOM) {
atEnd = offset > domIndex(this.contentDOM);
} else if (this.contentDOM && this.contentDOM != this.dom && this.dom.contains(this.contentDOM)) {
atEnd = dom.compareDocumentPosition(this.contentDOM) & 2;
} else if (this.dom.firstChild) {
if (offset == 0)
for (let search = dom; ; search = search.parentNode) {
if (search == this.dom) {
atEnd = false;
break;
}
if (search.previousSibling)
break;
}
if (atEnd == null && offset == dom.childNodes.length)
for (let search = dom; ; search = search.parentNode) {
if (search == this.dom) {
atEnd = true;
break;
}
if (search.nextSibling)
break;
}
}
return (atEnd == null ? bias > 0 : atEnd) ? this.posAtEnd : this.posAtStart;
}
nearestDesc(dom, onlyNodes = false) {
for (let first2 = true, cur = dom; cur; cur = cur.parentNode) {
let desc = this.getDesc(cur), nodeDOM;
if (desc && (!onlyNodes || desc.node)) {
if (first2 && (nodeDOM = desc.nodeDOM) && !(nodeDOM.nodeType == 1 ? nodeDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode) : nodeDOM == dom))
first2 = false;
else
return desc;
}
}
}
getDesc(dom) {
let desc = dom.pmViewDesc;
for (let cur = desc; cur; cur = cur.parent)
if (cur == this)
return desc;
}
posFromDOM(dom, offset, bias) {
for (let scan = dom; scan; scan = scan.parentNode) {
let desc = this.getDesc(scan);
if (desc)
return desc.localPosFromDOM(dom, offset, bias);
}
return -1;
}
// Find the desc for the node after the given pos, if any. (When a
// parent node overrode rendering, there might not be one.)
descAt(pos) {
for (let i = 0, offset = 0; i < this.children.length; i++) {
let child = this.children[i], end = offset + child.size;
if (offset == pos && end != offset) {
while (!child.border && child.children.length)
child = child.children[0];
return child;
}
if (pos < end)
return child.descAt(pos - offset - child.border);
offset = end;
}
}
domFromPos(pos, side) {
if (!this.contentDOM)
return { node: this.dom, offset: 0, atom: pos + 1 };
let i = 0, offset = 0;
for (let curPos = 0; i < this.children.length; i++) {
let child = this.children[i], end = curPos + child.size;
if (end > pos || child instanceof TrailingHackViewDesc) {
offset = pos - curPos;
break;
}
curPos = end;
}
if (offset)
return this.children[i].domFromPos(offset - this.children[i].border, side);
for (let prev; i && !(prev = this.children[i - 1]).size && prev instanceof WidgetViewDesc && prev.side >= 0; i--) {
}
if (side <= 0) {
let prev, enter2 = true;
for (; ; i--, enter2 = false) {
prev = i ? this.children[i - 1] : null;
if (!prev || prev.dom.parentNode == this.contentDOM)
break;
}
if (prev && side && enter2 && !prev.border && !prev.domAtom)
return prev.domFromPos(prev.size, side);
return { node: this.contentDOM, offset: prev ? domIndex(prev.dom) + 1 : 0 };
} else {
let next, enter2 = true;
for (; ; i++, enter2 = false) {
next = i < this.children.length ? this.children[i] : null;
if (!next || next.dom.parentNode == this.contentDOM)
break;
}
if (next && enter2 && !next.border && !next.domAtom)
return next.domFromPos(0, side);
return { node: this.contentDOM, offset: next ? domIndex(next.dom) : this.contentDOM.childNodes.length };
}
}
// Used to find a DOM range in a single parent for a given changed
// range.
parseRange(from2, to, base2 = 0) {
if (this.children.length == 0)
return { node: this.contentDOM, from: from2, to, fromOffset: 0, toOffset: this.contentDOM.childNodes.length };
let fromOffset = -1, toOffset = -1;
for (let offset = base2, i = 0; ; i++) {
let child = this.children[i], end = offset + child.size;
if (fromOffset == -1 && from2 <= end) {
let childBase = offset + child.border;
if (from2 >= childBase && to <= end - child.border && child.node && child.contentDOM && this.contentDOM.contains(child.contentDOM))
return child.parseRange(from2, to, childBase);
from2 = offset;
for (let j = i; j > 0; j--) {
let prev = this.children[j - 1];
if (prev.size && prev.dom.parentNode == this.contentDOM && !prev.emptyChildAt(1)) {
fromOffset = domIndex(prev.dom) + 1;
break;
}
from2 -= prev.size;
}
if (fromOffset == -1)
fromOffset = 0;
}
if (fromOffset > -1 && (end > to || i == this.children.length - 1)) {
to = end;
for (let j = i + 1; j < this.children.length; j++) {
let next = this.children[j];
if (next.size && next.dom.parentNode == this.contentDOM && !next.emptyChildAt(-1)) {
toOffset = domIndex(next.dom);
break;
}
to += next.size;
}
if (toOffset == -1)
toOffset = this.contentDOM.childNodes.length;
break;
}
offset = end;
}
return { node: this.contentDOM, from: from2, to, fromOffset, toOffset };
}
emptyChildAt(side) {
if (this.border || !this.contentDOM || !this.children.length)
return false;
let child = this.children[side < 0 ? 0 : this.children.length - 1];
return child.size == 0 || child.emptyChildAt(side);
}
domAfterPos(pos) {
let { node, offset } = this.domFromPos(pos, 0);
if (node.nodeType != 1 || offset == node.childNodes.length)
throw new RangeError("No node after pos " + pos);
return node.childNodes[offset];
}
// View descs are responsible for setting any selection that falls
// entirely inside of them, so that custom implementations can do
// custom things with the selection. Note that this falls apart when
// a selection starts in such a node and ends in another, in which
// case we just use whatever domFromPos produces as a best effort.
setSelection(anchor, head, root, force = false) {
let from2 = Math.min(anchor, head), to = Math.max(anchor, head);
for (let i = 0, offset = 0; i < this.children.length; i++) {
let child = this.children[i], end = offset + child.size;
if (from2 > offset && to < end)
return child.setSelection(anchor - offset - child.border, head - offset - child.border, root, force);
offset = end;
}
let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
let domSel = root.getSelection();
let brKludge = false;
if ((gecko || safari) && anchor == head) {
let { node, offset } = anchorDOM;
if (node.nodeType == 3) {
brKludge = !!(offset && node.nodeValue[offset - 1] == "\n");
if (brKludge && offset == node.nodeValue.length) {
for (let scan = node, after; scan; scan = scan.parentNode) {
if (after = scan.nextSibling) {
if (after.nodeName == "BR")
anchorDOM = headDOM = { node: after.parentNode, offset: domIndex(after) + 1 };
break;
}
let desc = scan.pmViewDesc;
if (desc && desc.node && desc.node.isBlock)
break;
}
}
} else {
let prev = node.childNodes[offset - 1];
brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false");
}
}
if (gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
let after = domSel.focusNode.childNodes[domSel.focusOffset];
if (after && after.contentEditable == "false")
force = true;
}
if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset))
return;
let domSelExtended = false;
if ((domSel.extend || anchor == head) && !brKludge) {
domSel.collapse(anchorDOM.node, anchorDOM.offset);
try {
if (anchor != head)
domSel.extend(headDOM.node, headDOM.offset);
domSelExtended = true;
} catch (_) {
}
}
if (!domSelExtended) {
if (anchor > head) {
let tmp = anchorDOM;
anchorDOM = headDOM;
headDOM = tmp;
}
let range = document.createRange();
range.setEnd(headDOM.node, headDOM.offset);
range.setStart(anchorDOM.node, anchorDOM.offset);
domSel.removeAllRanges();
domSel.addRange(range);
}
}
ignoreMutation(mutation) {
return !this.contentDOM && mutation.type != "selection";
}
get contentLost() {
return this.contentDOM && this.contentDOM != this.dom && !this.dom.contains(this.contentDOM);
}
// Remove a subtree of the element tree that has been touched
// by a DOM change, so that the next update will redraw it.
markDirty(from2, to) {
for (let offset = 0, i = 0; i < this.children.length; i++) {
let child = this.children[i], end = offset + child.size;
if (offset == end ? from2 <= end && to >= offset : from2 < end && to > offset) {
let startInside = offset + child.border, endInside = end - child.border;
if (from2 >= startInside && to <= endInside) {
this.dirty = from2 == offset || to == end ? CONTENT_DIRTY : CHILD_DIRTY;
if (from2 == startInside && to == endInside && (child.contentLost || child.dom.parentNode != this.contentDOM))
child.dirty = NODE_DIRTY;
else
child.markDirty(from2 - startInside, to - startInside);
return;
} else {
child.dirty = child.dom == child.contentDOM && child.dom.parentNode == this.contentDOM && !child.children.length ? CONTENT_DIRTY : NODE_DIRTY;
}
}
offset = end;
}
this.dirty = CONTENT_DIRTY;
}
markParentsDirty() {
let level = 1;
for (let node = this.parent; node; node = node.parent, level++) {
let dirty = level == 1 ? CONTENT_DIRTY : CHILD_DIRTY;
if (node.dirty < dirty)
node.dirty = dirty;
}
}
get domAtom() {
return false;
}
get ignoreForCoords() {
return false;
}
}
class WidgetViewDesc extends ViewDesc {
constructor(parent, widget, view, pos) {
let self, dom = widget.type.toDOM;
if (typeof dom == "function")
dom = dom(view, () => {
if (!self)
return pos;
if (self.parent)
return self.parent.posBeforeChild(self);
});
if (!widget.type.spec.raw) {
if (dom.nodeType != 1) {
let wrap2 = document.createElement("span");
wrap2.appendChild(dom);
dom = wrap2;
}
dom.contentEditable = "false";
dom.classList.add("ProseMirror-widget");
}
super(parent, [], dom, null);
this.widget = widget;
this.widget = widget;
self = this;
}
matchesWidget(widget) {
return this.dirty == NOT_DIRTY && widget.type.eq(this.widget.type);
}
parseRule() {
return { ignore: true };
}
stopEvent(event) {
let stop = this.widget.spec.stopEvent;
return stop ? stop(event) : false;
}
ignoreMutation(mutation) {
return mutation.type != "selection" || this.widget.spec.ignoreSelection;
}
destroy() {
this.widget.type.destroy(this.dom);
super.destroy();
}
get domAtom() {
return true;
}
get side() {
return this.widget.type.side;
}
}
class CompositionViewDesc extends ViewDesc {
constructor(parent, dom, textDOM, text2) {
super(parent, [], dom, null);
this.textDOM = textDOM;
this.text = text2;
}
get size() {
return this.text.length;
}
localPosFromDOM(dom, offset) {
if (dom != this.textDOM)
return this.posAtStart + (offset ? this.size : 0);
return this.posAtStart + offset;
}
domFromPos(pos) {
return { node: this.textDOM, offset: pos };
}
ignoreMutation(mut) {
return mut.type === "characterData" && mut.target.nodeValue == mut.oldValue;
}
}
class MarkViewDesc extends ViewDesc {
constructor(parent, mark, dom, contentDOM) {
super(parent, [], dom, contentDOM);
this.mark = mark;
}
static create(parent, mark, inline, view) {
let custom = view.nodeViews[mark.type.name];
let spec = custom && custom(mark, view, inline);
if (!spec || !spec.dom)
spec = DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline));
return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom);
}
parseRule() {
if (this.dirty & NODE_DIRTY || this.mark.type.spec.reparseInView)
return null;
return { mark: this.mark.type.name, attrs: this.mark.attrs, contentElement: this.contentDOM || void 0 };
}
matchesMark(mark) {
return this.dirty != NODE_DIRTY && this.mark.eq(mark);
}
markDirty(from2, to) {
super.markDirty(from2, to);
if (this.dirty != NOT_DIRTY) {
let parent = this.parent;
while (!parent.node)
parent = parent.parent;
if (parent.dirty < this.dirty)
parent.dirty = this.dirty;
this.dirty = NOT_DIRTY;
}
}
slice(from2, to, view) {
let copy2 = MarkViewDesc.create(this.parent, this.mark, true, view);
let nodes = this.children, size = this.size;
if (to < size)
nodes = replaceNodes(nodes, to, size, view);
if (from2 > 0)
nodes = replaceNodes(nodes, 0, from2, view);
for (let i = 0; i < nodes.length; i++)
nodes[i].parent = copy2;
copy2.children = nodes;
return copy2;
}
}
class NodeViewDesc extends ViewDesc {
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos) {
super(parent, [], dom, contentDOM);
this.node = node;
this.outerDeco = outerDeco;
this.innerDeco = innerDeco;
this.nodeDOM = nodeDOM;
if (contentDOM)
this.updateChildren(view, pos);
}
// By default, a node is rendered using the `toDOM` method from the
// node type spec. But client code can use the `nodeViews` spec to
// supply a custom node view, which can influence various aspects of
// the way the node works.
//
// (Using subclassing for this was intentionally decided against,
// since it'd require exposing a whole slew of finicky
// implementation details to the user code that they probably will
// never need.)
static create(parent, node, outerDeco, innerDeco, view, pos) {
let custom = view.nodeViews[node.type.name], descObj;
let spec = custom && custom(node, view, () => {
if (!descObj)
return pos;
if (descObj.parent)
return descObj.parent.posBeforeChild(descObj);
}, outerDeco, innerDeco);
let dom = spec && spec.dom, contentDOM = spec && spec.contentDOM;
if (node.isText) {
if (!dom)
dom = document.createTextNode(node.text);
else if (dom.nodeType != 3)
throw new RangeError("Text must be rendered as a DOM text node");
} else if (!dom) {
({ dom, contentDOM } = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node)));
}
if (!contentDOM && !node.isText && dom.nodeName != "BR") {
if (!dom.hasAttribute("contenteditable"))
dom.contentEditable = "false";
if (node.type.spec.draggable)
dom.draggable = true;
}
let nodeDOM = dom;
dom = applyOuterDeco(dom, outerDeco, node);
if (spec)
return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec, view, pos + 1);
else if (node.isText)
return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view);
else
return new NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, view, pos + 1);
}
parseRule() {
if (this.node.type.spec.reparseInView)
return null;
let rule = { node: this.node.type.name, attrs: this.node.attrs };
if (this.node.type.whitespace == "pre")
rule.preserveWhitespace = "full";
if (!this.contentDOM) {
rule.getContent = () => this.node.content;
} else if (!this.contentLost) {
rule.contentElement = this.contentDOM;
} else {
for (let i = this.children.length - 1; i >= 0; i--) {
let child = this.children[i];
if (this.dom.contains(child.dom.parentNode)) {
rule.contentElement = child.dom.parentNode;
break;
}
}
if (!rule.contentElement)
rule.getContent = () => Fragment.empty;
}
return rule;
}
matchesNode(node, outerDeco, innerDeco) {
return this.dirty == NOT_DIRTY && node.eq(this.node) && sameOuterDeco(outerDeco, this.outerDeco) && innerDeco.eq(this.innerDeco);
}
get size() {
return this.node.nodeSize;
}
get border() {
return this.node.isLeaf ? 0 : 1;
}
// Syncs `this.children` to match `this.node.content` and the local
// decorations, possibly introducing nesting for marks. Then, in a
// separate step, syncs the DOM inside `this.contentDOM` to
// `this.children`.
updateChildren(view, pos) {
let inline = this.node.inlineContent, off = pos;
let composition = view.composing ? this.localCompositionInfo(view, pos) : null;
let localComposition = composition && composition.pos > -1 ? composition : null;
let compositionInChild = composition && composition.pos < 0;
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
if (widget.spec.marks)
updater.syncToMarks(widget.spec.marks, inline, view);
else if (widget.type.side >= 0 && !insideNode)
updater.syncToMarks(i == this.node.childCount ? Mark$1.none : this.node.child(i).marks, inline, view);
updater.placeWidget(widget, view, off);
}, (child, outerDeco, innerDeco, i) => {
updater.syncToMarks(child.marks, inline, view);
let compIndex;
if (updater.findNodeMatch(child, outerDeco, innerDeco, i))
;
else if (compositionInChild && view.state.selection.from > off && view.state.selection.to < off + child.nodeSize && (compIndex = updater.findIndexWithChild(composition.node)) > -1 && updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view))
;
else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i))
;
else {
updater.addNode(child, outerDeco, innerDeco, view, off);
}
off += child.nodeSize;
});
updater.syncToMarks([], inline, view);
if (this.node.isTextblock)
updater.addTextblockHacks();
updater.destroyRest();
if (updater.changed || this.dirty == CONTENT_DIRTY) {
if (localComposition)
this.protectLocalComposition(view, localComposition);
renderDescs(this.contentDOM, this.children, view);
if (ios)
iosHacks(this.dom);
}
}
localCompositionInfo(view, pos) {
let { from: from2, to } = view.state.selection;
if (!(view.state.selection instanceof TextSelection) || from2 < pos || to > pos + this.node.content.size)
return null;
let sel = view.domSelectionRange();
let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
if (!textNode || !this.dom.contains(textNode.parentNode))
return null;
if (this.node.inlineContent) {
let text2 = textNode.nodeValue;
let textPos = findTextInFragment(this.node.content, text2, from2 - pos, to - pos);
return textPos < 0 ? null : { node: textNode, pos: textPos, text: text2 };
} else {
return { node: textNode, pos: -1, text: "" };
}
}
protectLocalComposition(view, { node, pos, text: text2 }) {
if (this.getDesc(node))
return;
let topNode = node;
for (; ; topNode = topNode.parentNode) {
if (topNode.parentNode == this.contentDOM)
break;
while (topNode.previousSibling)
topNode.parentNode.removeChild(topNode.previousSibling);
while (topNode.nextSibling)
topNode.parentNode.removeChild(topNode.nextSibling);
if (topNode.pmViewDesc)
topNode.pmViewDesc = void 0;
}
let desc = new CompositionViewDesc(this, topNode, node, text2);
view.input.compositionNodes.push(desc);
this.children = replaceNodes(this.children, pos, pos + text2.length, view, desc);
}
// If this desc must be updated to match the given node decoration,
// do so and return true.
update(node, outerDeco, innerDeco, view) {
if (this.dirty == NODE_DIRTY || !node.sameMarkup(this.node))
return false;
this.updateInner(node, outerDeco, innerDeco, view);
return true;
}
updateInner(node, outerDeco, innerDeco, view) {
this.updateOuterDeco(outerDeco);
this.node = node;
this.innerDeco = innerDeco;
if (this.contentDOM)
this.updateChildren(view, this.posAtStart);
this.dirty = NOT_DIRTY;
}
updateOuterDeco(outerDeco) {
if (sameOuterDeco(outerDeco, this.outerDeco))
return;
let needsWrap = this.nodeDOM.nodeType != 1;
let oldDOM = this.dom;
this.dom = patchOuterDeco(this.dom, this.nodeDOM, computeOuterDeco(this.outerDeco, this.node, needsWrap), computeOuterDeco(outerDeco, this.node, needsWrap));
if (this.dom != oldDOM) {
oldDOM.pmViewDesc = void 0;
this.dom.pmViewDesc = this;
}
this.outerDeco = outerDeco;
}
// Mark this node as being the selected node.
selectNode() {
if (this.nodeDOM.nodeType == 1)
this.nodeDOM.classList.add("ProseMirror-selectednode");
if (this.contentDOM || !this.node.type.spec.draggable)
this.dom.draggable = true;
}
// Remove selected node marking from this node.
deselectNode() {
if (this.nodeDOM.nodeType == 1)
this.nodeDOM.classList.remove("ProseMirror-selectednode");
if (this.contentDOM || !this.node.type.spec.draggable)
this.dom.removeAttribute("draggable");
}
get domAtom() {
return this.node.isAtom;
}
}
function docViewDesc(doc2, outerDeco, innerDeco, dom, view) {
applyOuterDeco(dom, outerDeco, doc2);
return new NodeViewDesc(void 0, doc2, outerDeco, innerDeco, dom, dom, dom, view, 0);
}
class TextViewDesc extends NodeViewDesc {
constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) {
super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0);
}
parseRule() {
let skip = this.nodeDOM.parentNode;
while (skip && skip != this.dom && !skip.pmIsDeco)
skip = skip.parentNode;
return { skip: skip || true };
}
update(node, outerDeco, innerDeco, view) {
if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node))
return false;
this.updateOuterDeco(outerDeco);
if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.nodeDOM.nodeValue) {
this.nodeDOM.nodeValue = node.text;
if (view.trackWrites == this.nodeDOM)
view.trackWrites = null;
}
this.node = node;
this.dirty = NOT_DIRTY;
return true;
}
inParent() {
let parentDOM = this.parent.contentDOM;
for (let n = this.nodeDOM; n; n = n.parentNode)
if (n == parentDOM)
return true;
return false;
}
domFromPos(pos) {
return { node: this.nodeDOM, offset: pos };
}
localPosFromDOM(dom, offset, bias) {
if (dom == this.nodeDOM)
return this.posAtStart + Math.min(offset, this.node.text.length);
return super.localPosFromDOM(dom, offset, bias);
}
ignoreMutation(mutation) {
return mutation.type != "characterData" && mutation.type != "selection";
}
slice(from2, to, view) {
let node = this.node.cut(from2, to), dom = document.createTextNode(node.text);
return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view);
}
markDirty(from2, to) {
super.markDirty(from2, to);
if (this.dom != this.nodeDOM && (from2 == 0 || to == this.nodeDOM.nodeValue.length))
this.dirty = NODE_DIRTY;
}
get domAtom() {
return false;
}
}
class TrailingHackViewDesc extends ViewDesc {
parseRule() {
return { ignore: true };
}
matchesHack(nodeName) {
return this.dirty == NOT_DIRTY && this.dom.nodeName == nodeName;
}
get domAtom() {
return true;
}
get ignoreForCoords() {
return this.dom.nodeName == "IMG";
}
}
class CustomNodeViewDesc extends NodeViewDesc {
constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view, pos) {
super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos);
this.spec = spec;
}
// A custom `update` method gets to decide whether the update goes
// through. If it does, and there's a `contentDOM` node, our logic
// updates the children.
update(node, outerDeco, innerDeco, view) {
if (this.dirty == NODE_DIRTY)
return false;
if (this.spec.update) {
let result = this.spec.update(node, outerDeco, innerDeco);
if (result)
this.updateInner(node, outerDeco, innerDeco, view);
return result;
} else if (!this.contentDOM && !node.isLeaf) {
return false;
} else {
return super.update(node, outerDeco, innerDeco, view);
}
}
selectNode() {
this.spec.selectNode ? this.spec.selectNode() : super.selectNode();
}
deselectNode() {
this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode();
}
setSelection(anchor, head, root, force) {
this.spec.setSelection ? this.spec.setSelection(anchor, head, root) : super.setSelection(anchor, head, root, force);
}
destroy() {
if (this.spec.destroy)
this.spec.destroy();
super.destroy();
}
stopEvent(event) {
return this.spec.stopEvent ? this.spec.stopEvent(event) : false;
}
ignoreMutation(mutation) {
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation);
}
}
function renderDescs(parentDOM, descs, view) {
let dom = parentDOM.firstChild, written = false;
for (let i = 0; i < descs.length; i++) {
let desc = descs[i], childDOM = desc.dom;
if (childDOM.parentNode == parentDOM) {
while (childDOM != dom) {
dom = rm(dom);
written = true;
}
dom = dom.nextSibling;
} else {
written = true;
parentDOM.insertBefore(childDOM, dom);
}
if (desc instanceof MarkViewDesc) {
let pos = dom ? dom.previousSibling : parentDOM.lastChild;
renderDescs(desc.contentDOM, desc.children, view);
dom = pos ? pos.nextSibling : parentDOM.firstChild;
}
}
while (dom) {
dom = rm(dom);
written = true;
}
if (written && view.trackWrites == parentDOM)
view.trackWrites = null;
}
const OuterDecoLevel = function(nodeName) {
if (nodeName)
this.nodeName = nodeName;
};
OuterDecoLevel.prototype = /* @__PURE__ */ Object.create(null);
const noDeco = [new OuterDecoLevel()];
function computeOuterDeco(outerDeco, node, needsWrap) {
if (outerDeco.length == 0)
return noDeco;
let top = needsWrap ? noDeco[0] : new OuterDecoLevel(), result = [top];
for (let i = 0; i < outerDeco.length; i++) {
let attrs = outerDeco[i].type.attrs;
if (!attrs)
continue;
if (attrs.nodeName)
result.push(top = new OuterDecoLevel(attrs.nodeName));
for (let name in attrs) {
let val = attrs[name];
if (val == null)
continue;
if (needsWrap && result.length == 1)
result.push(top = new OuterDecoLevel(node.isInline ? "span" : "div"));
if (name == "class")
top.class = (top.class ? top.class + " " : "") + val;
else if (name == "style")
top.style = (top.style ? top.style + ";" : "") + val;
else if (name != "nodeName")
top[name] = val;
}
}
return result;
}
function patchOuterDeco(outerDOM, nodeDOM, prevComputed, curComputed) {
if (prevComputed == noDeco && curComputed == noDeco)
return nodeDOM;
let curDOM = nodeDOM;
for (let i = 0; i < curComputed.length; i++) {
let deco = curComputed[i], prev = prevComputed[i];
if (i) {
let parent;
if (prev && prev.nodeName == deco.nodeName && curDOM != outerDOM && (parent = curDOM.parentNode) && parent.nodeName.toLowerCase() == deco.nodeName) {
curDOM = parent;
} else {
parent = document.createElement(deco.nodeName);
parent.pmIsDeco = true;
parent.appendChild(curDOM);
prev = noDeco[0];
curDOM = parent;
}
}
patchAttributes(curDOM, prev || noDeco[0], deco);
}
return curDOM;
}
function patchAttributes(dom, prev, cur) {
for (let name in prev)
if (name != "class" && name != "style" && name != "nodeName" && !(name in cur))
dom.removeAttribute(name);
for (let name in cur)
if (name != "class" && name != "style" && name != "nodeName" && cur[name] != prev[name])
dom.setAttribute(name, cur[name]);
if (prev.class != cur.class) {
let prevList = prev.class ? prev.class.split(" ").filter(Boolean) : [];
let curList = cur.class ? cur.class.split(" ").filter(Boolean) : [];
for (let i = 0; i < prevList.length; i++)
if (curList.indexOf(prevList[i]) == -1)
dom.classList.remove(prevList[i]);
for (let i = 0; i < curList.length; i++)
if (prevList.indexOf(curList[i]) == -1)
dom.classList.add(curList[i]);
if (dom.classList.length == 0)
dom.removeAttribute("class");
}
if (prev.style != cur.style) {
if (prev.style) {
let prop = /\s*([\w\-\xa1-\uffff]+)\s*:(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|\(.*?\)|[^;])*/g, m;
while (m = prop.exec(prev.style))
dom.style.removeProperty(m[1]);
}
if (cur.style)
dom.style.cssText += cur.style;
}
}
function applyOuterDeco(dom, deco, node) {
return patchOuterDeco(dom, dom, noDeco, computeOuterDeco(deco, node, dom.nodeType != 1));
}
function sameOuterDeco(a, b) {
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; i++)
if (!a[i].type.eq(b[i].type))
return false;
return true;
}
function rm(dom) {
let next = dom.nextSibling;
dom.parentNode.removeChild(dom);
return next;
}
class ViewTreeUpdater {
constructor(top, lock, view) {
this.lock = lock;
this.view = view;
this.index = 0;
this.stack = [];
this.changed = false;
this.top = top;
this.preMatch = preMatch(top.node.content, top);
}
// Destroy and remove the children between the given indices in
// `this.top`.
destroyBetween(start, end) {
if (start == end)
return;
for (let i = start; i < end; i++)
this.top.children[i].destroy();
this.top.children.splice(start, end - start);
this.changed = true;
}
// Destroy all remaining children in `this.top`.
destroyRest() {
this.destroyBetween(this.index, this.top.children.length);
}
// Sync the current stack of mark descs with the given array of
// marks, reusing existing mark descs when possible.
syncToMarks(marks, inline, view) {
let keep = 0, depth = this.stack.length >> 1;
let maxKeep = Math.min(depth, marks.length);
while (keep < maxKeep && (keep == depth - 1 ? this.top : this.stack[keep + 1 << 1]).matchesMark(marks[keep]) && marks[keep].type.spec.spanning !== false)
keep++;
while (keep < depth) {
this.destroyRest();
this.top.dirty = NOT_DIRTY;
this.index = this.stack.pop();
this.top = this.stack.pop();
depth--;
}
while (depth < marks.length) {
this.stack.push(this.top, this.index + 1);
let found2 = -1;
for (let i = this.index; i < Math.min(this.index + 3, this.top.children.length); i++) {
let next = this.top.children[i];
if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) {
found2 = i;
break;
}
}
if (found2 > -1) {
if (found2 > this.index) {
this.changed = true;
this.destroyBetween(this.index, found2);
}
this.top = this.top.children[this.index];
} else {
let markDesc = MarkViewDesc.create(this.top, marks[depth], inline, view);
this.top.children.splice(this.index, 0, markDesc);
this.top = markDesc;
this.changed = true;
}
this.index = 0;
depth++;
}
}
// Try to find a node desc matching the given data. Skip over it and
// return true when successful.
findNodeMatch(node, outerDeco, innerDeco, index) {
let found2 = -1, targetDesc;
if (index >= this.preMatch.index && (targetDesc = this.preMatch.matches[index - this.preMatch.index]).parent == this.top && targetDesc.matchesNode(node, outerDeco, innerDeco)) {
found2 = this.top.children.indexOf(targetDesc, this.index);
} else {
for (let i = this.index, e = Math.min(this.top.children.length, i + 5); i < e; i++) {
let child = this.top.children[i];
if (child.matchesNode(node, outerDeco, innerDeco) && !this.preMatch.matched.has(child)) {
found2 = i;
break;
}
}
}
if (found2 < 0)
return false;
this.destroyBetween(this.index, found2);
this.index++;
return true;
}
updateNodeAt(node, outerDeco, innerDeco, index, view) {
let child = this.top.children[index];
if (child.dirty == NODE_DIRTY && child.dom == child.contentDOM)
child.dirty = CONTENT_DIRTY;
if (!child.update(node, outerDeco, innerDeco, view))
return false;
this.destroyBetween(this.index, index);
this.index++;
return true;
}
findIndexWithChild(domNode) {
for (; ; ) {
let parent = domNode.parentNode;
if (!parent)
return -1;
if (parent == this.top.contentDOM) {
let desc = domNode.pmViewDesc;
if (desc)
for (let i = this.index; i < this.top.children.length; i++) {
if (this.top.children[i] == desc)
return i;
}
return -1;
}
domNode = parent;
}
}
// Try to update the next node, if any, to the given data. Checks
// pre-matches to avoid overwriting nodes that could still be used.
updateNextNode(node, outerDeco, innerDeco, view, index) {
for (let i = this.index; i < this.top.children.length; i++) {
let next = this.top.children[i];
if (next instanceof NodeViewDesc) {
let preMatch2 = this.preMatch.matched.get(next);
if (preMatch2 != null && preMatch2 != index)
return false;
let nextDOM = next.dom;
let locked = this.isLocked(nextDOM) && !(node.isText && next.node && next.node.isText && next.nodeDOM.nodeValue == node.text && next.dirty != NODE_DIRTY && sameOuterDeco(outerDeco, next.outerDeco));
if (!locked && next.update(node, outerDeco, innerDeco, view)) {
this.destroyBetween(this.index, i);
if (next.dom != nextDOM)
this.changed = true;
this.index++;
return true;
}
break;
}
}
return false;
}
// Insert the node as a newly created node desc.
addNode(node, outerDeco, innerDeco, view, pos) {
this.top.children.splice(this.index++, 0, NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos));
this.changed = true;
}
placeWidget(widget, view, pos) {
let next = this.index < this.top.children.length ? this.top.children[this.index] : null;
if (next && next.matchesWidget(widget) && (widget == next.widget || !next.widget.type.toDOM.parentNode)) {
this.index++;
} else {
let desc = new WidgetViewDesc(this.top, widget, view, pos);
this.top.children.splice(this.index++, 0, desc);
this.changed = true;
}
}
// Make sure a textblock looks and behaves correctly in
// contentEditable.
addTextblockHacks() {
let lastChild = this.top.children[this.index - 1], parent = this.top;
while (lastChild instanceof MarkViewDesc) {
parent = lastChild;
lastChild = parent.children[parent.children.length - 1];
}
if (!lastChild || // Empty textblock
!(lastChild instanceof TextViewDesc) || /\n$/.test(lastChild.node.text) || this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text)) {
if ((safari || chrome) && lastChild && lastChild.dom.contentEditable == "false")
this.addHackNode("IMG", parent);
this.addHackNode("BR", this.top);
}
}
addHackNode(nodeName, parent) {
if (parent == this.top && this.index < parent.children.length && parent.children[this.index].matchesHack(nodeName)) {
this.index++;
} else {
let dom = document.createElement(nodeName);
if (nodeName == "IMG") {
dom.className = "ProseMirror-separator";
dom.alt = "";
}
if (nodeName == "BR")
dom.className = "ProseMirror-trailingBreak";
let hack = new TrailingHackViewDesc(this.top, [], dom, null);
if (parent != this.top)
parent.children.push(hack);
else
parent.children.splice(this.index++, 0, hack);
this.changed = true;
}
}
isLocked(node) {
return this.lock && (node == this.lock || node.nodeType == 1 && node.contains(this.lock.parentNode));
}
}
function preMatch(frag, parentDesc) {
let curDesc = parentDesc, descI = curDesc.children.length;
let fI = frag.childCount, matched = /* @__PURE__ */ new Map(), matches2 = [];
outer:
while (fI > 0) {
let desc;
for (; ; ) {
if (descI) {
let next = curDesc.children[descI - 1];
if (next instanceof MarkViewDesc) {
curDesc = next;
descI = next.children.length;
} else {
desc = next;
descI--;
break;
}
} else if (curDesc == parentDesc) {
break outer;
} else {
descI = curDesc.parent.children.indexOf(curDesc);
curDesc = curDesc.parent;
}
}
let node = desc.node;
if (!node)
continue;
if (node != frag.child(fI - 1))
break;
--fI;
matched.set(desc, fI);
matches2.push(desc);
}
return { index: fI, matched, matches: matches2.reverse() };
}
function compareSide(a, b) {
return a.type.side - b.type.side;
}
function iterDeco(parent, deco, onWidget, onNode) {
let locals = deco.locals(parent), offset = 0;
if (locals.length == 0) {
for (let i = 0; i < parent.childCount; i++) {
let child = parent.child(i);
onNode(child, locals, deco.forChild(offset, child), i);
offset += child.nodeSize;
}
return;
}
let decoIndex = 0, active = [], restNode = null;
for (let parentIndex = 0; ; ) {
if (decoIndex < locals.length && locals[decoIndex].to == offset) {
let widget = locals[decoIndex++], widgets;
while (decoIndex < locals.length && locals[decoIndex].to == offset)
(widgets || (widgets = [widget])).push(locals[decoIndex++]);
if (widgets) {
widgets.sort(compareSide);
for (let i = 0; i < widgets.length; i++)
onWidget(widgets[i], parentIndex, !!restNode);
} else {
onWidget(widget, parentIndex, !!restNode);
}
}
let child, index;
if (restNode) {
index = -1;
child = restNode;
restNode = null;
} else if (parentIndex < parent.childCount) {
index = parentIndex;
child = parent.child(parentIndex++);
} else {
break;
}
for (let i = 0; i < active.length; i++)
if (active[i].to <= offset)
active.splice(i--, 1);
while (decoIndex < locals.length && locals[decoIndex].from <= offset && locals[decoIndex].to > offset)
active.push(locals[decoIndex++]);
let end = offset + child.nodeSize;
if (child.isText) {
let cutAt = end;
if (decoIndex < locals.length && locals[decoIndex].from < cutAt)
cutAt = locals[decoIndex].from;
for (let i = 0; i < active.length; i++)
if (active[i].to < cutAt)
cutAt = active[i].to;
if (cutAt < end) {
restNode = child.cut(cutAt - offset);
child = child.cut(0, cutAt - offset);
end = cutAt;
index = -1;
}
}
let outerDeco = child.isInline && !child.isLeaf ? active.filter((d) => !d.inline) : active.slice();
onNode(child, outerDeco, deco.forChild(offset, child), index);
offset = end;
}
}
function iosHacks(dom) {
if (dom.nodeName == "UL" || dom.nodeName == "OL") {
let oldCSS = dom.style.cssText;
dom.style.cssText = oldCSS + "; list-style: square !important";
window.getComputedStyle(dom).listStyle;
dom.style.cssText = oldCSS;
}
}
function nearbyTextNode(node, offset) {
for (; ; ) {
if (node.nodeType == 3)
return node;
if (node.nodeType == 1 && offset > 0) {
if (node.childNodes.length > offset && node.childNodes[offset].nodeType == 3)
return node.childNodes[offset];
node = node.childNodes[offset - 1];
offset = nodeSize(node);
} else if (node.nodeType == 1 && offset < node.childNodes.length) {
node = node.childNodes[offset];
offset = 0;
} else {
return null;
}
}
}
function findTextInFragment(frag, text2, from2, to) {
for (let i = 0, pos = 0; i < frag.childCount && pos <= to; ) {
let child = frag.child(i++), childStart = pos;
pos += child.nodeSize;
if (!child.isText)
continue;
let str = child.text;
while (i < frag.childCount) {
let next = frag.child(i++);
pos += next.nodeSize;
if (!next.isText)
break;
str += next.text;
}
if (pos >= from2) {
let found2 = childStart < to ? str.lastIndexOf(text2, to - childStart - 1) : -1;
if (found2 >= 0 && found2 + text2.length + childStart >= from2)
return childStart + found2;
if (from2 == to && str.length >= to + text2.length - childStart && str.slice(to - childStart, to - childStart + text2.length) == text2)
return to;
}
}
return -1;
}
function replaceNodes(nodes, from2, to, view, replacement) {
let result = [];
for (let i = 0, off = 0; i < nodes.length; i++) {
let child = nodes[i], start = off, end = off += child.size;
if (start >= to || end <= from2) {
result.push(child);
} else {
if (start < from2)
result.push(child.slice(0, from2 - start, view));
if (replacement) {
result.push(replacement);
replacement = void 0;
}
if (end > to)
result.push(child.slice(to - start, child.size, view));
}
}
return result;
}
function selectionFromDOM(view, origin = null) {
let domSel = view.domSelectionRange(), doc2 = view.state.doc;
if (!domSel.focusNode)
return null;
let nearestDesc = view.docView.nearestDesc(domSel.focusNode), inWidget = nearestDesc && nearestDesc.size == 0;
let head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1);
if (head < 0)
return null;
let $head = doc2.resolve(head), $anchor, selection;
if (selectionCollapsed(domSel)) {
$anchor = $head;
while (nearestDesc && !nearestDesc.node)
nearestDesc = nearestDesc.parent;
let nearestDescNode = nearestDesc.node;
if (nearestDesc && nearestDescNode.isAtom && NodeSelection.isSelectable(nearestDescNode) && nearestDesc.parent && !(nearestDescNode.isInline && isOnEdge(domSel.focusNode, domSel.focusOffset, nearestDesc.dom))) {
let pos = nearestDesc.posBefore;
selection = new NodeSelection(head == pos ? $head : doc2.resolve(pos));
}
} else {
let anchor = view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset, 1);
if (anchor < 0)
return null;
$anchor = doc2.resolve(anchor);
}
if (!selection) {
let bias = origin == "pointer" || view.state.selection.head < $head.pos && !inWidget ? 1 : -1;
selection = selectionBetween(view, $anchor, $head, bias);
}
return selection;
}
function editorOwnsSelection(view) {
return view.editable ? view.hasFocus() : hasSelection(view) && document.activeElement && document.activeElement.contains(view.dom);
}
function selectionToDOM(view, force = false) {
let sel = view.state.selection;
syncNodeSelection(view, sel);
if (!editorOwnsSelection(view))
return;
if (!force && view.input.mouseDown && view.input.mouseDown.allowDefault && chrome) {
let domSel = view.domSelectionRange(), curSel = view.domObserver.currentSelection;
if (domSel.anchorNode && curSel.anchorNode && isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)) {
view.input.mouseDown.delayedSelectionSync = true;
view.domObserver.setCurSelection();
return;
}
}
view.domObserver.disconnectSelection();
if (view.cursorWrapper) {
selectCursorWrapper(view);
} else {
let { anchor, head } = sel, resetEditableFrom, resetEditableTo;
if (brokenSelectBetweenUneditable && !(sel instanceof TextSelection)) {
if (!sel.$from.parent.inlineContent)
resetEditableFrom = temporarilyEditableNear(view, sel.from);
if (!sel.empty && !sel.$from.parent.inlineContent)
resetEditableTo = temporarilyEditableNear(view, sel.to);
}
view.docView.setSelection(anchor, head, view.root, force);
if (brokenSelectBetweenUneditable) {
if (resetEditableFrom)
resetEditable(resetEditableFrom);
if (resetEditableTo)
resetEditable(resetEditableTo);
}
if (sel.visible) {
view.dom.classList.remove("ProseMirror-hideselection");
} else {
view.dom.classList.add("ProseMirror-hideselection");
if ("onselectionchange" in document)
removeClassOnSelectionChange(view);
}
}
view.domObserver.setCurSelection();
view.domObserver.connectSelection();
}
const brokenSelectBetweenUneditable = safari || chrome && chrome_version < 63;
function temporarilyEditableNear(view, pos) {
let { node, offset } = view.docView.domFromPos(pos, 0);
let after = offset < node.childNodes.length ? node.childNodes[offset] : null;
let before = offset ? node.childNodes[offset - 1] : null;
if (safari && after && after.contentEditable == "false")
return setEditable(after);
if ((!after || after.contentEditable == "false") && (!before || before.contentEditable == "false")) {
if (after)
return setEditable(after);
else if (before)
return setEditable(before);
}
}
function setEditable(element) {
element.contentEditable = "true";
if (safari && element.draggable) {
element.draggable = false;
element.wasDraggable = true;
}
return element;
}
function resetEditable(element) {
element.contentEditable = "false";
if (element.wasDraggable) {
element.draggable = true;
element.wasDraggable = null;
}
}
function removeClassOnSelectionChange(view) {
let doc2 = view.dom.ownerDocument;
doc2.removeEventListener("selectionchange", view.input.hideSelectionGuard);
let domSel = view.domSelectionRange();
let node = domSel.anchorNode, offset = domSel.anchorOffset;
doc2.addEventListener("selectionchange", view.input.hideSelectionGuard = () => {
if (domSel.anchorNode != node || domSel.anchorOffset != offset) {
doc2.removeEventListener("selectionchange", view.input.hideSelectionGuard);
setTimeout(() => {
if (!editorOwnsSelection(view) || view.state.selection.visible)
view.dom.classList.remove("ProseMirror-hideselection");
}, 20);
}
});
}
function selectCursorWrapper(view) {
let domSel = view.domSelection(), range = document.createRange();
let node = view.cursorWrapper.dom, img = node.nodeName == "IMG";
if (img)
range.setEnd(node.parentNode, domIndex(node) + 1);
else
range.setEnd(node, 0);
range.collapse(false);
domSel.removeAllRanges();
domSel.addRange(range);
if (!img && !view.state.selection.visible && ie && ie_version <= 11) {
node.disabled = true;
node.disabled = false;
}
}
function syncNodeSelection(view, sel) {
if (sel instanceof NodeSelection) {
let desc = view.docView.descAt(sel.from);
if (desc != view.lastSelectedViewDesc) {
clearNodeSelection(view);
if (desc)
desc.selectNode();
view.lastSelectedViewDesc = desc;
}
} else {
clearNodeSelection(view);
}
}
function clearNodeSelection(view) {
if (view.lastSelectedViewDesc) {
if (view.lastSelectedViewDesc.parent)
view.lastSelectedViewDesc.deselectNode();
view.lastSelectedViewDesc = void 0;
}
}
function selectionBetween(view, $anchor, $head, bias) {
return view.someProp("createSelectionBetween", (f) => f(view, $anchor, $head)) || TextSelection.between($anchor, $head, bias);
}
function hasFocusAndSelection(view) {
if (view.editable && !view.hasFocus())
return false;
return hasSelection(view);
}
function hasSelection(view) {
let sel = view.domSelectionRange();
if (!sel.anchorNode)
return false;
try {
return view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode) && (view.editable || view.dom.contains(sel.focusNode.nodeType == 3 ? sel.focusNode.parentNode : sel.focusNode));
} catch (_) {
return false;
}
}
function anchorInRightPlace(view) {
let anchorDOM = view.docView.domFromPos(view.state.selection.anchor, 0);
let domSel = view.domSelectionRange();
return isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset);
}
function moveSelectionBlock(state, dir) {
let { $anchor, $head } = state.selection;
let $side = dir > 0 ? $anchor.max($head) : $anchor.min($head);
let $start = !$side.parent.inlineContent ? $side : $side.depth ? state.doc.resolve(dir > 0 ? $side.after() : $side.before()) : null;
return $start && Selection.findFrom($start, dir);
}
function apply(view, sel) {
view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
return true;
}
function selectHorizontally(view, dir, mods) {
let sel = view.state.selection;
if (sel instanceof TextSelection) {
if (!sel.empty || mods.indexOf("s") > -1) {
return false;
} else if (view.endOfTextblock(dir > 0 ? "right" : "left")) {
let next = moveSelectionBlock(view.state, dir);
if (next && next instanceof NodeSelection)
return apply(view, next);
return false;
} else if (!(mac$1 && mods.indexOf("m") > -1)) {
let $head = sel.$head, node = $head.textOffset ? null : dir < 0 ? $head.nodeBefore : $head.nodeAfter, desc;
if (!node || node.isText)
return false;
let nodePos = dir < 0 ? $head.pos - node.nodeSize : $head.pos;
if (!(node.isAtom || (desc = view.docView.descAt(nodePos)) && !desc.contentDOM))
return false;
if (NodeSelection.isSelectable(node)) {
return apply(view, new NodeSelection(dir < 0 ? view.state.doc.resolve($head.pos - node.nodeSize) : $head));
} else if (webkit) {
return apply(view, new TextSelection(view.state.doc.resolve(dir < 0 ? nodePos : nodePos + node.nodeSize)));
} else {
return false;
}
}
} else if (sel instanceof NodeSelection && sel.node.isInline) {
return apply(view, new TextSelection(dir > 0 ? sel.$to : sel.$from));
} else {
let next = moveSelectionBlock(view.state, dir);
if (next)
return apply(view, next);
return false;
}
}
function nodeLen(node) {
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
}
function isIgnorable(dom) {
let desc = dom.pmViewDesc;
return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR");
}
function skipIgnoredNodesLeft(view) {
let sel = view.domSelectionRange();
let node = sel.focusNode, offset = sel.focusOffset;
if (!node)
return;
let moveNode, moveOffset, force = false;
if (gecko && node.nodeType == 1 && offset < nodeLen(node) && isIgnorable(node.childNodes[offset]))
force = true;
for (; ; ) {
if (offset > 0) {
if (node.nodeType != 1) {
break;
} else {
let before = node.childNodes[offset - 1];
if (isIgnorable(before)) {
moveNode = node;
moveOffset = --offset;
} else if (before.nodeType == 3) {
node = before;
offset = node.nodeValue.length;
} else
break;
}
} else if (isBlockNode(node)) {
break;
} else {
let prev = node.previousSibling;
while (prev && isIgnorable(prev)) {
moveNode = node.parentNode;
moveOffset = domIndex(prev);
prev = prev.previousSibling;
}
if (!prev) {
node = node.parentNode;
if (node == view.dom)
break;
offset = 0;
} else {
node = prev;
offset = nodeLen(node);
}
}
}
if (force)
setSelFocus(view, node, offset);
else if (moveNode)
setSelFocus(view, moveNode, moveOffset);
}
function skipIgnoredNodesRight(view) {
let sel = view.domSelectionRange();
let node = sel.focusNode, offset = sel.focusOffset;
if (!node)
return;
let len = nodeLen(node);
let moveNode, moveOffset;
for (; ; ) {
if (offset < len) {
if (node.nodeType != 1)
break;
let after = node.childNodes[offset];
if (isIgnorable(after)) {
moveNode = node;
moveOffset = ++offset;
} else
break;
} else if (isBlockNode(node)) {
break;
} else {
let next = node.nextSibling;
while (next && isIgnorable(next)) {
moveNode = next.parentNode;
moveOffset = domIndex(next) + 1;
next = next.nextSibling;
}
if (!next) {
node = node.parentNode;
if (node == view.dom)
break;
offset = len = 0;
} else {
node = next;
offset = 0;
len = nodeLen(node);
}
}
}
if (moveNode)
setSelFocus(view, moveNode, moveOffset);
}
function isBlockNode(dom) {
let desc = dom.pmViewDesc;
return desc && desc.node && desc.node.isBlock;
}
function setSelFocus(view, node, offset) {
let sel = view.domSelection();
if (selectionCollapsed(sel)) {
let range = document.createRange();
range.setEnd(node, offset);
range.setStart(node, offset);
sel.removeAllRanges();
sel.addRange(range);
} else if (sel.extend) {
sel.extend(node, offset);
}
view.domObserver.setCurSelection();
let { state } = view;
setTimeout(() => {
if (view.state == state)
selectionToDOM(view);
}, 50);
}
function selectVertically(view, dir, mods) {
let sel = view.state.selection;
if (sel instanceof TextSelection && !sel.empty || mods.indexOf("s") > -1)
return false;
if (mac$1 && mods.indexOf("m") > -1)
return false;
let { $from, $to } = sel;
if (!$from.parent.inlineContent || view.endOfTextblock(dir < 0 ? "up" : "down")) {
let next = moveSelectionBlock(view.state, dir);
if (next && next instanceof NodeSelection)
return apply(view, next);
}
if (!$from.parent.inlineContent) {
let side = dir < 0 ? $from : $to;
let beyond = sel instanceof AllSelection ? Selection.near(side, dir) : Selection.findFrom(side, dir);
return beyond ? apply(view, beyond) : false;
}
return false;
}
function stopNativeHorizontalDelete(view, dir) {
if (!(view.state.selection instanceof TextSelection))
return true;
let { $head, $anchor, empty: empty2 } = view.state.selection;
if (!$head.sameParent($anchor))
return true;
if (!empty2)
return false;
if (view.endOfTextblock(dir > 0 ? "forward" : "backward"))
return true;
let nextNode = !$head.textOffset && (dir < 0 ? $head.nodeBefore : $head.nodeAfter);
if (nextNode && !nextNode.isText) {
let tr = view.state.tr;
if (dir < 0)
tr.delete($head.pos - nextNode.nodeSize, $head.pos);
else
tr.delete($head.pos, $head.pos + nextNode.nodeSize);
view.dispatch(tr);
return true;
}
return false;
}
function switchEditable(view, node, state) {
view.domObserver.stop();
node.contentEditable = state;
view.domObserver.start();
}
function safariDownArrowBug(view) {
if (!safari || view.state.selection.$head.parentOffset > 0)
return false;
let { focusNode, focusOffset } = view.domSelectionRange();
if (focusNode && focusNode.nodeType == 1 && focusOffset == 0 && focusNode.firstChild && focusNode.firstChild.contentEditable == "false") {
let child = focusNode.firstChild;
switchEditable(view, child, "true");
setTimeout(() => switchEditable(view, child, "false"), 20);
}
return false;
}
function getMods(event) {
let result = "";
if (event.ctrlKey)
result += "c";
if (event.metaKey)
result += "m";
if (event.altKey)
result += "a";
if (event.shiftKey)
result += "s";
return result;
}
function captureKeyDown(view, event) {
let code = event.keyCode, mods = getMods(event);
if (code == 8 || mac$1 && code == 72 && mods == "c") {
return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view);
} else if (code == 46 || mac$1 && code == 68 && mods == "c") {
return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view);
} else if (code == 13 || code == 27) {
return true;
} else if (code == 37 || mac$1 && code == 66 && mods == "c") {
return selectHorizontally(view, -1, mods) || skipIgnoredNodesLeft(view);
} else if (code == 39 || mac$1 && code == 70 && mods == "c") {
return selectHorizontally(view, 1, mods) || skipIgnoredNodesRight(view);
} else if (code == 38 || mac$1 && code == 80 && mods == "c") {
return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view);
} else if (code == 40 || mac$1 && code == 78 && mods == "c") {
return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view);
} else if (mods == (mac$1 ? "m" : "c") && (code == 66 || code == 73 || code == 89 || code == 90)) {
return true;
}
return false;
}
function serializeForClipboard(view, slice2) {
view.someProp("transformCopied", (f) => {
slice2 = f(slice2, view);
});
let context = [], { content, openStart, openEnd } = slice2;
while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
openStart--;
openEnd--;
let node = content.firstChild;
context.push(node.type.name, node.attrs != node.type.defaultAttrs ? node.attrs : null);
content = node.content;
}
let serializer = view.someProp("clipboardSerializer") || DOMSerializer.fromSchema(view.state.schema);
let doc2 = detachedDoc(), wrap2 = doc2.createElement("div");
wrap2.appendChild(serializer.serializeFragment(content, { document: doc2 }));
let firstChild = wrap2.firstChild, needsWrap, wrappers = 0;
while (firstChild && firstChild.nodeType == 1 && (needsWrap = wrapMap[firstChild.nodeName.toLowerCase()])) {
for (let i = needsWrap.length - 1; i >= 0; i--) {
let wrapper = doc2.createElement(needsWrap[i]);
while (wrap2.firstChild)
wrapper.appendChild(wrap2.firstChild);
wrap2.appendChild(wrapper);
wrappers++;
}
firstChild = wrap2.firstChild;
}
if (firstChild && firstChild.nodeType == 1)
firstChild.setAttribute("data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`);
let text2 = view.someProp("clipboardTextSerializer", (f) => f(slice2, view)) || slice2.content.textBetween(0, slice2.content.size, "\n\n");
return { dom: wrap2, text: text2 };
}
function parseFromClipboard(view, text2, html, plainText, $context) {
let inCode = $context.parent.type.spec.code;
let dom, slice2;
if (!html && !text2)
return null;
let asText = text2 && (plainText || inCode || !html);
if (asText) {
view.someProp("transformPastedText", (f) => {
text2 = f(text2, inCode || plainText, view);
});
if (inCode)
return text2 ? new Slice(Fragment.from(view.state.schema.text(text2.replace(/\r\n?/g, "\n"))), 0, 0) : Slice.empty;
let parsed = view.someProp("clipboardTextParser", (f) => f(text2, $context, plainText, view));
if (parsed) {
slice2 = parsed;
} else {
let marks = $context.marks();
let { schema } = view.state, serializer = DOMSerializer.fromSchema(schema);
dom = document.createElement("div");
text2.split(/(?:\r\n?|\n)+/).forEach((block) => {
let p = dom.appendChild(document.createElement("p"));
if (block)
p.appendChild(serializer.serializeNode(schema.text(block, marks)));
});
}
} else {
view.someProp("transformPastedHTML", (f) => {
html = f(html, view);
});
dom = readHTML(html);
if (webkit)
restoreReplacedSpaces(dom);
}
let contextNode = dom && dom.querySelector("[data-pm-slice]");
let sliceData = contextNode && /^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(contextNode.getAttribute("data-pm-slice") || "");
if (sliceData && sliceData[3])
for (let i = +sliceData[3]; i > 0; i--) {
let child = dom.firstChild;
while (child && child.nodeType != 1)
child = child.nextSibling;
if (!child)
break;
dom = child;
}
if (!slice2) {
let parser = view.someProp("clipboardParser") || view.someProp("domParser") || DOMParser.fromSchema(view.state.schema);
slice2 = parser.parseSlice(dom, {
preserveWhitespace: !!(asText || sliceData),
context: $context,
ruleFromNode(dom2) {
if (dom2.nodeName == "BR" && !dom2.nextSibling && dom2.parentNode && !inlineParents.test(dom2.parentNode.nodeName))
return { ignore: true };
return null;
}
});
}
if (sliceData) {
slice2 = addContext(closeSlice(slice2, +sliceData[1], +sliceData[2]), sliceData[4]);
} else {
slice2 = Slice.maxOpen(normalizeSiblings(slice2.content, $context), true);
if (slice2.openStart || slice2.openEnd) {
let openStart = 0, openEnd = 0;
for (let node = slice2.content.firstChild; openStart < slice2.openStart && !node.type.spec.isolating; openStart++, node = node.firstChild) {
}
for (let node = slice2.content.lastChild; openEnd < slice2.openEnd && !node.type.spec.isolating; openEnd++, node = node.lastChild) {
}
slice2 = closeSlice(slice2, openStart, openEnd);
}
}
view.someProp("transformPasted", (f) => {
slice2 = f(slice2, view);
});
return slice2;
}
const inlineParents = /^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i;
function normalizeSiblings(fragment, $context) {
if (fragment.childCount < 2)
return fragment;
for (let d = $context.depth; d >= 0; d--) {
let parent = $context.node(d);
let match = parent.contentMatchAt($context.index(d));
let lastWrap, result = [];
fragment.forEach((node) => {
if (!result)
return;
let wrap2 = match.findWrapping(node.type), inLast;
if (!wrap2)
return result = null;
if (inLast = result.length && lastWrap.length && addToSibling(wrap2, lastWrap, node, result[result.length - 1], 0)) {
result[result.length - 1] = inLast;
} else {
if (result.length)
result[result.length - 1] = closeRight(result[result.length - 1], lastWrap.length);
let wrapped = withWrappers(node, wrap2);
result.push(wrapped);
match = match.matchType(wrapped.type);
lastWrap = wrap2;
}
});
if (result)
return Fragment.from(result);
}
return fragment;
}
function withWrappers(node, wrap2, from2 = 0) {
for (let i = wrap2.length - 1; i >= from2; i--)
node = wrap2[i].create(null, Fragment.from(node));
return node;
}
function addToSibling(wrap2, lastWrap, node, sibling, depth) {
if (depth < wrap2.length && depth < lastWrap.length && wrap2[depth] == lastWrap[depth]) {
let inner = addToSibling(wrap2, lastWrap, node, sibling.lastChild, depth + 1);
if (inner)
return sibling.copy(sibling.content.replaceChild(sibling.childCount - 1, inner));
let match = sibling.contentMatchAt(sibling.childCount);
if (match.matchType(depth == wrap2.length - 1 ? node.type : wrap2[depth + 1]))
return sibling.copy(sibling.content.append(Fragment.from(withWrappers(node, wrap2, depth + 1))));
}
}
function closeRight(node, depth) {
if (depth == 0)
return node;
let fragment = node.content.replaceChild(node.childCount - 1, closeRight(node.lastChild, depth - 1));
let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment.empty, true);
return node.copy(fragment.append(fill));
}
function closeRange(fragment, side, from2, to, depth, openEnd) {
let node = side < 0 ? fragment.firstChild : fragment.lastChild, inner = node.content;
if (depth < to - 1)
inner = closeRange(inner, side, from2, to, depth + 1, openEnd);
if (depth >= from2)
inner = side < 0 ? node.contentMatchAt(0).fillBefore(inner, fragment.childCount > 1 || openEnd <= depth).append(inner) : inner.append(node.contentMatchAt(node.childCount).fillBefore(Fragment.empty, true));
return fragment.replaceChild(side < 0 ? 0 : fragment.childCount - 1, node.copy(inner));
}
function closeSlice(slice2, openStart, openEnd) {
if (openStart < slice2.openStart)
slice2 = new Slice(closeRange(slice2.content, -1, openStart, slice2.openStart, 0, slice2.openEnd), openStart, slice2.openEnd);
if (openEnd < slice2.openEnd)
slice2 = new Slice(closeRange(slice2.content, 1, openEnd, slice2.openEnd, 0, 0), slice2.openStart, openEnd);
return slice2;
}
const wrapMap = {
thead: ["table"],
tbody: ["table"],
tfoot: ["table"],
caption: ["table"],
colgroup: ["table"],
col: ["table", "colgroup"],
tr: ["table", "tbody"],
td: ["table", "tbody", "tr"],
th: ["table", "tbody", "tr"]
};
let _detachedDoc = null;
function detachedDoc() {
return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
}
function readHTML(html) {
let metas = /^(\s*<meta [^>]*>)*/.exec(html);
if (metas)
html = html.slice(metas[0].length);
let elt = detachedDoc().createElement("div");
let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap2;
if (wrap2 = firstTag && wrapMap[firstTag[1].toLowerCase()])
html = wrap2.map((n) => "<" + n + ">").join("") + html + wrap2.map((n) => "</" + n + ">").reverse().join("");
elt.innerHTML = html;
if (wrap2)
for (let i = 0; i < wrap2.length; i++)
elt = elt.querySelector(wrap2[i]) || elt;
return elt;
}
function restoreReplacedSpaces(dom) {
let nodes = dom.querySelectorAll(chrome ? "span:not([class]):not([style])" : "span.Apple-converted-space");
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (node.childNodes.length == 1 && node.textContent == " " && node.parentNode)
node.parentNode.replaceChild(dom.ownerDocument.createTextNode(" "), node);
}
}
function addContext(slice2, context) {
if (!slice2.size)
return slice2;
let schema = slice2.content.firstChild.type.schema, array;
try {
array = JSON.parse(context);
} catch (e) {
return slice2;
}
let { content, openStart, openEnd } = slice2;
for (let i = array.length - 2; i >= 0; i -= 2) {
let type = schema.nodes[array[i]];
if (!type || type.hasRequiredAttrs())
break;
content = Fragment.from(type.create(array[i + 1], content));
openStart++;
openEnd++;
}
return new Slice(content, openStart, openEnd);
}
const handlers = {};
const editHandlers = {};
const passiveHandlers = { touchstart: true, touchmove: true };
class InputState {
constructor() {
this.shiftKey = false;
this.mouseDown = null;
this.lastKeyCode = null;
this.lastKeyCodeTime = 0;
this.lastClick = { time: 0, x: 0, y: 0, type: "" };
this.lastSelectionOrigin = null;
this.lastSelectionTime = 0;
this.lastIOSEnter = 0;
this.lastIOSEnterFallbackTimeout = -1;
this.lastFocus = 0;
this.lastTouch = 0;
this.lastAndroidDelete = 0;
this.composing = false;
this.composingTimeout = -1;
this.compositionNodes = [];
this.compositionEndedAt = -2e8;
this.domChangeCount = 0;
this.eventHandlers = /* @__PURE__ */ Object.create(null);
this.hideSelectionGuard = null;
}
}
function initInput(view) {
for (let event in handlers) {
let handler = handlers[event];
view.dom.addEventListener(event, view.input.eventHandlers[event] = (event2) => {
if (eventBelongsToView(view, event2) && !runCustomHandler(view, event2) && (view.editable || !(event2.type in editHandlers)))
handler(view, event2);
}, passiveHandlers[event] ? { passive: true } : void 0);
}
if (safari)
view.dom.addEventListener("input", () => null);
ensureListeners(view);
}
function setSelectionOrigin(view, origin) {
view.input.lastSelectionOrigin = origin;
view.input.lastSelectionTime = Date.now();
}
function destroyInput(view) {
view.domObserver.stop();
for (let type in view.input.eventHandlers)
view.dom.removeEventListener(type, view.input.eventHandlers[type]);
clearTimeout(view.input.composingTimeout);
clearTimeout(view.input.lastIOSEnterFallbackTimeout);
}
function ensureListeners(view) {
view.someProp("handleDOMEvents", (currentHandlers) => {
for (let type in currentHandlers)
if (!view.input.eventHandlers[type])
view.dom.addEventListener(type, view.input.eventHandlers[type] = (event) => runCustomHandler(view, event));
});
}
function runCustomHandler(view, event) {
return view.someProp("handleDOMEvents", (handlers2) => {
let handler = handlers2[event.type];
return handler ? handler(view, event) || event.defaultPrevented : false;
});
}
function eventBelongsToView(view, event) {
if (!event.bubbles)
return true;
if (event.defaultPrevented)
return false;
for (let node = event.target; node != view.dom; node = node.parentNode)
if (!node || node.nodeType == 11 || node.pmViewDesc && node.pmViewDesc.stopEvent(event))
return false;
return true;
}
function dispatchEvent(view, event) {
if (!runCustomHandler(view, event) && handlers[event.type] && (view.editable || !(event.type in editHandlers)))
handlers[event.type](view, event);
}
editHandlers.keydown = (view, _event) => {
let event = _event;
view.input.shiftKey = event.keyCode == 16 || event.shiftKey;
if (inOrNearComposition(view, event))
return;
view.input.lastKeyCode = event.keyCode;
view.input.lastKeyCodeTime = Date.now();
if (android && chrome && event.keyCode == 13)
return;
if (event.keyCode != 229)
view.domObserver.forceFlush();
if (ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
let now = Date.now();
view.input.lastIOSEnter = now;
view.input.lastIOSEnterFallbackTimeout = setTimeout(() => {
if (view.input.lastIOSEnter == now) {
view.someProp("handleKeyDown", (f) => f(view, keyEvent(13, "Enter")));
view.input.lastIOSEnter = 0;
}
}, 200);
} else if (view.someProp("handleKeyDown", (f) => f(view, event)) || captureKeyDown(view, event)) {
event.preventDefault();
} else {
setSelectionOrigin(view, "key");
}
};
editHandlers.keyup = (view, event) => {
if (event.keyCode == 16)
view.input.shiftKey = false;
};
editHandlers.keypress = (view, _event) => {
let event = _event;
if (inOrNearComposition(view, event) || !event.charCode || event.ctrlKey && !event.altKey || mac$1 && event.metaKey)
return;
if (view.someProp("handleKeyPress", (f) => f(view, event))) {
event.preventDefault();
return;
}
let sel = view.state.selection;
if (!(sel instanceof TextSelection) || !sel.$from.sameParent(sel.$to)) {
let text2 = String.fromCharCode(event.charCode);
if (!/[\r\n]/.test(text2) && !view.someProp("handleTextInput", (f) => f(view, sel.$from.pos, sel.$to.pos, text2)))
view.dispatch(view.state.tr.insertText(text2).scrollIntoView());
event.preventDefault();
}
};
function eventCoords(event) {
return { left: event.clientX, top: event.clientY };
}
function isNear(event, click) {
let dx = click.x - event.clientX, dy = click.y - event.clientY;
return dx * dx + dy * dy < 100;
}
function runHandlerOnContext(view, propName, pos, inside, event) {
if (inside == -1)
return false;
let $pos = view.state.doc.resolve(inside);
for (let i = $pos.depth + 1; i > 0; i--) {
if (view.someProp(propName, (f) => i > $pos.depth ? f(view, pos, $pos.nodeAfter, $pos.before(i), event, true) : f(view, pos, $pos.node(i), $pos.before(i), event, false)))
return true;
}
return false;
}
function updateSelection(view, selection, origin) {
if (!view.focused)
view.focus();
let tr = view.state.tr.setSelection(selection);
if (origin == "pointer")
tr.setMeta("pointer", true);
view.dispatch(tr);
}
function selectClickedLeaf(view, inside) {
if (inside == -1)
return false;
let $pos = view.state.doc.resolve(inside), node = $pos.nodeAfter;
if (node && node.isAtom && NodeSelection.isSelectable(node)) {
updateSelection(view, new NodeSelection($pos), "pointer");
return true;
}
return false;
}
function selectClickedNode(view, inside) {
if (inside == -1)
return false;
let sel = view.state.selection, selectedNode, selectAt;
if (sel instanceof NodeSelection)
selectedNode = sel.node;
let $pos = view.state.doc.resolve(inside);
for (let i = $pos.depth + 1; i > 0; i--) {
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i);
if (NodeSelection.isSelectable(node)) {
if (selectedNode && sel.$from.depth > 0 && i >= sel.$from.depth && $pos.before(sel.$from.depth + 1) == sel.$from.pos)
selectAt = $pos.before(sel.$from.depth);
else
selectAt = $pos.before(i);
break;
}
}
if (selectAt != null) {
updateSelection(view, NodeSelection.create(view.state.doc, selectAt), "pointer");
return true;
} else {
return false;
}
}
function handleSingleClick(view, pos, inside, event, selectNode) {
return runHandlerOnContext(view, "handleClickOn", pos, inside, event) || view.someProp("handleClick", (f) => f(view, pos, event)) || (selectNode ? selectClickedNode(view, inside) : selectClickedLeaf(view, inside));
}
function handleDoubleClick(view, pos, inside, event) {
return runHandlerOnContext(view, "handleDoubleClickOn", pos, inside, event) || view.someProp("handleDoubleClick", (f) => f(view, pos, event));
}
function handleTripleClick(view, pos, inside, event) {
return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) || view.someProp("handleTripleClick", (f) => f(view, pos, event)) || defaultTripleClick(view, inside, event);
}
function defaultTripleClick(view, inside, event) {
if (event.button != 0)
return false;
let doc2 = view.state.doc;
if (inside == -1) {
if (doc2.inlineContent) {
updateSelection(view, TextSelection.create(doc2, 0, doc2.content.size), "pointer");
return true;
}
return false;
}
let $pos = doc2.resolve(inside);
for (let i = $pos.depth + 1; i > 0; i--) {
let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i);
let nodePos = $pos.before(i);
if (node.inlineContent)
updateSelection(view, TextSelection.create(doc2, nodePos + 1, nodePos + 1 + node.content.size), "pointer");
else if (NodeSelection.isSelectable(node))
updateSelection(view, NodeSelection.create(doc2, nodePos), "pointer");
else
continue;
return true;
}
}
function forceDOMFlush(view) {
return endComposition(view);
}
const selectNodeModifier = mac$1 ? "metaKey" : "ctrlKey";
handlers.mousedown = (view, _event) => {
let event = _event;
view.input.shiftKey = event.shiftKey;
let flushed = forceDOMFlush(view);
let now = Date.now(), type = "singleClick";
if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier]) {
if (view.input.lastClick.type == "singleClick")
type = "doubleClick";
else if (view.input.lastClick.type == "doubleClick")
type = "tripleClick";
}
view.input.lastClick = { time: now, x: event.clientX, y: event.clientY, type };
let pos = view.posAtCoords(eventCoords(event));
if (!pos)
return;
if (type == "singleClick") {
if (view.input.mouseDown)
view.input.mouseDown.done();
view.input.mouseDown = new MouseDown(view, pos, event, !!flushed);
} else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) {
event.preventDefault();
} else {
setSelectionOrigin(view, "pointer");
}
};
class MouseDown {
constructor(view, pos, event, flushed) {
this.view = view;
this.pos = pos;
this.event = event;
this.flushed = flushed;
this.delayedSelectionSync = false;
this.mightDrag = null;
this.startDoc = view.state.doc;
this.selectNode = !!event[selectNodeModifier];
this.allowDefault = event.shiftKey;
let targetNode, targetPos;
if (pos.inside > -1) {
targetNode = view.state.doc.nodeAt(pos.inside);
targetPos = pos.inside;
} else {
let $pos = view.state.doc.resolve(pos.pos);
targetNode = $pos.parent;
targetPos = $pos.depth ? $pos.before() : 0;
}
const target = flushed ? null : event.target;
const targetDesc = target ? view.docView.nearestDesc(target, true) : null;
this.target = targetDesc ? targetDesc.dom : null;
let { selection } = view.state;
if (event.button == 0 && targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false || selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
this.mightDrag = {
node: targetNode,
pos: targetPos,
addAttr: !!(this.target && !this.target.draggable),
setUneditable: !!(this.target && gecko && !this.target.hasAttribute("contentEditable"))
};
if (this.target && this.mightDrag && (this.mightDrag.addAttr || this.mightDrag.setUneditable)) {
this.view.domObserver.stop();
if (this.mightDrag.addAttr)
this.target.draggable = true;
if (this.mightDrag.setUneditable)
setTimeout(() => {
if (this.view.input.mouseDown == this)
this.target.setAttribute("contentEditable", "false");
}, 20);
this.view.domObserver.start();
}
view.root.addEventListener("mouseup", this.up = this.up.bind(this));
view.root.addEventListener("mousemove", this.move = this.move.bind(this));
setSelectionOrigin(view, "pointer");
}
done() {
this.view.root.removeEventListener("mouseup", this.up);
this.view.root.removeEventListener("mousemove", this.move);
if (this.mightDrag && this.target) {
this.view.domObserver.stop();
if (this.mightDrag.addAttr)
this.target.removeAttribute("draggable");
if (this.mightDrag.setUneditable)
this.target.removeAttribute("contentEditable");
this.view.domObserver.start();
}
if (this.delayedSelectionSync)
setTimeout(() => selectionToDOM(this.view));
this.view.input.mouseDown = null;
}
up(event) {
this.done();
if (!this.view.dom.contains(event.target))
return;
let pos = this.pos;
if (this.view.state.doc != this.startDoc)
pos = this.view.posAtCoords(eventCoords(event));
this.updateAllowDefault(event);
if (this.allowDefault || !pos) {
setSelectionOrigin(this.view, "pointer");
} else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
event.preventDefault();
} else if (event.button == 0 && (this.flushed || // Safari ignores clicks on draggable elements
safari && this.mightDrag && !this.mightDrag.node.isAtom || // Chrome will sometimes treat a node selection as a
// cursor, but still report that the node is selected
// when asked through getSelection. You'll then get a
// situation where clicking at the point where that
// (hidden) cursor is doesn't change the selection, and
// thus doesn't get a reaction from ProseMirror. This
// works around that.
chrome && !this.view.state.selection.visible && Math.min(Math.abs(pos.pos - this.view.state.selection.from), Math.abs(pos.pos - this.view.state.selection.to)) <= 2)) {
updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer");
event.preventDefault();
} else {
setSelectionOrigin(this.view, "pointer");
}
}
move(event) {
this.updateAllowDefault(event);
setSelectionOrigin(this.view, "pointer");
if (event.buttons == 0)
this.done();
}
updateAllowDefault(event) {
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 || Math.abs(this.event.y - event.clientY) > 4))
this.allowDefault = true;
}
}
handlers.touchstart = (view) => {
view.input.lastTouch = Date.now();
forceDOMFlush(view);
setSelectionOrigin(view, "pointer");
};
handlers.touchmove = (view) => {
view.input.lastTouch = Date.now();
setSelectionOrigin(view, "pointer");
};
handlers.contextmenu = (view) => forceDOMFlush(view);
function inOrNearComposition(view, event) {
if (view.composing)
return true;
if (safari && Math.abs(event.timeStamp - view.input.compositionEndedAt) < 500) {
view.input.compositionEndedAt = -2e8;
return true;
}
return false;
}
const timeoutComposition = android ? 5e3 : -1;
editHandlers.compositionstart = editHandlers.compositionupdate = (view) => {
if (!view.composing) {
view.domObserver.flush();
let { state } = view, $pos = state.selection.$from;
if (state.selection.empty && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some((m) => m.type.spec.inclusive === false))) {
view.markCursor = view.state.storedMarks || $pos.marks();
endComposition(view, true);
view.markCursor = null;
} else {
endComposition(view);
if (gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore.marks.length) {
let sel = view.domSelectionRange();
for (let node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0; ) {
let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1];
if (!before)
break;
if (before.nodeType == 3) {
view.domSelection().collapse(before, before.nodeValue.length);
break;
} else {
node = before;
offset = -1;
}
}
}
}
view.input.composing = true;
}
scheduleComposeEnd(view, timeoutComposition);
};
editHandlers.compositionend = (view, event) => {
if (view.composing) {
view.input.composing = false;
view.input.compositionEndedAt = event.timeStamp;
scheduleComposeEnd(view, 20);
}
};
function scheduleComposeEnd(view, delay) {
clearTimeout(view.input.composingTimeout);
if (delay > -1)
view.input.composingTimeout = setTimeout(() => endComposition(view), delay);
}
function clearComposition(view) {
if (view.composing) {
view.input.composing = false;
view.input.compositionEndedAt = timestampFromCustomEvent();
}
while (view.input.compositionNodes.length > 0)
view.input.compositionNodes.pop().markParentsDirty();
}
function timestampFromCustomEvent() {
let event = document.createEvent("Event");
event.initEvent("event", true, true);
return event.timeStamp;
}
function endComposition(view, forceUpdate = false) {
if (android && view.domObserver.flushingSoon >= 0)
return;
view.domObserver.forceFlush();
clearComposition(view);
if (forceUpdate || view.docView && view.docView.dirty) {
let sel = selectionFromDOM(view);
if (sel && !sel.eq(view.state.selection))
view.dispatch(view.state.tr.setSelection(sel));
else
view.updateState(view.state);
return true;
}
return false;
}
function captureCopy(view, dom) {
if (!view.dom.parentNode)
return;
let wrap2 = view.dom.parentNode.appendChild(document.createElement("div"));
wrap2.appendChild(dom);
wrap2.style.cssText = "position: fixed; left: -10000px; top: 10px";
let sel = getSelection(), range = document.createRange();
range.selectNodeContents(dom);
view.dom.blur();
sel.removeAllRanges();
sel.addRange(range);
setTimeout(() => {
if (wrap2.parentNode)
wrap2.parentNode.removeChild(wrap2);
view.focus();
}, 50);
}
const brokenClipboardAPI = ie && ie_version < 15 || ios && webkit_version < 604;
handlers.copy = editHandlers.cut = (view, _event) => {
let event = _event;
let sel = view.state.selection, cut = event.type == "cut";
if (sel.empty)
return;
let data = brokenClipboardAPI ? null : event.clipboardData;
let slice2 = sel.content(), { dom, text: text2 } = serializeForClipboard(view, slice2);
if (data) {
event.preventDefault();
data.clearData();
data.setData("text/html", dom.innerHTML);
data.setData("text/plain", text2);
} else {
captureCopy(view, dom);
}
if (cut)
view.dispatch(view.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent", "cut"));
};
function sliceSingleNode(slice2) {
return slice2.openStart == 0 && slice2.openEnd == 0 && slice2.content.childCount == 1 ? slice2.content.firstChild : null;
}
function capturePaste(view, event) {
if (!view.dom.parentNode)
return;
let plainText = view.input.shiftKey || view.state.selection.$from.parent.type.spec.code;
let target = view.dom.parentNode.appendChild(document.createElement(plainText ? "textarea" : "div"));
if (!plainText)
target.contentEditable = "true";
target.style.cssText = "position: fixed; left: -10000px; top: 10px";
target.focus();
setTimeout(() => {
view.focus();
if (target.parentNode)
target.parentNode.removeChild(target);
if (plainText)
doPaste(view, target.value, null, view.input.shiftKey, event);
else
doPaste(view, target.textContent, target.innerHTML, view.input.shiftKey, event);
}, 50);
}
function doPaste(view, text2, html, preferPlain, event) {
let slice2 = parseFromClipboard(view, text2, html, preferPlain, view.state.selection.$from);
if (view.someProp("handlePaste", (f) => f(view, event, slice2 || Slice.empty)))
return true;
if (!slice2)
return false;
let singleNode = sliceSingleNode(slice2);
let tr = singleNode ? view.state.tr.replaceSelectionWith(singleNode, view.input.shiftKey) : view.state.tr.replaceSelection(slice2);
view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
return true;
}
editHandlers.paste = (view, _event) => {
let event = _event;
if (view.composing && !android)
return;
let data = brokenClipboardAPI ? null : event.clipboardData;
if (data && doPaste(view, data.getData("text/plain"), data.getData("text/html"), view.input.shiftKey, event))
event.preventDefault();
else
capturePaste(view, event);
};
class Dragging {
constructor(slice2, move) {
this.slice = slice2;
this.move = move;
}
}
const dragCopyModifier = mac$1 ? "altKey" : "ctrlKey";
handlers.dragstart = (view, _event) => {
let event = _event;
let mouseDown = view.input.mouseDown;
if (mouseDown)
mouseDown.done();
if (!event.dataTransfer)
return;
let sel = view.state.selection;
let pos = sel.empty ? null : view.posAtCoords(eventCoords(event));
if (pos && pos.pos >= sel.from && pos.pos <= (sel instanceof NodeSelection ? sel.to - 1 : sel.to))
;
else if (mouseDown && mouseDown.mightDrag) {
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)));
} else if (event.target && event.target.nodeType == 1) {
let desc = view.docView.nearestDesc(event.target, true);
if (desc && desc.node.type.spec.draggable && desc != view.docView)
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore)));
}
let slice2 = view.state.selection.content(), { dom, text: text2 } = serializeForClipboard(view, slice2);
event.dataTransfer.clearData();
event.dataTransfer.setData(brokenClipboardAPI ? "Text" : "text/html", dom.innerHTML);
event.dataTransfer.effectAllowed = "copyMove";
if (!brokenClipboardAPI)
event.dataTransfer.setData("text/plain", text2);
view.dragging = new Dragging(slice2, !event[dragCopyModifier]);
};
handlers.dragend = (view) => {
let dragging = view.dragging;
window.setTimeout(() => {
if (view.dragging == dragging)
view.dragging = null;
}, 50);
};
editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault();
editHandlers.drop = (view, _event) => {
let event = _event;
let dragging = view.dragging;
view.dragging = null;
if (!event.dataTransfer)
return;
let eventPos = view.posAtCoords(eventCoords(event));
if (!eventPos)
return;
let $mouse = view.state.doc.resolve(eventPos.pos);
let slice2 = dragging && dragging.slice;
if (slice2) {
view.someProp("transformPasted", (f) => {
slice2 = f(slice2, view);
});
} else {
slice2 = parseFromClipboard(view, event.dataTransfer.getData(brokenClipboardAPI ? "Text" : "text/plain"), brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse);
}
let move = !!(dragging && !event[dragCopyModifier]);
if (view.someProp("handleDrop", (f) => f(view, event, slice2 || Slice.empty, move))) {
event.preventDefault();
return;
}
if (!slice2)
return;
event.preventDefault();
let insertPos = slice2 ? dropPoint(view.state.doc, $mouse.pos, slice2) : $mouse.pos;
if (insertPos == null)
insertPos = $mouse.pos;
let tr = view.state.tr;
if (move)
tr.deleteSelection();
let pos = tr.mapping.map(insertPos);
let isNode = slice2.openStart == 0 && slice2.openEnd == 0 && slice2.content.childCount == 1;
let beforeInsert = tr.doc;
if (isNode)
tr.replaceRangeWith(pos, pos, slice2.content.firstChild);
else
tr.replaceRange(pos, pos, slice2);
if (tr.doc.eq(beforeInsert))
return;
let $pos = tr.doc.resolve(pos);
if (isNode && NodeSelection.isSelectable(slice2.content.firstChild) && $pos.nodeAfter && $pos.nodeAfter.sameMarkup(slice2.content.firstChild)) {
tr.setSelection(new NodeSelection($pos));
} else {
let end = tr.mapping.map(insertPos);
tr.mapping.maps[tr.mapping.maps.length - 1].forEach((_from, _to, _newFrom, newTo) => end = newTo);
tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)));
}
view.focus();
view.dispatch(tr.setMeta("uiEvent", "drop"));
};
handlers.focus = (view) => {
view.input.lastFocus = Date.now();
if (!view.focused) {
view.domObserver.stop();
view.dom.classList.add("ProseMirror-focused");
view.domObserver.start();
view.focused = true;
setTimeout(() => {
if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelectionRange()))
selectionToDOM(view);
}, 20);
}
};
handlers.blur = (view, _event) => {
let event = _event;
if (view.focused) {
view.domObserver.stop();
view.dom.classList.remove("ProseMirror-focused");
view.domObserver.start();
if (event.relatedTarget && view.dom.contains(event.relatedTarget))
view.domObserver.currentSelection.clear();
view.focused = false;
}
};
handlers.beforeinput = (view, _event) => {
let event = _event;
if (chrome && android && event.inputType == "deleteContentBackward") {
view.domObserver.flushSoon();
let { domChangeCount } = view.input;
setTimeout(() => {
if (view.input.domChangeCount != domChangeCount)
return;
view.dom.blur();
view.focus();
if (view.someProp("handleKeyDown", (f) => f(view, keyEvent(8, "Backspace"))))
return;
let { $cursor } = view.state.selection;
if ($cursor && $cursor.pos > 0)
view.dispatch(view.state.tr.delete($cursor.pos - 1, $cursor.pos).scrollIntoView());
}, 50);
}
};
for (let prop in editHandlers)
handlers[prop] = editHandlers[prop];
function compareObjs(a, b) {
if (a == b)
return true;
for (let p in a)
if (a[p] !== b[p])
return false;
for (let p in b)
if (!(p in a))
return false;
return true;
}
class WidgetType {
constructor(toDOM, spec) {
this.toDOM = toDOM;
this.spec = spec || noSpec;
this.side = this.spec.side || 0;
}
map(mapping, span, offset, oldOffset) {
let { pos, deleted } = mapping.mapResult(span.from + oldOffset, this.side < 0 ? -1 : 1);
return deleted ? null : new Decoration(pos - offset, pos - offset, this);
}
valid() {
return true;
}
eq(other) {
return this == other || other instanceof WidgetType && (this.spec.key && this.spec.key == other.spec.key || this.toDOM == other.toDOM && compareObjs(this.spec, other.spec));
}
destroy(node) {
if (this.spec.destroy)
this.spec.destroy(node);
}
}
class InlineType {
constructor(attrs, spec) {
this.attrs = attrs;
this.spec = spec || noSpec;
}
map(mapping, span, offset, oldOffset) {
let from2 = mapping.map(span.from + oldOffset, this.spec.inclusiveStart ? -1 : 1) - offset;
let to = mapping.map(span.to + oldOffset, this.spec.inclusiveEnd ? 1 : -1) - offset;
return from2 >= to ? null : new Decoration(from2, to, this);
}
valid(_, span) {
return span.from < span.to;
}
eq(other) {
return this == other || other instanceof InlineType && compareObjs(this.attrs, other.attrs) && compareObjs(this.spec, other.spec);
}
static is(span) {
return span.type instanceof InlineType;
}
destroy() {
}
}
class NodeType2 {
constructor(attrs, spec) {
this.attrs = attrs;
this.spec = spec || noSpec;
}
map(mapping, span, offset, oldOffset) {
let from2 = mapping.mapResult(span.from + oldOffset, 1);
if (from2.deleted)
return null;
let to = mapping.mapResult(span.to + oldOffset, -1);
if (to.deleted || to.pos <= from2.pos)
return null;
return new Decoration(from2.pos - offset, to.pos - offset, this);
}
valid(node, span) {
let { index, offset } = node.content.findIndex(span.from), child;
return offset == span.from && !(child = node.child(index)).isText && offset + child.nodeSize == span.to;
}
eq(other) {
return this == other || other instanceof NodeType2 && compareObjs(this.attrs, other.attrs) && compareObjs(this.spec, other.spec);
}
destroy() {
}
}
class Decoration {
/**
@internal
*/
constructor(from2, to, type) {
this.from = from2;
this.to = to;
this.type = type;
}
/**
@internal
*/
copy(from2, to) {
return new Decoration(from2, to, this.type);
}
/**
@internal
*/
eq(other, offset = 0) {
return this.type.eq(other.type) && this.from + offset == other.from && this.to + offset == other.to;
}
/**
@internal
*/
map(mapping, offset, oldOffset) {
return this.type.map(mapping, this, offset, oldOffset);
}
/**
Creates a widget decoration, which is a DOM node that's shown in
the document at the given position. It is recommended that you
delay rendering the widget by passing a function that will be
called when the widget is actually drawn in a view, but you can
also directly pass a DOM node. `getPos` can be used to find the
widget's current document position.
*/
static widget(pos, toDOM, spec) {
return new Decoration(pos, pos, new WidgetType(toDOM, spec));
}
/**
Creates an inline decoration, which adds the given attributes to
each inline node between `from` and `to`.
*/
static inline(from2, to, attrs, spec) {
return new Decoration(from2, to, new InlineType(attrs, spec));
}
/**
Creates a node decoration. `from` and `to` should point precisely
before and after a node in the document. That node, and only that
node, will receive the given attributes.
*/
static node(from2, to, attrs, spec) {
return new Decoration(from2, to, new NodeType2(attrs, spec));
}
/**
The spec provided when creating this decoration. Can be useful
if you've stored extra information in that object.
*/
get spec() {
return this.type.spec;
}
/**
@internal
*/
get inline() {
return this.type instanceof InlineType;
}
}
const none = [], noSpec = {};
class DecorationSet {
/**
@internal
*/
constructor(local, children) {
this.local = local.length ? local : none;
this.children = children.length ? children : none;
}
/**
Create a set of decorations, using the structure of the given
document.
*/
static create(doc2, decorations) {
return decorations.length ? buildTree(decorations, doc2, 0, noSpec) : empty;
}
/**
Find all decorations in this set which touch the given range
(including decorations that start or end directly at the
boundaries) and match the given predicate on their spec. When
`start` and `end` are omitted, all decorations in the set are
considered. When `predicate` isn't given, all decorations are
assumed to match.
*/
find(start, end, predicate) {
let result = [];
this.findInner(start == null ? 0 : start, end == null ? 1e9 : end, result, 0, predicate);
return result;
}
findInner(start, end, result, offset, predicate) {
for (let i = 0; i < this.local.length; i++) {
let span = this.local[i];
if (span.from <= end && span.to >= start && (!predicate || predicate(span.spec)))
result.push(span.copy(span.from + offset, span.to + offset));
}
for (let i = 0; i < this.children.length; i += 3) {
if (this.children[i] < end && this.children[i + 1] > start) {
let childOff = this.children[i] + 1;
this.children[i + 2].findInner(start - childOff, end - childOff, result, offset + childOff, predicate);
}
}
}
/**
Map the set of decorations in response to a change in the
document.
*/
map(mapping, doc2, options) {
if (this == empty || mapping.maps.length == 0)
return this;
return this.mapInner(mapping, doc2, 0, 0, options || noSpec);
}
/**
@internal
*/
mapInner(mapping, node, offset, oldOffset, options) {
let newLocal;
for (let i = 0; i < this.local.length; i++) {
let mapped = this.local[i].map(mapping, offset, oldOffset);
if (mapped && mapped.type.valid(node, mapped))
(newLocal || (newLocal = [])).push(mapped);
else if (options.onRemove)
options.onRemove(this.local[i].spec);
}
if (this.children.length)
return mapChildren(this.children, newLocal || [], mapping, node, offset, oldOffset, options);
else
return newLocal ? new DecorationSet(newLocal.sort(byPos), none) : empty;
}
/**
Add the given array of decorations to the ones in the set,
producing a new set. Needs access to the current document to
create the appropriate tree structure.
*/
add(doc2, decorations) {
if (!decorations.length)
return this;
if (this == empty)
return DecorationSet.create(doc2, decorations);
return this.addInner(doc2, decorations, 0);
}
addInner(doc2, decorations, offset) {
let children, childIndex = 0;
doc2.forEach((childNode, childOffset) => {
let baseOffset = childOffset + offset, found2;
if (!(found2 = takeSpansForNode(decorations, childNode, baseOffset)))
return;
if (!children)
children = this.children.slice();
while (childIndex < children.length && children[childIndex] < childOffset)
childIndex += 3;
if (children[childIndex] == childOffset)
children[childIndex + 2] = children[childIndex + 2].addInner(childNode, found2, baseOffset + 1);
else
children.splice(childIndex, 0, childOffset, childOffset + childNode.nodeSize, buildTree(found2, childNode, baseOffset + 1, noSpec));
childIndex += 3;
});
let local = moveSpans(childIndex ? withoutNulls(decorations) : decorations, -offset);
for (let i = 0; i < local.length; i++)
if (!local[i].type.valid(doc2, local[i]))
local.splice(i--, 1);
return new DecorationSet(local.length ? this.local.concat(local).sort(byPos) : this.local, children || this.children);
}
/**
Create a new set that contains the decorations in this set, minus
the ones in the given array.
*/
remove(decorations) {
if (decorations.length == 0 || this == empty)
return this;
return this.removeInner(decorations, 0);
}
removeInner(decorations, offset) {
let children = this.children, local = this.local;
for (let i = 0; i < children.length; i += 3) {
let found2;
let from2 = children[i] + offset, to = children[i + 1] + offset;
for (let j = 0, span; j < decorations.length; j++)
if (span = decorations[j]) {
if (span.from > from2 && span.to < to) {
decorations[j] = null;
(found2 || (found2 = [])).push(span);
}
}
if (!found2)
continue;
if (children == this.children)
children = this.children.slice();
let removed = children[i + 2].removeInner(found2, from2 + 1);
if (removed != empty) {
children[i + 2] = removed;
} else {
children.splice(i, 3);
i -= 3;
}
}
if (local.length) {
for (let i = 0, span; i < decorations.length; i++)
if (span = decorations[i]) {
for (let j = 0; j < local.length; j++)
if (local[j].eq(span, offset)) {
if (local == this.local)
local = this.local.slice();
local.splice(j--, 1);
}
}
}
if (children == this.children && local == this.local)
return this;
return local.length || children.length ? new DecorationSet(local, children) : empty;
}
/**
@internal
*/
forChild(offset, node) {
if (this == empty)
return this;
if (node.isLeaf)
return DecorationSet.empty;
let child, local;
for (let i = 0; i < this.children.length; i += 3)
if (this.children[i] >= offset) {
if (this.children[i] == offset)
child = this.children[i + 2];
break;
}
let start = offset + 1, end = start + node.content.size;
for (let i = 0; i < this.local.length; i++) {
let dec = this.local[i];
if (dec.from < end && dec.to > start && dec.type instanceof InlineType) {
let from2 = Math.max(start, dec.from) - start, to = Math.min(end, dec.to) - start;
if (from2 < to)
(local || (local = [])).push(dec.copy(from2, to));
}
}
if (local) {
let localSet = new DecorationSet(local.sort(byPos), none);
return child ? new DecorationGroup([localSet, child]) : localSet;
}
return child || empty;
}
/**
@internal
*/
eq(other) {
if (this == other)
return true;
if (!(other instanceof DecorationSet) || this.local.length != other.local.length || this.children.length != other.children.length)
return false;
for (let i = 0; i < this.local.length; i++)
if (!this.local[i].eq(other.local[i]))
return false;
for (let i = 0; i < this.children.length; i += 3)
if (this.children[i] != other.children[i] || this.children[i + 1] != other.children[i + 1] || !this.children[i + 2].eq(other.children[i + 2]))
return false;
return true;
}
/**
@internal
*/
locals(node) {
return removeOverlap(this.localsInner(node));
}
/**
@internal
*/
localsInner(node) {
if (this == empty)
return none;
if (node.inlineContent || !this.local.some(InlineType.is))
return this.local;
let result = [];
for (let i = 0; i < this.local.length; i++) {
if (!(this.local[i].type instanceof InlineType))
result.push(this.local[i]);
}
return result;
}
}
DecorationSet.empty = new DecorationSet([], []);
DecorationSet.removeOverlap = removeOverlap;
const empty = DecorationSet.empty;
class DecorationGroup {
constructor(members) {
this.members = members;
}
map(mapping, doc2) {
const mappedDecos = this.members.map((member) => member.map(mapping, doc2, noSpec));
return DecorationGroup.from(mappedDecos);
}
forChild(offset, child) {
if (child.isLeaf)
return DecorationSet.empty;
let found2 = [];
for (let i = 0; i < this.members.length; i++) {
let result = this.members[i].forChild(offset, child);
if (result == empty)
continue;
if (result instanceof DecorationGroup)
found2 = found2.concat(result.members);
else
found2.push(result);
}
return DecorationGroup.from(found2);
}
eq(other) {
if (!(other instanceof DecorationGroup) || other.members.length != this.members.length)
return false;
for (let i = 0; i < this.members.length; i++)
if (!this.members[i].eq(other.members[i]))
return false;
return true;
}
locals(node) {
let result, sorted = true;
for (let i = 0; i < this.members.length; i++) {
let locals = this.members[i].localsInner(node);
if (!locals.length)
continue;
if (!result) {
result = locals;
} else {
if (sorted) {
result = result.slice();
sorted = false;
}
for (let j = 0; j < locals.length; j++)
result.push(locals[j]);
}
}
return result ? removeOverlap(sorted ? result : result.sort(byPos)) : none;
}
// Create a group for the given array of decoration sets, or return
// a single set when possible.
static from(members) {
switch (members.length) {
case 0:
return empty;
case 1:
return members[0];
default:
return new DecorationGroup(members.every((m) => m instanceof DecorationSet) ? members : members.reduce((r, m) => r.concat(m instanceof DecorationSet ? m : m.members), []));
}
}
}
function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, options) {
let children = oldChildren.slice();
for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
let moved = 0;
mapping.maps[i].forEach((oldStart, oldEnd, newStart, newEnd) => {
let dSize = newEnd - newStart - (oldEnd - oldStart);
for (let i2 = 0; i2 < children.length; i2 += 3) {
let end = children[i2 + 1];
if (end < 0 || oldStart > end + baseOffset - moved)
continue;
let start = children[i2] + baseOffset - moved;
if (oldEnd >= start) {
children[i2 + 1] = oldStart <= start ? -2 : -1;
} else if (newStart >= offset && dSize) {
children[i2] += dSize;
children[i2 + 1] += dSize;
}
}
moved += dSize;
});
baseOffset = mapping.maps[i].map(baseOffset, -1);
}
let mustRebuild = false;
for (let i = 0; i < children.length; i += 3)
if (children[i + 1] < 0) {
if (children[i + 1] == -2) {
mustRebuild = true;
children[i + 1] = -1;
continue;
}
let from2 = mapping.map(oldChildren[i] + oldOffset), fromLocal = from2 - offset;
if (fromLocal < 0 || fromLocal >= node.content.size) {
mustRebuild = true;
continue;
}
let to = mapping.map(oldChildren[i + 1] + oldOffset, -1), toLocal = to - offset;
let { index, offset: childOffset } = node.content.findIndex(fromLocal);
let childNode = node.maybeChild(index);
if (childNode && childOffset == fromLocal && childOffset + childNode.nodeSize == toLocal) {
let mapped = children[i + 2].mapInner(mapping, childNode, from2 + 1, oldChildren[i] + oldOffset + 1, options);
if (mapped != empty) {
children[i] = fromLocal;
children[i + 1] = toLocal;
children[i + 2] = mapped;
} else {
children[i + 1] = -2;
mustRebuild = true;
}
} else {
mustRebuild = true;
}
}
if (mustRebuild) {
let decorations = mapAndGatherRemainingDecorations(children, oldChildren, newLocal, mapping, offset, oldOffset, options);
let built = buildTree(decorations, node, 0, options);
newLocal = built.local;
for (let i = 0; i < children.length; i += 3)
if (children[i + 1] < 0) {
children.splice(i, 3);
i -= 3;
}
for (let i = 0, j = 0; i < built.children.length; i += 3) {
let from2 = built.children[i];
while (j < children.length && children[j] < from2)
j += 3;
children.splice(j, 0, built.children[i], built.children[i + 1], built.children[i + 2]);
}
}
return new DecorationSet(newLocal.sort(byPos), children);
}
function moveSpans(spans, offset) {
if (!offset || !spans.length)
return spans;
let result = [];
for (let i = 0; i < spans.length; i++) {
let span = spans[i];
result.push(new Decoration(span.from + offset, span.to + offset, span.type));
}
return result;
}
function mapAndGatherRemainingDecorations(children, oldChildren, decorations, mapping, offset, oldOffset, options) {
function gather(set, oldOffset2) {
for (let i = 0; i < set.local.length; i++) {
let mapped = set.local[i].map(mapping, offset, oldOffset2);
if (mapped)
decorations.push(mapped);
else if (options.onRemove)
options.onRemove(set.local[i].spec);
}
for (let i = 0; i < set.children.length; i += 3)
gather(set.children[i + 2], set.children[i] + oldOffset2 + 1);
}
for (let i = 0; i < children.length; i += 3)
if (children[i + 1] == -1)
gather(children[i + 2], oldChildren[i] + oldOffset + 1);
return decorations;
}
function takeSpansForNode(spans, node, offset) {
if (node.isLeaf)
return null;
let end = offset + node.nodeSize, found2 = null;
for (let i = 0, span; i < spans.length; i++) {
if ((span = spans[i]) && span.from > offset && span.to < end) {
(found2 || (found2 = [])).push(span);
spans[i] = null;
}
}
return found2;
}
function withoutNulls(array) {
let result = [];
for (let i = 0; i < array.length; i++)
if (array[i] != null)
result.push(array[i]);
return result;
}
function buildTree(spans, node, offset, options) {
let children = [], hasNulls = false;
node.forEach((childNode, localStart) => {
let found2 = takeSpansForNode(spans, childNode, localStart + offset);
if (found2) {
hasNulls = true;
let subtree = buildTree(found2, childNode, offset + localStart + 1, options);
if (subtree != empty)
children.push(localStart, localStart + childNode.nodeSize, subtree);
}
});
let locals = moveSpans(hasNulls ? withoutNulls(spans) : spans, -offset).sort(byPos);
for (let i = 0; i < locals.length; i++)
if (!locals[i].type.valid(node, locals[i])) {
if (options.onRemove)
options.onRemove(locals[i].spec);
locals.splice(i--, 1);
}
return locals.length || children.length ? new DecorationSet(locals, children) : empty;
}
function byPos(a, b) {
return a.from - b.from || a.to - b.to;
}
function removeOverlap(spans) {
let working = spans;
for (let i = 0; i < working.length - 1; i++) {
let span = working[i];
if (span.from != span.to)
for (let j = i + 1; j < working.length; j++) {
let next = working[j];
if (next.from == span.from) {
if (next.to != span.to) {
if (working == spans)
working = spans.slice();
working[j] = next.copy(next.from, span.to);
insertAhead(working, j + 1, next.copy(span.to, next.to));
}
continue;
} else {
if (next.from < span.to) {
if (working == spans)
working = spans.slice();
working[i] = span.copy(span.from, next.from);
insertAhead(working, j, span.copy(next.from, span.to));
}
break;
}
}
}
return working;
}
function insertAhead(array, i, deco) {
while (i < array.length && byPos(deco, array[i]) > 0)
i++;
array.splice(i, 0, deco);
}
function viewDecorations(view) {
let found2 = [];
view.someProp("decorations", (f) => {
let result = f(view.state);
if (result && result != empty)
found2.push(result);
});
if (view.cursorWrapper)
found2.push(DecorationSet.create(view.state.doc, [view.cursorWrapper.deco]));
return DecorationGroup.from(found2);
}
const observeOptions = {
childList: true,
characterData: true,
characterDataOldValue: true,
attributes: true,
attributeOldValue: true,
subtree: true
};
const useCharData = ie && ie_version <= 11;
class SelectionState {
constructor() {
this.anchorNode = null;
this.anchorOffset = 0;
this.focusNode = null;
this.focusOffset = 0;
}
set(sel) {
this.anchorNode = sel.anchorNode;
this.anchorOffset = sel.anchorOffset;
this.focusNode = sel.focusNode;
this.focusOffset = sel.focusOffset;
}
clear() {
this.anchorNode = this.focusNode = null;
}
eq(sel) {
return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset && sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset;
}
}
class DOMObserver {
constructor(view, handleDOMChange) {
this.view = view;
this.handleDOMChange = handleDOMChange;
this.queue = [];
this.flushingSoon = -1;
this.observer = null;
this.currentSelection = new SelectionState();
this.onCharData = null;
this.suppressingSelectionUpdates = false;
this.observer = window.MutationObserver && new window.MutationObserver((mutations) => {
for (let i = 0; i < mutations.length; i++)
this.queue.push(mutations[i]);
if (ie && ie_version <= 11 && mutations.some((m) => m.type == "childList" && m.removedNodes.length || m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
this.flushSoon();
else
this.flush();
});
if (useCharData) {
this.onCharData = (e) => {
this.queue.push({ target: e.target, type: "characterData", oldValue: e.prevValue });
this.flushSoon();
};
}
this.onSelectionChange = this.onSelectionChange.bind(this);
}
flushSoon() {
if (this.flushingSoon < 0)
this.flushingSoon = window.setTimeout(() => {
this.flushingSoon = -1;
this.flush();
}, 20);
}
forceFlush() {
if (this.flushingSoon > -1) {
window.clearTimeout(this.flushingSoon);
this.flushingSoon = -1;
this.flush();
}
}
start() {
if (this.observer) {
this.observer.takeRecords();
this.observer.observe(this.view.dom, observeOptions);
}
if (this.onCharData)
this.view.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
this.connectSelection();
}
stop() {
if (this.observer) {
let take = this.observer.takeRecords();
if (take.length) {
for (let i = 0; i < take.length; i++)
this.queue.push(take[i]);
window.setTimeout(() => this.flush(), 20);
}
this.observer.disconnect();
}
if (this.onCharData)
this.view.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
this.disconnectSelection();
}
connectSelection() {
this.view.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
}
disconnectSelection() {
this.view.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
}
suppressSelectionUpdates() {
this.suppressingSelectionUpdates = true;
setTimeout(() => this.suppressingSelectionUpdates = false, 50);
}
onSelectionChange() {
if (!hasFocusAndSelection(this.view))
return;
if (this.suppressingSelectionUpdates)
return selectionToDOM(this.view);
if (ie && ie_version <= 11 && !this.view.state.selection.empty) {
let sel = this.view.domSelectionRange();
if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
return this.flushSoon();
}
this.flush();
}
setCurSelection() {
this.currentSelection.set(this.view.domSelectionRange());
}
ignoreSelectionChange(sel) {
if (!sel.focusNode)
return true;
let ancestors = /* @__PURE__ */ new Set(), container;
for (let scan = sel.focusNode; scan; scan = parentNode(scan))
ancestors.add(scan);
for (let scan = sel.anchorNode; scan; scan = parentNode(scan))
if (ancestors.has(scan)) {
container = scan;
break;
}
let desc = container && this.view.docView.nearestDesc(container);
if (desc && desc.ignoreMutation({
type: "selection",
target: container.nodeType == 3 ? container.parentNode : container
})) {
this.setCurSelection();
return true;
}
}
flush() {
let { view } = this;
if (!view.docView || this.flushingSoon > -1)
return;
let mutations = this.observer ? this.observer.takeRecords() : [];
if (this.queue.length) {
mutations = this.queue.concat(mutations);
this.queue.length = 0;
}
let sel = view.domSelectionRange();
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
let from2 = -1, to = -1, typeOver = false, added = [];
if (view.editable) {
for (let i = 0; i < mutations.length; i++) {
let result = this.registerMutation(mutations[i], added);
if (result) {
from2 = from2 < 0 ? result.from : Math.min(result.from, from2);
to = to < 0 ? result.to : Math.max(result.to, to);
if (result.typeOver)
typeOver = true;
}
}
}
if (gecko && added.length > 1) {
let brs = added.filter((n) => n.nodeName == "BR");
if (brs.length == 2) {
let a = brs[0], b = brs[1];
if (a.parentNode && a.parentNode.parentNode == b.parentNode)
b.remove();
else
a.remove();
}
}
let readSel = null;
if (from2 < 0 && newSel && view.input.lastFocus > Date.now() - 200 && Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 && selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) && readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
view.input.lastFocus = 0;
selectionToDOM(view);
this.currentSelection.set(sel);
view.scrollToSelection();
} else if (from2 > -1 || newSel) {
if (from2 > -1) {
view.docView.markDirty(from2, to);
checkCSS(view);
}
this.handleDOMChange(from2, to, typeOver, added);
if (view.docView && view.docView.dirty)
view.updateState(view.state);
else if (!this.currentSelection.eq(sel))
selectionToDOM(view);
this.currentSelection.set(sel);
}
}
registerMutation(mut, added) {
if (added.indexOf(mut.target) > -1)
return null;
let desc = this.view.docView.nearestDesc(mut.target);
if (mut.type == "attributes" && (desc == this.view.docView || mut.attributeName == "contenteditable" || // Firefox sometimes fires spurious events for null/empty styles
mut.attributeName == "style" && !mut.oldValue && !mut.target.getAttribute("style")))
return null;
if (!desc || desc.ignoreMutation(mut))
return null;
if (mut.type == "childList") {
for (let i = 0; i < mut.addedNodes.length; i++)
added.push(mut.addedNodes[i]);
if (desc.contentDOM && desc.contentDOM != desc.dom && !desc.contentDOM.contains(mut.target))
return { from: desc.posBefore, to: desc.posAfter };
let prev = mut.previousSibling, next = mut.nextSibling;
if (ie && ie_version <= 11 && mut.addedNodes.length) {
for (let i = 0; i < mut.addedNodes.length; i++) {
let { previousSibling, nextSibling } = mut.addedNodes[i];
if (!previousSibling || Array.prototype.indexOf.call(mut.addedNodes, previousSibling) < 0)
prev = previousSibling;
if (!nextSibling || Array.prototype.indexOf.call(mut.addedNodes, nextSibling) < 0)
next = nextSibling;
}
}
let fromOffset = prev && prev.parentNode == mut.target ? domIndex(prev) + 1 : 0;
let from2 = desc.localPosFromDOM(mut.target, fromOffset, -1);
let toOffset = next && next.parentNode == mut.target ? domIndex(next) : mut.target.childNodes.length;
let to = desc.localPosFromDOM(mut.target, toOffset, 1);
return { from: from2, to };
} else if (mut.type == "attributes") {
return { from: desc.posAtStart - desc.border, to: desc.posAtEnd + desc.border };
} else {
return {
from: desc.posAtStart,
to: desc.posAtEnd,
// An event was generated for a text change that didn't change
// any text. Mark the dom change to fall back to assuming the
// selection was typed over with an identical value if it can't
// find another change.
typeOver: mut.target.nodeValue == mut.oldValue
};
}
}
}
let cssChecked = /* @__PURE__ */ new WeakMap();
let cssCheckWarned = false;
function checkCSS(view) {
if (cssChecked.has(view))
return;
cssChecked.set(view, null);
if (["normal", "nowrap", "pre-line"].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
view.requiresGeckoHackNode = gecko;
if (cssCheckWarned)
return;
console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.");
cssCheckWarned = true;
}
}
function safariShadowSelectionRange(view) {
let found2;
function read(event) {
event.preventDefault();
event.stopImmediatePropagation();
found2 = event.getTargetRanges()[0];
}
view.dom.addEventListener("beforeinput", read, true);
document.execCommand("indent");
view.dom.removeEventListener("beforeinput", read, true);
let anchorNode = found2.startContainer, anchorOffset = found2.startOffset;
let focusNode = found2.endContainer, focusOffset = found2.endOffset;
let currentAnchor = view.domAtPos(view.state.selection.anchor);
if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
[anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
return { anchorNode, anchorOffset, focusNode, focusOffset };
}
function parseBetween(view, from_, to_) {
let { node: parent, fromOffset, toOffset, from: from2, to } = view.docView.parseRange(from_, to_);
let domSel = view.domSelectionRange();
let find;
let anchor = domSel.anchorNode;
if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
find = [{ node: anchor, offset: domSel.anchorOffset }];
if (!selectionCollapsed(domSel))
find.push({ node: domSel.focusNode, offset: domSel.focusOffset });
}
if (chrome && view.input.lastKeyCode === 8) {
for (let off = toOffset; off > fromOffset; off--) {
let node = parent.childNodes[off - 1], desc = node.pmViewDesc;
if (node.nodeName == "BR" && !desc) {
toOffset = off;
break;
}
if (!desc || desc.size)
break;
}
}
let startDoc = view.state.doc;
let parser = view.someProp("domParser") || DOMParser.fromSchema(view.state.schema);
let $from = startDoc.resolve(from2);
let sel = null, doc2 = parser.parse(parent, {
topNode: $from.parent,
topMatch: $from.parent.contentMatchAt($from.index()),
topOpen: true,
from: fromOffset,
to: toOffset,
preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
findPositions: find,
ruleFromNode,
context: $from
});
if (find && find[0].pos != null) {
let anchor2 = find[0].pos, head = find[1] && find[1].pos;
if (head == null)
head = anchor2;
sel = { anchor: anchor2 + from2, head: head + from2 };
}
return { doc: doc2, sel, from: from2, to };
}
function ruleFromNode(dom) {
let desc = dom.pmViewDesc;
if (desc) {
return desc.parseRule();
} else if (dom.nodeName == "BR" && dom.parentNode) {
if (safari && /^(ul|ol)$/i.test(dom.parentNode.nodeName)) {
let skip = document.createElement("div");
skip.appendChild(document.createElement("li"));
return { skip };
} else if (dom.parentNode.lastChild == dom || safari && /^(tr|table)$/i.test(dom.parentNode.nodeName)) {
return { ignore: true };
}
} else if (dom.nodeName == "IMG" && dom.getAttribute("mark-placeholder")) {
return { ignore: true };
}
return null;
}
function readDOMChange(view, from2, to, typeOver, addedNodes) {
if (from2 < 0) {
let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null;
let newSel = selectionFromDOM(view, origin);
if (newSel && !view.state.selection.eq(newSel)) {
if (chrome && android && view.input.lastKeyCode === 13 && Date.now() - 100 < view.input.lastKeyCodeTime && view.someProp("handleKeyDown", (f) => f(view, keyEvent(13, "Enter"))))
return;
let tr2 = view.state.tr.setSelection(newSel);
if (origin == "pointer")
tr2.setMeta("pointer", true);
else if (origin == "key")
tr2.scrollIntoView();
view.dispatch(tr2);
}
return;
}
let $before = view.state.doc.resolve(from2);
let shared = $before.sharedDepth(to);
from2 = $before.before(shared + 1);
to = view.state.doc.resolve(to).after(shared + 1);
let sel = view.state.selection;
let parse = parseBetween(view, from2, to);
let doc2 = view.state.doc, compare = doc2.slice(parse.from, parse.to);
let preferredPos, preferredSide;
if (view.input.lastKeyCode === 8 && Date.now() - 100 < view.input.lastKeyCodeTime) {
preferredPos = view.state.selection.to;
preferredSide = "end";
} else {
preferredPos = view.state.selection.from;
preferredSide = "start";
}
view.input.lastKeyCode = null;
let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide);
if ((ios && view.input.lastIOSEnter > Date.now() - 225 || android) && addedNodes.some((n) => n.nodeName == "DIV" || n.nodeName == "P" || n.nodeName == "LI") && (!change || change.endA >= change.endB) && view.someProp("handleKeyDown", (f) => f(view, keyEvent(13, "Enter")))) {
view.input.lastIOSEnter = 0;
return;
}
if (!change) {
if (typeOver && sel instanceof TextSelection && !sel.empty && sel.$head.sameParent(sel.$anchor) && !view.composing && !(parse.sel && parse.sel.anchor != parse.sel.head)) {
change = { start: sel.from, endA: sel.to, endB: sel.to };
} else {
if (parse.sel) {
let sel2 = resolveSelection(view, view.state.doc, parse.sel);
if (sel2 && !sel2.eq(view.state.selection))
view.dispatch(view.state.tr.setSelection(sel2));
}
return;
}
}
if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from && parse.sel.head == parse.sel.anchor) {
let size = change.endB - change.start;
parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
}
view.input.domChangeCount++;
if (view.state.selection.from < view.state.selection.to && change.start == change.endB && view.state.selection instanceof TextSelection) {
if (change.start > view.state.selection.from && change.start <= view.state.selection.from + 2 && view.state.selection.from >= parse.from) {
change.start = view.state.selection.from;
} else if (change.endA < view.state.selection.to && change.endA >= view.state.selection.to - 2 && view.state.selection.to <= parse.to) {
change.endB += view.state.selection.to - change.endA;
change.endA = view.state.selection.to;
}
}
if (ie && ie_version <= 11 && change.endB == change.start + 1 && change.endA == change.start && change.start > parse.from && parse.doc.textBetween(change.start - parse.from - 1, change.start - parse.from + 1) == "  ") {
change.start--;
change.endA--;
change.endB--;
}
let $from = parse.doc.resolveNoCache(change.start - parse.from);
let $to = parse.doc.resolveNoCache(change.endB - parse.from);
let $fromA = doc2.resolve(change.start);
let inlineChange = $from.sameParent($to) && $from.parent.inlineContent && $fromA.end() >= change.endA;
let nextSel;
if ((ios && view.input.lastIOSEnter > Date.now() - 225 && (!inlineChange || addedNodes.some((n) => n.nodeName == "DIV" || n.nodeName == "P")) || !inlineChange && $from.pos < parse.doc.content.size && (nextSel = Selection.findFrom(parse.doc.resolve($from.pos + 1), 1, true)) && nextSel.head == $to.pos) && view.someProp("handleKeyDown", (f) => f(view, keyEvent(13, "Enter")))) {
view.input.lastIOSEnter = 0;
return;
}
if (view.state.selection.anchor > change.start && looksLikeJoin(doc2, change.start, change.endA, $from, $to) && view.someProp("handleKeyDown", (f) => f(view, keyEvent(8, "Backspace")))) {
if (android && chrome)
view.domObserver.suppressSelectionUpdates();
return;
}
if (chrome && android && change.endB == change.start)
view.input.lastAndroidDelete = Date.now();
if (android && !inlineChange && $from.start() != $to.start() && $to.parentOffset == 0 && $from.depth == $to.depth && parse.sel && parse.sel.anchor == parse.sel.head && parse.sel.head == change.endA) {
change.endB -= 2;
$to = parse.doc.resolveNoCache(change.endB - parse.from);
setTimeout(() => {
view.someProp("handleKeyDown", function(f) {
return f(view, keyEvent(13, "Enter"));
});
}, 20);
}
let chFrom = change.start, chTo = change.endA;
let tr, storedMarks, markChange;
if (inlineChange) {
if ($from.pos == $to.pos) {
if (ie && ie_version <= 11 && $from.parentOffset == 0) {
view.domObserver.suppressSelectionUpdates();
setTimeout(() => selectionToDOM(view), 20);
}
tr = view.state.tr.delete(chFrom, chTo);
storedMarks = doc2.resolve(change.start).marksAcross(doc2.resolve(change.endA));
} else if (
// Adding or removing a mark
change.endA == change.endB && (markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset), $fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))
) {
tr = view.state.tr;
if (markChange.type == "add")
tr.addMark(chFrom, chTo, markChange.mark);
else
tr.removeMark(chFrom, chTo, markChange.mark);
} else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
let text2 = $from.parent.textBetween($from.parentOffset, $to.parentOffset);
if (view.someProp("handleTextInput", (f) => f(view, chFrom, chTo, text2)))
return;
tr = view.state.tr.insertText(text2, chFrom, chTo);
}
}
if (!tr)
tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
if (parse.sel) {
let sel2 = resolveSelection(view, tr.doc, parse.sel);
if (sel2 && !(chrome && android && view.composing && sel2.empty && (change.start != change.endB || view.input.lastAndroidDelete < Date.now() - 100) && (sel2.head == chFrom || sel2.head == tr.mapping.map(chTo) - 1) || ie && sel2.empty && sel2.head == chFrom))
tr.setSelection(sel2);
}
if (storedMarks)
tr.ensureMarks(storedMarks);
view.dispatch(tr.scrollIntoView());
}
function resolveSelection(view, doc2, parsedSel) {
if (Math.max(parsedSel.anchor, parsedSel.head) > doc2.content.size)
return null;
return selectionBetween(view, doc2.resolve(parsedSel.anchor), doc2.resolve(parsedSel.head));
}
function isMarkChange(cur, prev) {
let curMarks = cur.firstChild.marks, prevMarks = prev.firstChild.marks;
let added = curMarks, removed = prevMarks, type, mark, update;
for (let i = 0; i < prevMarks.length; i++)
added = prevMarks[i].removeFromSet(added);
for (let i = 0; i < curMarks.length; i++)
removed = curMarks[i].removeFromSet(removed);
if (added.length == 1 && removed.length == 0) {
mark = added[0];
type = "add";
update = (node) => node.mark(mark.addToSet(node.marks));
} else if (added.length == 0 && removed.length == 1) {
mark = removed[0];
type = "remove";
update = (node) => node.mark(mark.removeFromSet(node.marks));
} else {
return null;
}
let updated = [];
for (let i = 0; i < prev.childCount; i++)
updated.push(update(prev.child(i)));
if (Fragment.from(updated).eq(cur))
return { mark, type };
}
function looksLikeJoin(old, start, end, $newStart, $newEnd) {
if (!$newStart.parent.isTextblock || // The content must have shrunk
end - start <= $newEnd.pos - $newStart.pos || // newEnd must point directly at or after the end of the block that newStart points into
skipClosingAndOpening($newStart, true, false) < $newEnd.pos)
return false;
let $start = old.resolve(start);
if ($start.parentOffset < $start.parent.content.size || !$start.parent.isTextblock)
return false;
let $next = old.resolve(skipClosingAndOpening($start, true, true));
if (!$next.parent.isTextblock || $next.pos > end || skipClosingAndOpening($next, true, false) < end)
return false;
return $newStart.parent.content.cut($newStart.parentOffset).eq($next.parent.content);
}
function skipClosingAndOpening($pos, fromEnd, mayOpen) {
let depth = $pos.depth, end = fromEnd ? $pos.end() : $pos.pos;
while (depth > 0 && (fromEnd || $pos.indexAfter(depth) == $pos.node(depth).childCount)) {
depth--;
end++;
fromEnd = false;
}
if (mayOpen) {
let next = $pos.node(depth).maybeChild($pos.indexAfter(depth));
while (next && !next.isLeaf) {
next = next.firstChild;
end++;
}
}
return end;
}
function findDiff(a, b, pos, preferredPos, preferredSide) {
let start = a.findDiffStart(b, pos);
if (start == null)
return null;
let { a: endA, b: endB } = a.findDiffEnd(b, pos + a.size, pos + b.size);
if (preferredSide == "end") {
let adjust = Math.max(0, start - Math.min(endA, endB));
preferredPos -= endA + adjust - start;
}
if (endA < start && a.size < b.size) {
let move = preferredPos <= start && preferredPos >= endA ? start - preferredPos : 0;
start -= move;
endB = start + (endB - endA);
endA = start;
} else if (endB < start) {
let move = preferredPos <= start && preferredPos >= endB ? start - preferredPos : 0;
start -= move;
endA = start + (endA - endB);
endB = start;
}
return { start, endA, endB };
}
class EditorView {
/**
Create a view. `place` may be a DOM node that the editor should
be appended to, a function that will place it into the document,
or an object whose `mount` property holds the node to use as the
document container. If it is `null`, the editor will not be
added to the document.
*/
constructor(place, props) {
this._root = null;
this.focused = false;
this.trackWrites = null;
this.mounted = false;
this.markCursor = null;
this.cursorWrapper = null;
this.lastSelectedViewDesc = void 0;
this.input = new InputState();
this.prevDirectPlugins = [];
this.pluginViews = [];
this.requiresGeckoHackNode = false;
this.dragging = null;
this._props = props;
this.state = props.state;
this.directPlugins = props.plugins || [];
this.directPlugins.forEach(checkStateComponent);
this.dispatch = this.dispatch.bind(this);
this.dom = place && place.mount || document.createElement("div");
if (place) {
if (place.appendChild)
place.appendChild(this.dom);
else if (typeof place == "function")
place(this.dom);
else if (place.mount)
this.mounted = true;
}
this.editable = getEditable(this);
updateCursorWrapper(this);
this.nodeViews = buildNodeViews(this);
this.docView = docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.dom, this);
this.domObserver = new DOMObserver(this, (from2, to, typeOver, added) => readDOMChange(this, from2, to, typeOver, added));
this.domObserver.start();
initInput(this);
this.updatePluginViews();
}
/**
Holds `true` when a
is active.
*/
get composing() {
return this.input.composing;
}
/**
*/
get props() {
if (this._props.state != this.state) {
let prev = this._props;
this._props = {};
for (let name in prev)
this._props[name] = prev[name];
this._props.state = this.state;
}
return this._props;
}
/**
Update the view's props. Will immediately cause an update to
the DOM.
*/
update(props) {
if (props.handleDOMEvents != this._props.handleDOMEvents)
ensureListeners(this);
let prevProps = this._props;
this._props = props;
if (props.plugins) {
props.plugins.forEach(checkStateComponent);
this.directPlugins = props.plugins;
}
this.updateStateInner(props.state, prevProps);
}
/**
Update the view by updating existing props object with the object
given as argument. Equivalent to `view.update(Object.assign({},
view.props, props))`.
*/
setProps(props) {
let updated = {};
for (let name in this._props)
updated[name] = this._props[name];
updated.state = this.state;
for (let name in props)
updated[name] = props[name];
this.update(updated);
}
/**
Update the editor's `state` prop, without touching any of the
other props.
*/
updateState(state) {
this.updateStateInner(state, this._props);
}
updateStateInner(state, prevProps) {
let prev = this.state, redraw = false, updateSel = false;
if (state.storedMarks && this.composing) {
clearComposition(this);
updateSel = true;
}
this.state = state;
let pluginsChanged = prev.plugins != state.plugins || this._props.plugins != prevProps.plugins;
if (pluginsChanged || this._props.plugins != prevProps.plugins || this._props.nodeViews != prevProps.nodeViews) {
let nodeViews = buildNodeViews(this);
if (changedNodeViews(nodeViews, this.nodeViews)) {
this.nodeViews = nodeViews;
redraw = true;
}
}
if (pluginsChanged || prevProps.handleDOMEvents != this._props.handleDOMEvents) {
ensureListeners(this);
}
this.editable = getEditable(this);
updateCursorWrapper(this);
let innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this);
let scroll2 = prev.plugins != state.plugins && !prev.doc.eq(state.doc) ? "reset" : state.scrollToSelection > prev.scrollToSelection ? "to selection" : "preserve";
let updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco);
if (updateDoc || !state.selection.eq(prev.selection))
updateSel = true;
let oldScrollPos = scroll2 == "preserve" && updateSel && this.dom.style.overflowAnchor == null && storeScrollPos(this);
if (updateSel) {
this.domObserver.stop();
let forceSelUpdate = updateDoc && (ie || chrome) && !this.composing && !prev.selection.empty && !state.selection.empty && selectionContextChanged(prev.selection, state.selection);
if (updateDoc) {
let chromeKludge = chrome ? this.trackWrites = this.domSelectionRange().focusNode : null;
if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
this.docView.updateOuterDeco([]);
this.docView.destroy();
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
}
if (chromeKludge && !this.trackWrites)
forceSelUpdate = true;
}
if (forceSelUpdate || !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) && anchorInRightPlace(this))) {
selectionToDOM(this, forceSelUpdate);
} else {
syncNodeSelection(this, state.selection);
this.domObserver.setCurSelection();
}
this.domObserver.start();
}
this.updatePluginViews(prev);
if (scroll2 == "reset") {
this.dom.scrollTop = 0;
} else if (scroll2 == "to selection") {
this.scrollToSelection();
} else if (oldScrollPos) {
resetScrollPos(oldScrollPos);
}
}
/**
@internal
*/
scrollToSelection() {
let startDOM = this.domSelectionRange().focusNode;
if (this.someProp("handleScrollToSelection", (f) => f(this)))
;
else if (this.state.selection instanceof NodeSelection) {
let target = this.docView.domAfterPos(this.state.selection.from);
if (target.nodeType == 1)
scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
} else {
scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM);
}
}
destroyPluginViews() {
let view;
while (view = this.pluginViews.pop())
if (view.destroy)
view.destroy();
}
updatePluginViews(prevState) {
if (!prevState || prevState.plugins != this.state.plugins || this.directPlugins != this.prevDirectPlugins) {
this.prevDirectPlugins = this.directPlugins;
this.destroyPluginViews();
for (let i = 0; i < this.directPlugins.length; i++) {
let plugin = this.directPlugins[i];
if (plugin.spec.view)
this.pluginViews.push(plugin.spec.view(this));
}
for (let i = 0; i < this.state.plugins.length; i++) {
let plugin = this.state.plugins[i];
if (plugin.spec.view)
this.pluginViews.push(plugin.spec.view(this));
}
} else {
for (let i = 0; i < this.pluginViews.length; i++) {
let pluginView = this.pluginViews[i];
if (pluginView.update)
pluginView.update(this, prevState);
}
}
}
someProp(propName, f) {
let prop = this._props && this._props[propName], value;
if (prop != null && (value = f ? f(prop) : prop))
return value;
for (let i = 0; i < this.directPlugins.length; i++) {
let prop2 = this.directPlugins[i].props[propName];
if (prop2 != null && (value = f ? f(prop2) : prop2))
return value;
}
let plugins = this.state.plugins;
if (plugins)
for (let i = 0; i < plugins.length; i++) {
let prop2 = plugins[i].props[propName];
if (prop2 != null && (value = f ? f(prop2) : prop2))
return value;
}
}
/**
Query whether the view has focus.
*/
hasFocus() {
if (ie) {
let node = this.root.activeElement;
if (node == this.dom)
return true;
if (!node || !this.dom.contains(node))
return false;
while (node && this.dom != node && this.dom.contains(node)) {
if (node.contentEditable == "false")
return false;
node = node.parentElement;
}
return true;
}
return this.root.activeElement == this.dom;
}
/**
Focus the editor.
*/
focus() {
this.domObserver.stop();
if (this.editable)
focusPreventScroll(this.dom);
selectionToDOM(this);
this.domObserver.start();
}
/**
Get the document root in which the editor exists. This will
usually be the top-level `document`, but might be a [shadow
root if the editor is inside one.
*/
get root() {
let cached = this._root;
if (cached == null)
for (let search = this.dom.parentNode; search; search = search.parentNode) {
if (search.nodeType == 9 || search.nodeType == 11 && search.host) {
if (!search.getSelection)
Object.getPrototypeOf(search).getSelection = () => search.ownerDocument.getSelection();
return this._root = search;
}
}
return cached || document;
}
/**
Given a pair of viewport coordinates, return the document
position that corresponds to them. May return null if the given
coordinates aren't inside of the editor. When an object is
returned, its `pos` property is the position nearest to the
coordinates, and its `inside` property holds the position of the
inner node that the position falls inside of, or -1 if it is at
the top level, not in any node.
*/
posAtCoords(coords) {
return posAtCoords(this, coords);
}
/**
Returns the viewport rectangle at a given document position.
`left` and `right` will be the same number, as this returns a
flat cursor-ish rectangle. If the position is between two things
that aren't directly adjacent, `side` determines which element
is used. When < 0, the element before the position is used,
otherwise the element after.
*/
coordsAtPos(pos, side = 1) {
return coordsAtPos(this, pos, side);
}
/**
Find the DOM position that corresponds to the given document
position. When `side` is negative, find the position as close as
possible to the content before the position. When positive,
prefer positions close to the content after the position. When
zero, prefer as shallow a position as possible.
Note that you should **not** mutate the editor's internal DOM,
only inspect it (and even that is usually not necessary).
*/
domAtPos(pos, side = 0) {
return this.docView.domFromPos(pos, side);
}
/**
Find the DOM node that represents the document node after the
given position. May return `null` when the position doesn't point
in front of a node or if the node is inside an opaque node view.
This is intended to be able to call things like
`getBoundingClientRect` on that DOM node. Do **not** mutate the
editor DOM directly, or add styling this way, since that will be
immediately overriden by the editor as it redraws the node.
*/
nodeDOM(pos) {
let desc = this.docView.descAt(pos);
return desc ? desc.nodeDOM : null;
}
/**
Find the document position that corresponds to a given DOM
position. (Whenever possible, it is preferable to inspect the
document structure directly, rather than poking around in the
DOM, but sometimes—for example when interpreting an event
target—you don't have a choice.)
The `bias` parameter can be used to influence which side of a DOM
node to use when the position is inside a leaf node.
*/
posAtDOM(node, offset, bias = -1) {
let pos = this.docView.posFromDOM(node, offset, bias);
if (pos == null)
throw new RangeError("DOM position not inside the editor");
return pos;
}
/**
Find out whether the selection is at the end of a textblock when
moving in a given direction. When, for example, given `"left"`,
it will return true if moving left from the current cursor
position would leave that position's parent textblock. Will apply
to the view's current state by default, but it is possible to
pass a different state.
*/
endOfTextblock(dir, state) {
return endOfTextblock(this, state || this.state, dir);
}
/**
Run the editor's paste logic with the given HTML string. The
`event`, if given, will be passed to the
*/
pasteHTML(html, event) {
return doPaste(this, "", html, false, event || new ClipboardEvent("paste"));
}
/**
Run the editor's paste logic with the given plain-text input.
*/
pasteText(text2, event) {
return doPaste(this, text2, null, true, event || new ClipboardEvent("paste"));
}
/**
Removes the editor from the DOM and destroys all [node
*/
destroy() {
if (!this.docView)
return;
destroyInput(this);
this.destroyPluginViews();
if (this.mounted) {
this.docView.update(this.state.doc, [], viewDecorations(this), this);
this.dom.textContent = "";
} else if (this.dom.parentNode) {
this.dom.parentNode.removeChild(this.dom);
}
this.docView.destroy();
this.docView = null;
}
/**
This is true when the view has been
[destroyed](https://prosemirror.net/docs/ref/#view.EditorView.destroy) (and thus should not be
used anymore).
*/
get isDestroyed() {
return this.docView == null;
}
/**
Used for testing.
*/
dispatchEvent(event) {
return dispatchEvent(this, event);
}
/**
Dispatch a transaction. Will call
when given, and otherwise defaults to applying the transaction to
the current state and calling
This method is bound to the view instance, so that it can be
easily passed around.
*/
dispatch(tr) {
let dispatchTransaction = this._props.dispatchTransaction;
if (dispatchTransaction)
dispatchTransaction.call(this, tr);
else
this.updateState(this.state.apply(tr));
}
/**
@internal
*/
domSelectionRange() {
return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom ? safariShadowSelectionRange(this) : this.domSelection();
}
/**
@internal
*/
domSelection() {
return this.root.getSelection();
}
}
function computeDocDeco(view) {
let attrs = /* @__PURE__ */ Object.create(null);
attrs.class = "ProseMirror";
attrs.contenteditable = String(view.editable);
attrs.translate = "no";
view.someProp("attributes", (value) => {
if (typeof value == "function")
value = value(view.state);
if (value)
for (let attr in value) {
if (attr == "class")
attrs.class += " " + value[attr];
if (attr == "style") {
attrs.style = (attrs.style ? attrs.style + ";" : "") + value[attr];
} else if (!attrs[attr] && attr != "contenteditable" && attr != "nodeName")
attrs[attr] = String(value[attr]);
}
});
return [Decoration.node(0, view.state.doc.content.size, attrs)];
}
function updateCursorWrapper(view) {
if (view.markCursor) {
let dom = document.createElement("img");
dom.className = "ProseMirror-separator";
dom.setAttribute("mark-placeholder", "true");
dom.setAttribute("alt", "");
view.cursorWrapper = { dom, deco: Decoration.widget(view.state.selection.head, dom, { raw: true, marks: view.markCursor }) };
} else {
view.cursorWrapper = null;
}
}
function getEditable(view) {
return !view.someProp("editable", (value) => value(view.state) === false);
}
function selectionContextChanged(sel1, sel2) {
let depth = Math.min(sel1.$anchor.sharedDepth(sel1.head), sel2.$anchor.sharedDepth(sel2.head));
return sel1.$anchor.start(depth) != sel2.$anchor.start(depth);
}
function buildNodeViews(view) {
let result = /* @__PURE__ */ Object.create(null);
function add(obj) {
for (let prop in obj)
if (!Object.prototype.hasOwnProperty.call(result, prop))
result[prop] = obj[prop];
}
view.someProp("nodeViews", add);
view.someProp("markViews", add);
return result;
}
function changedNodeViews(a, b) {
let nA = 0, nB = 0;
for (let prop in a) {
if (a[prop] != b[prop])
return true;
nA++;
}
for (let _ in b)
nB++;
return nA != nB;
}
function checkStateComponent(plugin) {
if (plugin.spec.state || plugin.spec.filterTransaction || plugin.spec.appendTransaction)
throw new RangeError("Plugins passed directly to the view must not have a state component");
}
const mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : false;
function normalizeKeyName$1(name) {
let parts = name.split(/-(?!$)/), result = parts[parts.length - 1];
if (result == "Space")
result = " ";
let alt, ctrl, shift, meta;
for (let i = 0; i < parts.length - 1; i++) {
let mod = parts[i];
if (/^(cmd|meta|m)$/i.test(mod))
meta = true;
else if (/^a(lt)?$/i.test(mod))
alt = true;
else if (/^(c|ctrl|control)$/i.test(mod))
ctrl = true;
else if (/^s(hift)?$/i.test(mod))
shift = true;
else if (/^mod$/i.test(mod)) {
if (mac)
meta = true;
else
ctrl = true;
} else
throw new Error("Unrecognized modifier name: " + mod);
}
if (alt)
result = "Alt-" + result;
if (ctrl)
result = "Ctrl-" + result;
if (meta)
result = "Meta-" + result;
if (shift)
result = "Shift-" + result;
return result;
}
function normalize(map2) {
let copy2 = /* @__PURE__ */ Object.create(null);
for (let prop in map2)
copy2[normalizeKeyName$1(prop)] = map2[prop];
return copy2;
}
function modifiers(name, event, shift = true) {
if (event.altKey)
name = "Alt-" + name;
if (event.ctrlKey)
name = "Ctrl-" + name;
if (event.metaKey)
name = "Meta-" + name;
if (shift && event.shiftKey)
name = "Shift-" + name;
return name;
}
function keymap(bindings) {
return new Plugin({ props: { handleKeyDown: keydownHandler(bindings) } });
}
function keydownHandler(bindings) {
let map2 = normalize(bindings);
return function(view, event) {
let name = keyName(event), baseName, direct = map2[modifiers(name, event)];
if (direct && direct(view.state, view.dispatch, view))
return true;
if (name.length == 1 && name != " ") {
if (event.shiftKey) {
let noShift = map2[modifiers(name, event, false)];
if (noShift && noShift(view.state, view.dispatch, view))
return true;
}
if ((event.shiftKey || event.altKey || event.metaKey || name.charCodeAt(0) > 127) && (baseName = base[event.keyCode]) && baseName != name) {
let fromCode = map2[modifiers(baseName, event)];
if (fromCode && fromCode(view.state, view.dispatch, view))
return true;
}
}
return false;
};
}
const deleteSelection$1 = (state, dispatch) => {
if (state.selection.empty)
return false;
if (dispatch)
dispatch(state.tr.deleteSelection().scrollIntoView());
return true;
};
function atBlockStart(state, view) {
let { $cursor } = state.selection;
if (!$cursor || (view ? !view.endOfTextblock("backward", state) : $cursor.parentOffset > 0))
return null;
return $cursor;
}
const joinBackward$1 = (state, dispatch, view) => {
let $cursor = atBlockStart(state, view);
if (!$cursor)
return false;
let $cut = findCutBefore($cursor);
if (!$cut) {
let range = $cursor.blockRange(), target = range && liftTarget(range);
if (target == null)
return false;
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
}
let before = $cut.nodeBefore;
if (!before.type.spec.isolating && deleteBarrier(state, $cut, dispatch))
return true;
if ($cursor.parent.content.size == 0 && (textblockAt(before, "end") || NodeSelection.isSelectable(before))) {
let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty);
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
if (dispatch) {
let tr = state.tr.step(delStep);
tr.setSelection(textblockAt(before, "end") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1) : NodeSelection.create(tr.doc, $cut.pos - before.nodeSize));
dispatch(tr.scrollIntoView());
}
return true;
}
}
if (before.isAtom && $cut.depth == $cursor.depth - 1) {
if (dispatch)
dispatch(state.tr.delete($cut.pos - before.nodeSize, $cut.pos).scrollIntoView());
return true;
}
return false;
};
function textblockAt(node, side, only = false) {
for (let scan = node; scan; scan = side == "start" ? scan.firstChild : scan.lastChild) {
if (scan.isTextblock)
return true;
if (only && scan.childCount != 1)
return false;
}
return false;
}
const selectNodeBackward$1 = (state, dispatch, view) => {
let { $head, empty: empty2 } = state.selection, $cut = $head;
if (!empty2)
return false;
if ($head.parent.isTextblock) {
if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0)
return false;
$cut = findCutBefore($head);
}
let node = $cut && $cut.nodeBefore;
if (!node || !NodeSelection.isSelectable(node))
return false;
if (dispatch)
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos - node.nodeSize)).scrollIntoView());
return true;
};
function findCutBefore($pos) {
if (!$pos.parent.type.spec.isolating)
for (let i = $pos.depth - 1; i >= 0; i--) {
if ($pos.index(i) > 0)
return $pos.doc.resolve($pos.before(i + 1));
if ($pos.node(i).type.spec.isolating)
break;
}
return null;
}
function atBlockEnd(state, view) {
let { $cursor } = state.selection;
if (!$cursor || (view ? !view.endOfTextblock("forward", state) : $cursor.parentOffset < $cursor.parent.content.size))
return null;
return $cursor;
}
const joinForward$1 = (state, dispatch, view) => {
let $cursor = atBlockEnd(state, view);
if (!$cursor)
return false;
let $cut = findCutAfter($cursor);
if (!$cut)
return false;
let after = $cut.nodeAfter;
if (deleteBarrier(state, $cut, dispatch))
return true;
if ($cursor.parent.content.size == 0 && (textblockAt(after, "start") || NodeSelection.isSelectable(after))) {
let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty);
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
if (dispatch) {
let tr = state.tr.step(delStep);
tr.setSelection(textblockAt(after, "start") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1) : NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)));
dispatch(tr.scrollIntoView());
}
return true;
}
}
if (after.isAtom && $cut.depth == $cursor.depth - 1) {
if (dispatch)
dispatch(state.tr.delete($cut.pos, $cut.pos + after.nodeSize).scrollIntoView());
return true;
}
return false;
};
const selectNodeForward$1 = (state, dispatch, view) => {
let { $head, empty: empty2 } = state.selection, $cut = $head;
if (!empty2)
return false;
if ($head.parent.isTextblock) {
if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size)
return false;
$cut = findCutAfter($head);
}
let node = $cut && $cut.nodeAfter;
if (!node || !NodeSelection.isSelectable(node))
return false;
if (dispatch)
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos)).scrollIntoView());
return true;
};
function findCutAfter($pos) {
if (!$pos.parent.type.spec.isolating)
for (let i = $pos.depth - 1; i >= 0; i--) {
let parent = $pos.node(i);
if ($pos.index(i) + 1 < parent.childCount)
return $pos.doc.resolve($pos.after(i + 1));
if (parent.type.spec.isolating)
break;
}
return null;
}
const joinUp$1 = (state, dispatch) => {
let sel = state.selection, nodeSel = sel instanceof NodeSelection, point;
if (nodeSel) {
if (sel.node.isTextblock || !canJoin(state.doc, sel.from))
return false;
point = sel.from;
} else {
point = joinPoint(state.doc, sel.from, -1);
if (point == null)
return false;
}
if (dispatch) {
let tr = state.tr.join(point);
if (nodeSel)
tr.setSelection(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize));
dispatch(tr.scrollIntoView());
}
return true;
};
const joinDown$1 = (state, dispatch) => {
let sel = state.selection, point;
if (sel instanceof NodeSelection) {
if (sel.node.isTextblock || !canJoin(state.doc, sel.to))
return false;
point = sel.to;
} else {
point = joinPoint(state.doc, sel.to, 1);
if (point == null)
return false;
}
if (dispatch)
dispatch(state.tr.join(point).scrollIntoView());
return true;
};
const lift$1 = (state, dispatch) => {
let { $from, $to } = state.selection;
let range = $from.blockRange($to), target = range && liftTarget(range);
if (target == null)
return false;
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
};
const newlineInCode$1 = (state, dispatch) => {
let { $head, $anchor } = state.selection;
if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
return false;
if (dispatch)
dispatch(state.tr.insertText("\n").scrollIntoView());
return true;
};
function defaultBlockAt$1(match) {
for (let i = 0; i < match.edgeCount; i++) {
let { type } = match.edge(i);
if (type.isTextblock && !type.hasRequiredAttrs())
return type;
}
return null;
}
const exitCode$1 = (state, dispatch) => {
let { $head, $anchor } = state.selection;
if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
return false;
let above = $head.node(-1), after = $head.indexAfter(-1), type = defaultBlockAt$1(above.contentMatchAt(after));
if (!type || !above.canReplaceWith(after, after, type))
return false;
if (dispatch) {
let pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill());
tr.setSelection(Selection.near(tr.doc.resolve(pos), 1));
dispatch(tr.scrollIntoView());
}
return true;
};
const createParagraphNear$1 = (state, dispatch) => {
let sel = state.selection, { $from, $to } = sel;
if (sel instanceof AllSelection || $from.parent.inlineContent || $to.parent.inlineContent)
return false;
let type = defaultBlockAt$1($to.parent.contentMatchAt($to.indexAfter()));
if (!type || !type.isTextblock)
return false;
if (dispatch) {
let side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos;
let tr = state.tr.insert(side, type.createAndFill());
tr.setSelection(TextSelection.create(tr.doc, side + 1));
dispatch(tr.scrollIntoView());
}
return true;
};
const liftEmptyBlock$1 = (state, dispatch) => {
let { $cursor } = state.selection;
if (!$cursor || $cursor.parent.content.size)
return false;
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
let before = $cursor.before();
if (canSplit(state.doc, before)) {
if (dispatch)
dispatch(state.tr.split(before).scrollIntoView());
return true;
}
}
let range = $cursor.blockRange(), target = range && liftTarget(range);
if (target == null)
return false;
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
};
const selectParentNode$1 = (state, dispatch) => {
let { $from, to } = state.selection, pos;
let same = $from.sharedDepth(to);
if (same == 0)
return false;
pos = $from.before(same);
if (dispatch)
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos)));
return true;
};
function joinMaybeClear(state, $pos, dispatch) {
let before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index();
if (!before || !after || !before.type.compatibleContent(after.type))
return false;
if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
if (dispatch)
dispatch(state.tr.delete($pos.pos - before.nodeSize, $pos.pos).scrollIntoView());
return true;
}
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || canJoin(state.doc, $pos.pos)))
return false;
if (dispatch)
dispatch(state.tr.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount)).join($pos.pos).scrollIntoView());
return true;
}
function deleteBarrier(state, $cut, dispatch) {
let before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match;
if (before.type.spec.isolating || after.type.spec.isolating)
return false;
if (joinMaybeClear(state, $cut, dispatch))
return true;
let canDelAfter = $cut.parent.canReplace($cut.index(), $cut.index() + 1);
if (canDelAfter && (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && match.matchType(conn[0] || after.type).validEnd) {
if (dispatch) {
let end = $cut.pos + after.nodeSize, wrap2 = Fragment.empty;
for (let i = conn.length - 1; i >= 0; i--)
wrap2 = Fragment.from(conn[i].create(null, wrap2));
wrap2 = Fragment.from(before.copy(wrap2));
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap2, 1, 0), conn.length, true));
let joinAt = end + 2 * conn.length;
if (canJoin(tr.doc, joinAt))
tr.join(joinAt);
dispatch(tr.scrollIntoView());
}
return true;
}
let selAfter = Selection.findFrom($cut, 1);
let range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range);
if (target != null && target >= $cut.depth) {
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
}
if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
let at = before, wrap2 = [];
for (; ; ) {
wrap2.push(at);
if (at.isTextblock)
break;
at = at.lastChild;
}
let afterText = after, afterDepth = 1;
for (; !afterText.isTextblock; afterText = afterText.firstChild)
afterDepth++;
if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
if (dispatch) {
let end = Fragment.empty;
for (let i = wrap2.length - 1; i >= 0; i--)
end = Fragment.from(wrap2[i].copy(end));
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - wrap2.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new Slice(end, wrap2.length, 0), 0, true));
dispatch(tr.scrollIntoView());
}
return true;
}
}
return false;
}
function selectTextblockSide(side) {
return function(state, dispatch) {
let sel = state.selection, $pos = side < 0 ? sel.$from : sel.$to;
let depth = $pos.depth;
while ($pos.node(depth).isInline) {
if (!depth)
return false;
depth--;
}
if (!$pos.node(depth).isTextblock)
return false;
if (dispatch)
dispatch(state.tr.setSelection(TextSelection.create(state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))));
return true;
};
}
const selectTextblockStart$1 = selectTextblockSide(-1);
const selectTextblockEnd$1 = selectTextblockSide(1);
function wrapIn$1(nodeType, attrs = null) {
return function(state, dispatch) {
let { $from, $to } = state.selection;
let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs);
if (!wrapping)
return false;
if (dispatch)
dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
return true;
};
}
function setBlockType(nodeType, attrs = null) {
return function(state, dispatch) {
let applicable = false;
for (let i = 0; i < state.selection.ranges.length && !applicable; i++) {
let { $from: { pos: from2 }, $to: { pos: to } } = state.selection.ranges[i];
state.doc.nodesBetween(from2, to, (node, pos) => {
if (applicable)
return false;
if (!node.isTextblock || node.hasMarkup(nodeType, attrs))
return;
if (node.type == nodeType) {
applicable = true;
} else {
let $pos = state.doc.resolve(pos), index = $pos.index();
applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType);
}
});
}
if (!applicable)
return false;
if (dispatch) {
let tr = state.tr;
for (let i = 0; i < state.selection.ranges.length; i++) {
let { $from: { pos: from2 }, $to: { pos: to } } = state.selection.ranges[i];
tr.setBlockType(from2, to, nodeType, attrs);
}
dispatch(tr.scrollIntoView());
}
return true;
};
}
typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false;
function wrapInList$1(listType, attrs = null) {
return function(state, dispatch) {
let { $from, $to } = state.selection;
let range = $from.blockRange($to), doJoin = false, outerRange = range;
if (!range)
return false;
if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex == 0) {
if ($from.index(range.depth - 1) == 0)
return false;
let $insert = state.doc.resolve(range.start - 2);
outerRange = new NodeRange($insert, $insert, range.depth);
if (range.endIndex < range.parent.childCount)
range = new NodeRange($from, state.doc.resolve($to.end(range.depth)), range.depth);
doJoin = true;
}
let wrap2 = findWrapping(outerRange, listType, attrs, range);
if (!wrap2)
return false;
if (dispatch)
dispatch(doWrapInList(state.tr, range, wrap2, doJoin, listType).scrollIntoView());
return true;
};
}
function doWrapInList(tr, range, wrappers, joinBefore, listType) {
let content = Fragment.empty;
for (let i = wrappers.length - 1; i >= 0; i--)
content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
let found2 = 0;
for (let i = 0; i < wrappers.length; i++)
if (wrappers[i].type == listType)
found2 = i + 1;
let splitDepth = wrappers.length - found2;
let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent;
for (let i = range.startIndex, e = range.endIndex, first2 = true; i < e; i++, first2 = false) {
if (!first2 && canSplit(tr.doc, splitPos, splitDepth)) {
tr.split(splitPos, splitDepth);
splitPos += 2 * splitDepth;
}
splitPos += parent.child(i).nodeSize;
}
return tr;
}
function liftListItem$1(itemType) {
return function(state, dispatch) {
let { $from, $to } = state.selection;
let range = $from.blockRange($to, (node) => node.childCount > 0 && node.firstChild.type == itemType);
if (!range)
return false;
if (!dispatch)
return true;
if ($from.node(range.depth - 1).type == itemType)
return liftToOuterList(state, dispatch, itemType, range);
else
return liftOutOfList(state, dispatch, range);
};
}
function liftToOuterList(state, dispatch, itemType, range) {
let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth);
if (end < endOfList) {
tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList, new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
}
const target = liftTarget(range);
if (target == null)
return false;
tr.lift(range, target);
let after = tr.mapping.map(end, -1) - 1;
if (canJoin(tr.doc, after))
tr.join(after);
dispatch(tr.scrollIntoView());
return true;
}
function liftOutOfList(state, dispatch, range) {
let tr = state.tr, list = range.parent;
for (let pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
pos -= list.child(i).nodeSize;
tr.delete(pos - 1, pos + 1);
}
let $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
if (tr.mapping.map(range.end) != range.start + $start.nodeAfter.nodeSize)
return false;
let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
let parent = $start.node(-1), indexBefore = $start.index(-1);
if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, item.content.append(atEnd ? Fragment.empty : Fragment.from(list))))
return false;
let start = $start.pos, end = start + item.nodeSize;
tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))).append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))), atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
dispatch(tr.scrollIntoView());
return true;
}
function sinkListItem$1(itemType) {
return function(state, dispatch) {
let { $from, $to } = state.selection;
let range = $from.blockRange($to, (node) => node.childCount > 0 && node.firstChild.type == itemType);
if (!range)
return false;
let startIndex = range.startIndex;
if (startIndex == 0)
return false;
let parent = range.parent, nodeBefore = parent.child(startIndex - 1);
if (nodeBefore.type != itemType)
return false;
if (dispatch) {
let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
let inner = Fragment.from(nestedBefore ? itemType.create() : null);
let slice2 = new Slice(Fragment.from(itemType.create(null, Fragment.from(parent.type.create(null, inner)))), nestedBefore ? 3 : 1, 0);
let before = range.start, after = range.end;
dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice2, 1, true)).scrollIntoView());
}
return true;
};
}
function createChainableState(config) {
const { state, transaction } = config;
let { selection } = transaction;
let { doc: doc2 } = transaction;
let { storedMarks } = transaction;
return {
...state,
apply: state.apply.bind(state),
applyTransaction: state.applyTransaction.bind(state),
filterTransaction: state.filterTransaction,
plugins: state.plugins,
schema: state.schema,
reconfigure: state.reconfigure.bind(state),
toJSON: state.toJSON.bind(state),
get storedMarks() {
return storedMarks;
},
get selection() {
return selection;
},
get doc() {
return doc2;
},
get tr() {
selection = transaction.selection;
doc2 = transaction.doc;
storedMarks = transaction.storedMarks;
return transaction;
}
};
}
class CommandManager {
constructor(props) {
this.editor = props.editor;
this.rawCommands = this.editor.extensionManager.commands;
this.customState = props.state;
}
get hasCustomState() {
return !!this.customState;
}
get state() {
return this.customState || this.editor.state;
}
get commands() {
const { rawCommands, editor: editor2, state } = this;
const { view } = editor2;
const { tr } = state;
const props = this.buildProps(tr);
return Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
const method = (...args) => {
const callback = command2(...args)(props);
if (!tr.getMeta("preventDispatch") && !this.hasCustomState) {
view.dispatch(tr);
}
return callback;
};
return [name, method];
}));
}
get chain() {
return () => this.createChain();
}
get can() {
return () => this.createCan();
}
createChain(startTr, shouldDispatch = true) {
const { rawCommands, editor: editor2, state } = this;
const { view } = editor2;
const callbacks = [];
const hasStartTransaction = !!startTr;
const tr = startTr || state.tr;
const run2 = () => {
if (!hasStartTransaction && shouldDispatch && !tr.getMeta("preventDispatch") && !this.hasCustomState) {
view.dispatch(tr);
}
return callbacks.every((callback) => callback === true);
};
const chain = {
...Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
const chainedCommand = (...args) => {
const props = this.buildProps(tr, shouldDispatch);
const callback = command2(...args)(props);
callbacks.push(callback);
return chain;
};
return [name, chainedCommand];
})),
run: run2
};
return chain;
}
createCan(startTr) {
const { rawCommands, state } = this;
const dispatch = false;
const tr = startTr || state.tr;
const props = this.buildProps(tr, dispatch);
const formattedCommands = Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
return [name, (...args) => command2(...args)({ ...props, dispatch: void 0 })];
}));
return {
...formattedCommands,
chain: () => this.createChain(tr, dispatch)
};
}
buildProps(tr, shouldDispatch = true) {
const { rawCommands, editor: editor2, state } = this;
const { view } = editor2;
if (state.storedMarks) {
tr.setStoredMarks(state.storedMarks);
}
const props = {
tr,
editor: editor2,
view,
state: createChainableState({
state,
transaction: tr
}),
dispatch: shouldDispatch ? () => void 0 : void 0,
chain: () => this.createChain(tr),
can: () => this.createCan(tr),
get commands() {
return Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
return [name, (...args) => command2(...args)(props)];
}));
}
};
return props;
}
}
class EventEmitter {
constructor() {
this.callbacks = {};
}
on(event, fn) {
if (!this.callbacks[event]) {
this.callbacks[event] = [];
}
this.callbacks[event].push(fn);
return this;
}
emit(event, ...args) {
const callbacks = this.callbacks[event];
if (callbacks) {
callbacks.forEach((callback) => callback.apply(this, args));
}
return this;
}
off(event, fn) {
const callbacks = this.callbacks[event];
if (callbacks) {
if (fn) {
this.callbacks[event] = callbacks.filter((callback) => callback !== fn);
} else {
delete this.callbacks[event];
}
}
return this;
}
removeAllListeners() {
this.callbacks = {};
}
}
function getExtensionField(extension, field, context) {
if (extension.config[field] === void 0 && extension.parent) {
return getExtensionField(extension.parent, field, context);
}
if (typeof extension.config[field] === "function") {
const value = extension.config[field].bind({
...context,
parent: extension.parent ? getExtensionField(extension.parent, field, context) : null
});
return value;
}
return extension.config[field];
}
function splitExtensions(extensions2) {
const baseExtensions = extensions2.filter((extension) => extension.type === "extension");
const nodeExtensions = extensions2.filter((extension) => extension.type === "node");
const markExtensions = extensions2.filter((extension) => extension.type === "mark");
return {
baseExtensions,
nodeExtensions,
markExtensions
};
}
function getAttributesFromExtensions(extensions2) {
const extensionAttributes = [];
const { nodeExtensions, markExtensions } = splitExtensions(extensions2);
const nodeAndMarkExtensions = [...nodeExtensions, ...markExtensions];
const defaultAttribute = {
default: null,
rendered: true,
renderHTML: null,
parseHTML: null,
keepOnSplit: true,
isRequired: false
};
extensions2.forEach((extension) => {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
const addGlobalAttributes = getExtensionField(extension, "addGlobalAttributes", context);
if (!addGlobalAttributes) {
return;
}
const globalAttributes = addGlobalAttributes();
globalAttributes.forEach((globalAttribute) => {
globalAttribute.types.forEach((type) => {
Object.entries(globalAttribute.attributes).forEach(([name, attribute]) => {
extensionAttributes.push({
type,
name,
attribute: {
...defaultAttribute,
...attribute
}
});
});
});
});
});
nodeAndMarkExtensions.forEach((extension) => {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
const addAttributes = getExtensionField(extension, "addAttributes", context);
if (!addAttributes) {
return;
}
const attributes = addAttributes();
Object.entries(attributes).forEach(([name, attribute]) => {
const mergedAttr = {
...defaultAttribute,
...attribute
};
if ((attribute === null || attribute === void 0 ? void 0 : attribute.isRequired) && (attribute === null || attribute === void 0 ? void 0 : attribute.default) === void 0) {
delete mergedAttr.default;
}
extensionAttributes.push({
type: extension.name,
name,
attribute: mergedAttr
});
});
});
return extensionAttributes;
}
function getNodeType(nameOrType, schema) {
if (typeof nameOrType === "string") {
if (!schema.nodes[nameOrType]) {
throw Error(`There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`);
}
return schema.nodes[nameOrType];
}
return nameOrType;
}
function mergeAttributes(...objects) {
return objects.filter((item) => !!item).reduce((items, item) => {
const mergedAttributes = { ...items };
Object.entries(item).forEach(([key, value]) => {
const exists = mergedAttributes[key];
if (!exists) {
mergedAttributes[key] = value;
return;
}
if (key === "class") {
mergedAttributes[key] = [mergedAttributes[key], value].join(" ");
} else if (key === "style") {
mergedAttributes[key] = [mergedAttributes[key], value].join("; ");
} else {
mergedAttributes[key] = value;
}
});
return mergedAttributes;
}, {});
}
function getRenderedAttributes(nodeOrMark, extensionAttributes) {
return extensionAttributes.filter((item) => item.attribute.rendered).map((item) => {
if (!item.attribute.renderHTML) {
return {
[item.name]: nodeOrMark.attrs[item.name]
};
}
return item.attribute.renderHTML(nodeOrMark.attrs) || {};
}).reduce((attributes, attribute) => mergeAttributes(attributes, attribute), {});
}
function isFunction(value) {
return typeof value === "function";
}
function callOrReturn(value, context = void 0, ...props) {
if (isFunction(value)) {
if (context) {
return value.bind(context)(...props);
}
return value(...props);
}
return value;
}
function isEmptyObject(value = {}) {
return Object.keys(value).length === 0 && value.constructor === Object;
}
function fromString(value) {
if (typeof value !== "string") {
return value;
}
if (value.match(/^[+-]?(?:\d*\.)?\d+$/)) {
return Number(value);
}
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
return value;
}
function injectExtensionAttributesToParseRule(parseRule, extensionAttributes) {
if (parseRule.style) {
return parseRule;
}
return {
...parseRule,
getAttrs: (node) => {
const oldAttributes = parseRule.getAttrs ? parseRule.getAttrs(node) : parseRule.attrs;
if (oldAttributes === false) {
return false;
}
const newAttributes = extensionAttributes.reduce((items, item) => {
const value = item.attribute.parseHTML ? item.attribute.parseHTML(node) : fromString(node.getAttribute(item.name));
if (value === null || value === void 0) {
return items;
}
return {
...items,
[item.name]: value
};
}, {});
return { ...oldAttributes, ...newAttributes };
}
};
}
function cleanUpSchemaItem(data) {
return Object.fromEntries(Object.entries(data).filter(([key, value]) => {
if (key === "attrs" && isEmptyObject(value)) {
return false;
}
return value !== null && value !== void 0;
}));
}
function getSchemaByResolvedExtensions(extensions2) {
var _a;
const allAttributes = getAttributesFromExtensions(extensions2);
const { nodeExtensions, markExtensions } = splitExtensions(extensions2);
const topNode = (_a = nodeExtensions.find((extension) => getExtensionField(extension, "topNode"))) === null || _a === void 0 ? void 0 : _a.name;
const nodes = Object.fromEntries(nodeExtensions.map((extension) => {
const extensionAttributes = allAttributes.filter((attribute) => attribute.type === extension.name);
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
const extraNodeFields = extensions2.reduce((fields, e) => {
const extendNodeSchema = getExtensionField(e, "extendNodeSchema", context);
return {
...fields,
...extendNodeSchema ? extendNodeSchema(extension) : {}
};
}, {});
const schema = cleanUpSchemaItem({
...extraNodeFields,
content: callOrReturn(getExtensionField(extension, "content", context)),
marks: callOrReturn(getExtensionField(extension, "marks", context)),
group: callOrReturn(getExtensionField(extension, "group", context)),
inline: callOrReturn(getExtensionField(extension, "inline", context)),
atom: callOrReturn(getExtensionField(extension, "atom", context)),
selectable: callOrReturn(getExtensionField(extension, "selectable", context)),
draggable: callOrReturn(getExtensionField(extension, "draggable", context)),
code: callOrReturn(getExtensionField(extension, "code", context)),
defining: callOrReturn(getExtensionField(extension, "defining", context)),
isolating: callOrReturn(getExtensionField(extension, "isolating", context)),
attrs: Object.fromEntries(extensionAttributes.map((extensionAttribute) => {
var _a2;
return [extensionAttribute.name, { default: (_a2 = extensionAttribute === null || extensionAttribute === void 0 ? void 0 : extensionAttribute.attribute) === null || _a2 === void 0 ? void 0 : _a2.default }];
}))
});
const parseHTML = callOrReturn(getExtensionField(extension, "parseHTML", context));
if (parseHTML) {
schema.parseDOM = parseHTML.map((parseRule) => injectExtensionAttributesToParseRule(parseRule, extensionAttributes));
}
const renderHTML = getExtensionField(extension, "renderHTML", context);
if (renderHTML) {
schema.toDOM = (node) => renderHTML({
node,
HTMLAttributes: getRenderedAttributes(node, extensionAttributes)
});
}
const renderText = getExtensionField(extension, "renderText", context);
if (renderText) {
schema.toText = renderText;
}
return [extension.name, schema];
}));
const marks = Object.fromEntries(markExtensions.map((extension) => {
const extensionAttributes = allAttributes.filter((attribute) => attribute.type === extension.name);
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
const extraMarkFields = extensions2.reduce((fields, e) => {
const extendMarkSchema = getExtensionField(e, "extendMarkSchema", context);
return {
...fields,
...extendMarkSchema ? extendMarkSchema(extension) : {}
};
}, {});
const schema = cleanUpSchemaItem({
...extraMarkFields,
inclusive: callOrReturn(getExtensionField(extension, "inclusive", context)),
excludes: callOrReturn(getExtensionField(extension, "excludes", context)),
group: callOrReturn(getExtensionField(extension, "group", context)),
spanning: callOrReturn(getExtensionField(extension, "spanning", context)),
code: callOrReturn(getExtensionField(extension, "code", context)),
attrs: Object.fromEntries(extensionAttributes.map((extensionAttribute) => {
var _a2;
return [extensionAttribute.name, { default: (_a2 = extensionAttribute === null || extensionAttribute === void 0 ? void 0 : extensionAttribute.attribute) === null || _a2 === void 0 ? void 0 : _a2.default }];
}))
});
const parseHTML = callOrReturn(getExtensionField(extension, "parseHTML", context));
if (parseHTML) {
schema.parseDOM = parseHTML.map((parseRule) => injectExtensionAttributesToParseRule(parseRule, extensionAttributes));
}
const renderHTML = getExtensionField(extension, "renderHTML", context);
if (renderHTML) {
schema.toDOM = (mark) => renderHTML({
mark,
HTMLAttributes: getRenderedAttributes(mark, extensionAttributes)
});
}
return [extension.name, schema];
}));
return new Schema({
topNode,
nodes,
marks
});
}
function getSchemaTypeByName(name, schema) {
return schema.nodes[name] || schema.marks[name] || null;
}
function isExtensionRulesEnabled(extension, enabled) {
if (Array.isArray(enabled)) {
return enabled.some((enabledExtension) => {
const name = typeof enabledExtension === "string" ? enabledExtension : enabledExtension.name;
return name === extension.name;
});
}
return enabled;
}
const getTextContentFromNodes = ($from, maxMatch = 500) => {
let textBefore = "";
const sliceEndPos = $from.parentOffset;
$from.parent.nodesBetween(Math.max(0, sliceEndPos - maxMatch), sliceEndPos, (node, pos, parent, index) => {
var _a, _b;
const chunk = ((_b = (_a = node.type.spec).toText) === null || _b === void 0 ? void 0 : _b.call(_a, {
node,
pos,
parent,
index
})) || node.textContent || "%leaf%";
textBefore += chunk.slice(0, Math.max(0, sliceEndPos - pos));
});
return textBefore;
};
function isRegExp(value) {
return Object.prototype.toString.call(value) === "[object RegExp]";
}
class InputRule {
constructor(config) {
this.find = config.find;
this.handler = config.handler;
}
}
const inputRuleMatcherHandler = (text2, find) => {
if (isRegExp(find)) {
return find.exec(text2);
}
const inputRuleMatch = find(text2);
if (!inputRuleMatch) {
return null;
}
const result = [inputRuleMatch.text];
result.index = inputRuleMatch.index;
result.input = text2;
result.data = inputRuleMatch.data;
if (inputRuleMatch.replaceWith) {
if (!inputRuleMatch.text.includes(inputRuleMatch.replaceWith)) {
console.warn('[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".');
}
result.push(inputRuleMatch.replaceWith);
}
return result;
};
function run$1(config) {
var _a;
const { editor: editor2, from: from2, to, text: text2, rules, plugin } = config;
const { view } = editor2;
if (view.composing) {
return false;
}
const $from = view.state.doc.resolve(from2);
if (
// check for code node
$from.parent.type.spec.code || !!((_a = $from.nodeBefore || $from.nodeAfter) === null || _a === void 0 ? void 0 : _a.marks.find((mark) => mark.type.spec.code))
) {
return false;
}
let matched = false;
const textBefore = getTextContentFromNodes($from) + text2;
rules.forEach((rule) => {
if (matched) {
return;
}
const match = inputRuleMatcherHandler(textBefore, rule.find);
if (!match) {
return;
}
const tr = view.state.tr;
const state = createChainableState({
state: view.state,
transaction: tr
});
const range = {
from: from2 - (match[0].length - text2.length),
to
};
const { commands: commands2, chain, can } = new CommandManager({
editor: editor2,
state
});
const handler = rule.handler({
state,
range,
match,
commands: commands2,
chain,
can
});
if (handler === null || !tr.steps.length) {
return;
}
tr.setMeta(plugin, {
transform: tr,
from: from2,
to,
text: text2
});
view.dispatch(tr);
matched = true;
});
return matched;
}
function inputRulesPlugin(props) {
const { editor: editor2, rules } = props;
const plugin = new Plugin({
state: {
init() {
return null;
},
apply(tr, prev) {
const stored = tr.getMeta(plugin);
if (stored) {
return stored;
}
return tr.selectionSet || tr.docChanged ? null : prev;
}
},
props: {
handleTextInput(view, from2, to, text2) {
return run$1({
editor: editor2,
from: from2,
to,
text: text2,
rules,
plugin
});
},
handleDOMEvents: {
compositionend: (view) => {
setTimeout(() => {
const { $cursor } = view.state.selection;
if ($cursor) {
run$1({
editor: editor2,
from: $cursor.pos,
to: $cursor.pos,
text: "",
rules,
plugin
});
}
});
return false;
}
},
// add support for input rules to trigger on enter
// this is useful for example for code blocks
handleKeyDown(view, event) {
if (event.key !== "Enter") {
return false;
}
const { $cursor } = view.state.selection;
if ($cursor) {
return run$1({
editor: editor2,
from: $cursor.pos,
to: $cursor.pos,
text: "\n",
rules,
plugin
});
}
return false;
}
},
// @ts-ignore
isInputRules: true
});
return plugin;
}
function isNumber(value) {
return typeof value === "number";
}
class PasteRule {
constructor(config) {
this.find = config.find;
this.handler = config.handler;
}
}
const pasteRuleMatcherHandler = (text2, find) => {
if (isRegExp(find)) {
return [...text2.matchAll(find)];
}
const matches2 = find(text2);
if (!matches2) {
return [];
}
return matches2.map((pasteRuleMatch) => {
const result = [pasteRuleMatch.text];
result.index = pasteRuleMatch.index;
result.input = text2;
result.data = pasteRuleMatch.data;
if (pasteRuleMatch.replaceWith) {
if (!pasteRuleMatch.text.includes(pasteRuleMatch.replaceWith)) {
console.warn('[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".');
}
result.push(pasteRuleMatch.replaceWith);
}
return result;
});
};
function run(config) {
const { editor: editor2, state, from: from2, to, rule } = config;
const { commands: commands2, chain, can } = new CommandManager({
editor: editor2,
state
});
const handlers2 = [];
state.doc.nodesBetween(from2, to, (node, pos) => {
if (!node.isTextblock || node.type.spec.code) {
return;
}
const resolvedFrom = Math.max(from2, pos);
const resolvedTo = Math.min(to, pos + node.content.size);
const textToMatch = node.textBetween(resolvedFrom - pos, resolvedTo - pos, void 0, "");
const matches2 = pasteRuleMatcherHandler(textToMatch, rule.find);
matches2.forEach((match) => {
if (match.index === void 0) {
return;
}
const start = resolvedFrom + match.index + 1;
const end = start + match[0].length;
const range = {
from: state.tr.mapping.map(start),
to: state.tr.mapping.map(end)
};
const handler = rule.handler({
state,
range,
match,
commands: commands2,
chain,
can
});
handlers2.push(handler);
});
});
const success = handlers2.every((handler) => handler !== null);
return success;
}
function pasteRulesPlugin(props) {
const { editor: editor2, rules } = props;
let dragSourceElement = null;
let isPastedFromProseMirror = false;
let isDroppedFromProseMirror = false;
const plugins = rules.map((rule) => {
return new Plugin({
// we register a global drag handler to track the current drag source element
view(view) {
const handleDragstart = (event) => {
var _a;
dragSourceElement = ((_a = view.dom.parentElement) === null || _a === void 0 ? void 0 : _a.contains(event.target)) ? view.dom.parentElement : null;
};
window.addEventListener("dragstart", handleDragstart);
return {
destroy() {
window.removeEventListener("dragstart", handleDragstart);
}
};
},
props: {
handleDOMEvents: {
drop: (view) => {
isDroppedFromProseMirror = dragSourceElement === view.dom.parentElement;
return false;
},
paste: (view, event) => {
var _a;
const html = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData("text/html");
isPastedFromProseMirror = !!(html === null || html === void 0 ? void 0 : html.includes("data-pm-slice"));
return false;
}
}
},
appendTransaction: (transactions, oldState, state) => {
const transaction = transactions[0];
const isPaste = transaction.getMeta("uiEvent") === "paste" && !isPastedFromProseMirror;
const isDrop = transaction.getMeta("uiEvent") === "drop" && !isDroppedFromProseMirror;
if (!isPaste && !isDrop) {
return;
}
const from2 = oldState.doc.content.findDiffStart(state.doc.content);
const to = oldState.doc.content.findDiffEnd(state.doc.content);
if (!isNumber(from2) || !to || from2 === to.b) {
return;
}
const tr = state.tr;
const chainableState = createChainableState({
state,
transaction: tr
});
const handler = run({
editor: editor2,
state: chainableState,
from: Math.max(from2 - 1, 0),
to: to.b - 1,
rule
});
if (!handler || !tr.steps.length) {
return;
}
return tr;
}
});
});
return plugins;
}
function findDuplicates(items) {
const filtered = items.filter((el, index) => items.indexOf(el) !== index);
return [...new Set(filtered)];
}
class ExtensionManager {
constructor(extensions2, editor2) {
this.splittableMarks = [];
this.editor = editor2;
this.extensions = ExtensionManager.resolve(extensions2);
this.schema = getSchemaByResolvedExtensions(this.extensions);
this.extensions.forEach((extension) => {
var _a;
this.editor.extensionStorage[extension.name] = extension.storage;
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage,
editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema)
};
if (extension.type === "mark") {
const keepOnSplit = (_a = callOrReturn(getExtensionField(extension, "keepOnSplit", context))) !== null && _a !== void 0 ? _a : true;
if (keepOnSplit) {
this.splittableMarks.push(extension.name);
}
}
const onBeforeCreate = getExtensionField(extension, "onBeforeCreate", context);
if (onBeforeCreate) {
this.editor.on("beforeCreate", onBeforeCreate);
}
const onCreate = getExtensionField(extension, "onCreate", context);
if (onCreate) {
this.editor.on("create", onCreate);
}
const onUpdate = getExtensionField(extension, "onUpdate", context);
if (onUpdate) {
this.editor.on("update", onUpdate);
}
const onSelectionUpdate = getExtensionField(extension, "onSelectionUpdate", context);
if (onSelectionUpdate) {
this.editor.on("selectionUpdate", onSelectionUpdate);
}
const onTransaction = getExtensionField(extension, "onTransaction", context);
if (onTransaction) {
this.editor.on("transaction", onTransaction);
}
const onFocus = getExtensionField(extension, "onFocus", context);
if (onFocus) {
this.editor.on("focus", onFocus);
}
const onBlur = getExtensionField(extension, "onBlur", context);
if (onBlur) {
this.editor.on("blur", onBlur);
}
const onDestroy = getExtensionField(extension, "onDestroy", context);
if (onDestroy) {
this.editor.on("destroy", onDestroy);
}
});
}
static resolve(extensions2) {
const resolvedExtensions = ExtensionManager.sort(ExtensionManager.flatten(extensions2));
const duplicatedNames = findDuplicates(resolvedExtensions.map((extension) => extension.name));
if (duplicatedNames.length) {
console.warn(`[tiptap warn]: Duplicate extension names found: [${duplicatedNames.map((item) => `'${item}'`).join(", ")}]. This can lead to issues.`);
}
return resolvedExtensions;
}
static flatten(extensions2) {
return extensions2.map((extension) => {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
const addExtensions = getExtensionField(extension, "addExtensions", context);
if (addExtensions) {
return [extension, ...this.flatten(addExtensions())];
}
return extension;
}).flat(10);
}
static sort(extensions2) {
const defaultPriority = 100;
return extensions2.sort((a, b) => {
const priorityA = getExtensionField(a, "priority") || defaultPriority;
const priorityB = getExtensionField(b, "priority") || defaultPriority;
if (priorityA > priorityB) {
return -1;
}
if (priorityA < priorityB) {
return 1;
}
return 0;
});
}
get commands() {
return this.extensions.reduce((commands2, extension) => {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage,
editor: this.editor,
type: getSchemaTypeByName(extension.name, this.schema)
};
const addCommands = getExtensionField(extension, "addCommands", context);
if (!addCommands) {
return commands2;
}
return {
...commands2,
...addCommands()
};
}, {});
}
get plugins() {
const { editor: editor2 } = this;
const extensions2 = ExtensionManager.sort([...this.extensions].reverse());
const inputRules = [];
const pasteRules = [];
const allPlugins = extensions2.map((extension) => {
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage,
editor: editor2,
type: getSchemaTypeByName(extension.name, this.schema)
};
const plugins = [];
const addKeyboardShortcuts = getExtensionField(extension, "addKeyboardShortcuts", context);
let defaultBindings = {};
if (extension.type === "mark" && extension.config.exitable) {
defaultBindings.ArrowRight = () => Mark2.handleExit({ editor: editor2, mark: extension });
}
if (addKeyboardShortcuts) {
const bindings = Object.fromEntries(Object.entries(addKeyboardShortcuts()).map(([shortcut, method]) => {
return [shortcut, () => method({ editor: editor2 })];
}));
defaultBindings = { ...defaultBindings, ...bindings };
}
const keyMapPlugin = keymap(defaultBindings);
plugins.push(keyMapPlugin);
const addInputRules = getExtensionField(extension, "addInputRules", context);
if (isExtensionRulesEnabled(extension, editor2.options.enableInputRules) && addInputRules) {
inputRules.push(...addInputRules());
}
const addPasteRules = getExtensionField(extension, "addPasteRules", context);
if (isExtensionRulesEnabled(extension, editor2.options.enablePasteRules) && addPasteRules) {
pasteRules.push(...addPasteRules());
}
const addProseMirrorPlugins = getExtensionField(extension, "addProseMirrorPlugins", context);
if (addProseMirrorPlugins) {
const proseMirrorPlugins = addProseMirrorPlugins();
plugins.push(...proseMirrorPlugins);
}
return plugins;
}).flat();
return [
inputRulesPlugin({
editor: editor2,
rules: inputRules
}),
...pasteRulesPlugin({
editor: editor2,
rules: pasteRules
}),
...allPlugins
];
}
get attributes() {
return getAttributesFromExtensions(this.extensions);
}
get nodeViews() {
const { editor: editor2 } = this;
const { nodeExtensions } = splitExtensions(this.extensions);
return Object.fromEntries(nodeExtensions.filter((extension) => !!getExtensionField(extension, "addNodeView")).map((extension) => {
const extensionAttributes = this.attributes.filter((attribute) => attribute.type === extension.name);
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage,
editor: editor2,
type: getNodeType(extension.name, this.schema)
};
const addNodeView = getExtensionField(extension, "addNodeView", context);
if (!addNodeView) {
return [];
}
const nodeview = (node, view, getPos, decorations) => {
const HTMLAttributes = getRenderedAttributes(node, extensionAttributes);
return addNodeView()({
editor: editor2,
node,
getPos,
decorations,
HTMLAttributes,
extension
});
};
return [extension.name, nodeview];
}));
}
}
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
function isPlainObject(value) {
if (getType(value) !== "Object") {
return false;
}
return value.constructor === Object && Object.getPrototypeOf(value) === Object.prototype;
}
function mergeDeep(target, source) {
const output = { ...target };
if (isPlainObject(target) && isPlainObject(source)) {
Object.keys(source).forEach((key) => {
if (isPlainObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
} else {
output[key] = mergeDeep(target[key], source[key]);
}
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
class Extension {
constructor(config = {}) {
this.type = "extension";
this.name = "extension";
this.parent = null;
this.child = null;
this.config = {
name: this.name,
defaultOptions: {}
};
this.config = {
...this.config,
...config
};
this.name = this.config.name;
if (config.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`);
}
this.options = this.config.defaultOptions;
if (this.config.addOptions) {
this.options = callOrReturn(getExtensionField(this, "addOptions", {
name: this.name
}));
}
this.storage = callOrReturn(getExtensionField(this, "addStorage", {
name: this.name,
options: this.options
})) || {};
}
static create(config = {}) {
return new Extension(config);
}
configure(options = {}) {
const extension = this.extend();
extension.options = mergeDeep(this.options, options);
extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
name: extension.name,
options: extension.options
}));
return extension;
}
extend(extendedConfig = {}) {
const extension = new Extension(extendedConfig);
extension.parent = this;
this.child = extension;
extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name;
if (extendedConfig.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`);
}
extension.options = callOrReturn(getExtensionField(extension, "addOptions", {
name: extension.name
}));
extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
name: extension.name,
options: extension.options
}));
return extension;
}
}
function getTextBetween(startNode, range, options) {
const { from: from2, to } = range;
const { blockSeparator = "\n\n", textSerializers = {} } = options || {};
let text2 = "";
let separated = true;
startNode.nodesBetween(from2, to, (node, pos, parent, index) => {
var _a;
const textSerializer = textSerializers === null || textSerializers === void 0 ? void 0 : textSerializers[node.type.name];
if (textSerializer) {
if (node.isBlock && !separated) {
text2 += blockSeparator;
separated = true;
}
if (parent) {
text2 += textSerializer({
node,
pos,
parent,
index,
range
});
}
} else if (node.isText) {
text2 += (_a = node === null || node === void 0 ? void 0 : node.text) === null || _a === void 0 ? void 0 : _a.slice(Math.max(from2, pos) - pos, to - pos);
separated = false;
} else if (node.isBlock && !separated) {
text2 += blockSeparator;
separated = true;
}
});
return text2;
}
function getTextSerializersFromSchema(schema) {
return Object.fromEntries(Object.entries(schema.nodes).filter(([, node]) => node.spec.toText).map(([name, node]) => [name, node.spec.toText]));
}
const ClipboardTextSerializer = Extension.create({
name: "clipboardTextSerializer",
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey("clipboardTextSerializer"),
props: {
clipboardTextSerializer: () => {
const { editor: editor2 } = this;
const { state, schema } = editor2;
const { doc: doc2, selection } = state;
const { ranges } = selection;
const from2 = Math.min(...ranges.map((range2) => range2.$from.pos));
const to = Math.max(...ranges.map((range2) => range2.$to.pos));
const textSerializers = getTextSerializersFromSchema(schema);
const range = { from: from2, to };
return getTextBetween(doc2, range, {
textSerializers
});
}
}
})
];
}
});
const blur = () => ({ editor: editor2, view }) => {
requestAnimationFrame(() => {
var _a;
if (!editor2.isDestroyed) {
view.dom.blur();
(_a = window === null || window === void 0 ? void 0 : window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
}
});
return true;
};
const clearContent = (emitUpdate = false) => ({ commands: commands2 }) => {
return commands2.setContent("", emitUpdate);
};
const clearNodes = () => ({ state, tr, dispatch }) => {
const { selection } = tr;
const { ranges } = selection;
if (!dispatch) {
return true;
}
ranges.forEach(({ $from, $to }) => {
state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
if (node.type.isText) {
return;
}
const { doc: doc2, mapping } = tr;
const $mappedFrom = doc2.resolve(mapping.map(pos));
const $mappedTo = doc2.resolve(mapping.map(pos + node.nodeSize));
const nodeRange = $mappedFrom.blockRange($mappedTo);
if (!nodeRange) {
return;
}
const targetLiftDepth = liftTarget(nodeRange);
if (node.type.isTextblock) {
const { defaultType } = $mappedFrom.parent.contentMatchAt($mappedFrom.index());
tr.setNodeMarkup(nodeRange.start, defaultType);
}
if (targetLiftDepth || targetLiftDepth === 0) {
tr.lift(nodeRange, targetLiftDepth);
}
});
});
return true;
};
const command = (fn) => (props) => {
return fn(props);
};
const createParagraphNear = () => ({ state, dispatch }) => {
return createParagraphNear$1(state, dispatch);
};
const deleteCurrentNode = () => ({ tr, dispatch }) => {
const { selection } = tr;
const currentNode = selection.$anchor.node();
if (currentNode.content.size > 0) {
return false;
}
const $pos = tr.selection.$anchor;
for (let depth = $pos.depth; depth > 0; depth -= 1) {
const node = $pos.node(depth);
if (node.type === currentNode.type) {
if (dispatch) {
const from2 = $pos.before(depth);
const to = $pos.after(depth);
tr.delete(from2, to).scrollIntoView();
}
return true;
}
}
return false;
};
const deleteNode = (typeOrName) => ({ tr, state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
const $pos = tr.selection.$anchor;
for (let depth = $pos.depth; depth > 0; depth -= 1) {
const node = $pos.node(depth);
if (node.type === type) {
if (dispatch) {
const from2 = $pos.before(depth);
const to = $pos.after(depth);
tr.delete(from2, to).scrollIntoView();
}
return true;
}
}
return false;
};
const deleteRange = (range) => ({ tr, dispatch }) => {
const { from: from2, to } = range;
if (dispatch) {
tr.delete(from2, to);
}
return true;
};
const deleteSelection = () => ({ state, dispatch }) => {
return deleteSelection$1(state, dispatch);
};
const enter = () => ({ commands: commands2 }) => {
return commands2.keyboardShortcut("Enter");
};
const exitCode = () => ({ state, dispatch }) => {
return exitCode$1(state, dispatch);
};
function objectIncludes(object1, object2, options = { strict: true }) {
const keys2 = Object.keys(object2);
if (!keys2.length) {
return true;
}
return keys2.every((key) => {
if (options.strict) {
return object2[key] === object1[key];
}
if (isRegExp(object2[key])) {
return object2[key].test(object1[key]);
}
return object2[key] === object1[key];
});
}
function findMarkInSet(marks, type, attributes = {}) {
return marks.find((item) => {
return item.type === type && objectIncludes(item.attrs, attributes);
});
}
function isMarkInSet(marks, type, attributes = {}) {
return !!findMarkInSet(marks, type, attributes);
}
function getMarkRange($pos, type, attributes = {}) {
if (!$pos || !type) {
return;
}
let start = $pos.parent.childAfter($pos.parentOffset);
if ($pos.parentOffset === start.offset && start.offset !== 0) {
start = $pos.parent.childBefore($pos.parentOffset);
}
if (!start.node) {
return;
}
const mark = findMarkInSet([...start.node.marks], type, attributes);
if (!mark) {
return;
}
let startIndex = start.index;
let startPos = $pos.start() + start.offset;
let endIndex = startIndex + 1;
let endPos = startPos + start.node.nodeSize;
findMarkInSet([...start.node.marks], type, attributes);
while (startIndex > 0 && mark.isInSet($pos.parent.child(startIndex - 1).marks)) {
startIndex -= 1;
startPos -= $pos.parent.child(startIndex).nodeSize;
}
while (endIndex < $pos.parent.childCount && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)) {
endPos += $pos.parent.child(endIndex).nodeSize;
endIndex += 1;
}
return {
from: startPos,
to: endPos
};
}
function getMarkType(nameOrType, schema) {
if (typeof nameOrType === "string") {
if (!schema.marks[nameOrType]) {
throw Error(`There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`);
}
return schema.marks[nameOrType];
}
return nameOrType;
}
const extendMarkRange = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
const type = getMarkType(typeOrName, state.schema);
const { doc: doc2, selection } = tr;
const { $from, from: from2, to } = selection;
if (dispatch) {
const range = getMarkRange($from, type, attributes);
if (range && range.from <= from2 && range.to >= to) {
const newSelection = TextSelection.create(doc2, range.from, range.to);
tr.setSelection(newSelection);
}
}
return true;
};
const first = (commands2) => (props) => {
const items = typeof commands2 === "function" ? commands2(props) : commands2;
for (let i = 0; i < items.length; i += 1) {
if (items[i](props)) {
return true;
}
}
return false;
};
function isTextSelection(value) {
return value instanceof TextSelection;
}
function minMax(value = 0, min = 0, max = 0) {
return Math.min(Math.max(value, min), max);
}
function resolveFocusPosition(doc2, position = null) {
if (!position) {
return null;
}
const selectionAtStart = Selection.atStart(doc2);
const selectionAtEnd = Selection.atEnd(doc2);
if (position === "start" || position === true) {
return selectionAtStart;
}
if (position === "end") {
return selectionAtEnd;
}
const minPos = selectionAtStart.from;
const maxPos = selectionAtEnd.to;
if (position === "all") {
return TextSelection.create(doc2, minMax(0, minPos, maxPos), minMax(doc2.content.size, minPos, maxPos));
}
return TextSelection.create(doc2, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos));
}
function isiOS() {
return [
"iPad Simulator",
"iPhone Simulator",
"iPod Simulator",
"iPad",
"iPhone",
"iPod"
].includes(navigator.platform) || navigator.userAgent.includes("Mac") && "ontouchend" in document;
}
const focus = (position = null, options = {}) => ({ editor: editor2, view, tr, dispatch }) => {
options = {
scrollIntoView: true,
...options
};
const delayedFocus = () => {
if (isiOS()) {
view.dom.focus();
}
requestAnimationFrame(() => {
if (!editor2.isDestroyed) {
view.focus();
if (options === null || options === void 0 ? void 0 : options.scrollIntoView) {
editor2.commands.scrollIntoView();
}
}
});
};
if (view.hasFocus() && position === null || position === false) {
return true;
}
if (dispatch && position === null && !isTextSelection(editor2.state.selection)) {
delayedFocus();
return true;
}
const selection = resolveFocusPosition(tr.doc, position) || editor2.state.selection;
const isSameSelection = editor2.state.selection.eq(selection);
if (dispatch) {
if (!isSameSelection) {
tr.setSelection(selection);
}
if (isSameSelection && tr.storedMarks) {
tr.setStoredMarks(tr.storedMarks);
}
delayedFocus();
}
return true;
};
const forEach = (items, fn) => (props) => {
return items.every((item, index) => fn(item, { ...props, index }));
};
const insertContent = (value, options) => ({ tr, commands: commands2 }) => {
return commands2.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options);
};
function elementFromString(value) {
const wrappedValue = `<body>${value}</body>`;
return new window.DOMParser().parseFromString(wrappedValue, "text/html").body;
}
function createNodeFromContent(content, schema, options) {
options = {
slice: true,
parseOptions: {},
...options
};
if (typeof content === "object" && content !== null) {
try {
if (Array.isArray(content)) {
return Fragment.fromArray(content.map((item) => schema.nodeFromJSON(item)));
}
return schema.nodeFromJSON(content);
} catch (error) {
console.warn("[tiptap warn]: Invalid content.", "Passed value:", content, "Error:", error);
return createNodeFromContent("", schema, options);
}
}
if (typeof content === "string") {
const parser = DOMParser.fromSchema(schema);
return options.slice ? parser.parseSlice(elementFromString(content), options.parseOptions).content : parser.parse(elementFromString(content), options.parseOptions);
}
return createNodeFromContent("", schema, options);
}
function selectionToInsertionEnd(tr, startLen, bias) {
const last = tr.steps.length - 1;
if (last < startLen) {
return;
}
const step = tr.steps[last];
if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) {
return;
}
const map2 = tr.mapping.maps[last];
let end = 0;
map2.forEach((_from, _to, _newFrom, newTo) => {
if (end === 0) {
end = newTo;
}
});
tr.setSelection(Selection.near(tr.doc.resolve(end), bias));
}
const isFragment = (nodeOrFragment) => {
return nodeOrFragment.toString().startsWith("<");
};
const insertContentAt = (position, value, options) => ({ tr, dispatch, editor: editor2 }) => {
if (dispatch) {
options = {
parseOptions: {},
updateSelection: true,
...options
};
const content = createNodeFromContent(value, editor2.schema, {
parseOptions: {
preserveWhitespace: "full",
...options.parseOptions
}
});
if (content.toString() === "<>") {
return true;
}
let { from: from2, to } = typeof position === "number" ? { from: position, to: position } : position;
let isOnlyTextContent = true;
let isOnlyBlockContent = true;
const nodes = isFragment(content) ? content : [content];
nodes.forEach((node) => {
node.check();
isOnlyTextContent = isOnlyTextContent ? node.isText && node.marks.length === 0 : false;
isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false;
});
if (from2 === to && isOnlyBlockContent) {
const { parent } = tr.doc.resolve(from2);
const isEmptyTextBlock = parent.isTextblock && !parent.type.spec.code && !parent.childCount;
if (isEmptyTextBlock) {
from2 -= 1;
to += 1;
}
}
if (isOnlyTextContent) {
tr.insertText(value, from2, to);
} else {
tr.replaceWith(from2, to, content);
}
if (options.updateSelection) {
selectionToInsertionEnd(tr, tr.steps.length - 1, -1);
}
}
return true;
};
const joinUp = () => ({ state, dispatch }) => {
return joinUp$1(state, dispatch);
};
const joinDown = () => ({ state, dispatch }) => {
return joinDown$1(state, dispatch);
};
const joinBackward = () => ({ state, dispatch }) => {
return joinBackward$1(state, dispatch);
};
const joinForward = () => ({ state, dispatch }) => {
return joinForward$1(state, dispatch);
};
function isMacOS() {
return typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
}
function normalizeKeyName(name) {
const parts = name.split(/-(?!$)/);
let result = parts[parts.length - 1];
if (result === "Space") {
result = " ";
}
let alt;
let ctrl;
let shift;
let meta;
for (let i = 0; i < parts.length - 1; i += 1) {
const mod = parts[i];
if (/^(cmd|meta|m)$/i.test(mod)) {
meta = true;
} else if (/^a(lt)?$/i.test(mod)) {
alt = true;
} else if (/^(c|ctrl|control)$/i.test(mod)) {
ctrl = true;
} else if (/^s(hift)?$/i.test(mod)) {
shift = true;
} else if (/^mod$/i.test(mod)) {
if (isiOS() || isMacOS()) {
meta = true;
} else {
ctrl = true;
}
} else {
throw new Error(`Unrecognized modifier name: ${mod}`);
}
}
if (alt) {
result = `Alt-${result}`;
}
if (ctrl) {
result = `Ctrl-${result}`;
}
if (meta) {
result = `Meta-${result}`;
}
if (shift) {
result = `Shift-${result}`;
}
return result;
}
const keyboardShortcut = (name) => ({ editor: editor2, view, tr, dispatch }) => {
const keys2 = normalizeKeyName(name).split(/-(?!$)/);
const key = keys2.find((item) => !["Alt", "Ctrl", "Meta", "Shift"].includes(item));
const event = new KeyboardEvent("keydown", {
key: key === "Space" ? " " : key,
altKey: keys2.includes("Alt"),
ctrlKey: keys2.includes("Ctrl"),
metaKey: keys2.includes("Meta"),
shiftKey: keys2.includes("Shift"),
bubbles: true,
cancelable: true
});
const capturedTransaction = editor2.captureTransaction(() => {
view.someProp("handleKeyDown", (f) => f(view, event));
});
capturedTransaction === null || capturedTransaction === void 0 ? void 0 : capturedTransaction.steps.forEach((step) => {
const newStep = step.map(tr.mapping);
if (newStep && dispatch) {
tr.maybeStep(newStep);
}
});
return true;
};
function isNodeActive(state, typeOrName, attributes = {}) {
const { from: from2, to, empty: empty2 } = state.selection;
const type = typeOrName ? getNodeType(typeOrName, state.schema) : null;
const nodeRanges = [];
state.doc.nodesBetween(from2, to, (node, pos) => {
if (node.isText) {
return;
}
const relativeFrom = Math.max(from2, pos);
const relativeTo = Math.min(to, pos + node.nodeSize);
nodeRanges.push({
node,
from: relativeFrom,
to: relativeTo
});
});
const selectionRange = to - from2;
const matchedNodeRanges = nodeRanges.filter((nodeRange) => {
if (!type) {
return true;
}
return type.name === nodeRange.node.type.name;
}).filter((nodeRange) => objectIncludes(nodeRange.node.attrs, attributes, { strict: false }));
if (empty2) {
return !!matchedNodeRanges.length;
}
const range = matchedNodeRanges.reduce((sum, nodeRange) => sum + nodeRange.to - nodeRange.from, 0);
return range >= selectionRange;
}
const lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
const isActive2 = isNodeActive(state, type, attributes);
if (!isActive2) {
return false;
}
return lift$1(state, dispatch);
};
const liftEmptyBlock = () => ({ state, dispatch }) => {
return liftEmptyBlock$1(state, dispatch);
};
const liftListItem = (typeOrName) => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
return liftListItem$1(type)(state, dispatch);
};
const newlineInCode = () => ({ state, dispatch }) => {
return newlineInCode$1(state, dispatch);
};
function getSchemaTypeNameByName(name, schema) {
if (schema.nodes[name]) {
return "node";
}
if (schema.marks[name]) {
return "mark";
}
return null;
}
function deleteProps(obj, propOrProps) {
const props = typeof propOrProps === "string" ? [propOrProps] : propOrProps;
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
const resetAttributes = (typeOrName, attributes) => ({ tr, state, dispatch }) => {
let nodeType = null;
let markType = null;
const schemaType = getSchemaTypeNameByName(typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema);
if (!schemaType) {
return false;
}
if (schemaType === "node") {
nodeType = getNodeType(typeOrName, state.schema);
}
if (schemaType === "mark") {
markType = getMarkType(typeOrName, state.schema);
}
if (dispatch) {
tr.selection.ranges.forEach((range) => {
state.doc.nodesBetween(range.$from.pos, range.$to.pos, (node, pos) => {
if (nodeType && nodeType === node.type) {
tr.setNodeMarkup(pos, void 0, deleteProps(node.attrs, attributes));
}
if (markType && node.marks.length) {
node.marks.forEach((mark) => {
if (markType === mark.type) {
tr.addMark(pos, pos + node.nodeSize, markType.create(deleteProps(mark.attrs, attributes)));
}
});
}
});
});
}
return true;
};
const scrollIntoView = () => ({ tr, dispatch }) => {
if (dispatch) {
tr.scrollIntoView();
}
return true;
};
const selectAll = () => ({ tr, commands: commands2 }) => {
return commands2.setTextSelection({
from: 0,
to: tr.doc.content.size
});
};
const selectNodeBackward = () => ({ state, dispatch }) => {
return selectNodeBackward$1(state, dispatch);
};
const selectNodeForward = () => ({ state, dispatch }) => {
return selectNodeForward$1(state, dispatch);
};
const selectParentNode = () => ({ state, dispatch }) => {
return selectParentNode$1(state, dispatch);
};
const selectTextblockEnd = () => ({ state, dispatch }) => {
return selectTextblockEnd$1(state, dispatch);
};
const selectTextblockStart = () => ({ state, dispatch }) => {
return selectTextblockStart$1(state, dispatch);
};
function createDocument(content, schema, parseOptions = {}) {
return createNodeFromContent(content, schema, { slice: false, parseOptions });
}
const setContent = (content, emitUpdate = false, parseOptions = {}) => ({ tr, editor: editor2, dispatch }) => {
const { doc: doc2 } = tr;
const document2 = createDocument(content, editor2.schema, parseOptions);
if (dispatch) {
tr.replaceWith(0, doc2.content.size, document2).setMeta("preventUpdate", !emitUpdate);
}
return true;
};
function defaultBlockAt(match) {
for (let i = 0; i < match.edgeCount; i += 1) {
const { type } = match.edge(i);
if (type.isTextblock && !type.hasRequiredAttrs()) {
return type;
}
}
return null;
}
function findParentNodeClosestToPos($pos, predicate) {
for (let i = $pos.depth; i > 0; i -= 1) {
const node = $pos.node(i);
if (predicate(node)) {
return {
pos: i > 0 ? $pos.before(i) : 0,
start: $pos.start(i),
depth: i,
node
};
}
}
}
function findParentNode(predicate) {
return (selection) => findParentNodeClosestToPos(selection.$from, predicate);
}
function getHTMLFromFragment(fragment, schema) {
const documentFragment = DOMSerializer.fromSchema(schema).serializeFragment(fragment);
const temporaryDocument = document.implementation.createHTMLDocument();
const container = temporaryDocument.createElement("div");
container.appendChild(documentFragment);
return container.innerHTML;
}
function getText(node, options) {
const range = {
from: 0,
to: node.content.size
};
return getTextBetween(node, range, options);
}
function getMarkAttributes(state, typeOrName) {
const type = getMarkType(typeOrName, state.schema);
const { from: from2, to, empty: empty2 } = state.selection;
const marks = [];
if (empty2) {
if (state.storedMarks) {
marks.push(...state.storedMarks);
}
marks.push(...state.selection.$head.marks());
} else {
state.doc.nodesBetween(from2, to, (node) => {
marks.push(...node.marks);
});
}
const mark = marks.find((markItem) => markItem.type.name === type.name);
if (!mark) {
return {};
}
return { ...mark.attrs };
}
function getNodeAttributes(state, typeOrName) {
const type = getNodeType(typeOrName, state.schema);
const { from: from2, to } = state.selection;
const nodes = [];
state.doc.nodesBetween(from2, to, (node2) => {
nodes.push(node2);
});
const node = nodes.reverse().find((nodeItem) => nodeItem.type.name === type.name);
if (!node) {
return {};
}
return { ...node.attrs };
}
function getAttributes(state, typeOrName) {
const schemaType = getSchemaTypeNameByName(typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema);
if (schemaType === "node") {
return getNodeAttributes(state, typeOrName);
}
if (schemaType === "mark") {
return getMarkAttributes(state, typeOrName);
}
return {};
}
function getMarksBetween(from2, to, doc2) {
const marks = [];
if (from2 === to) {
doc2.resolve(from2).marks().forEach((mark) => {
const $pos = doc2.resolve(from2 - 1);
const range = getMarkRange($pos, mark.type);
if (!range) {
return;
}
marks.push({
mark,
...range
});
});
} else {
doc2.nodesBetween(from2, to, (node, pos) => {
marks.push(...node.marks.map((mark) => ({
from: pos,
to: pos + node.nodeSize,
mark
})));
});
}
return marks;
}
function isMarkActive(state, typeOrName, attributes = {}) {
const { empty: empty2, ranges } = state.selection;
const type = typeOrName ? getMarkType(typeOrName, state.schema) : null;
if (empty2) {
return !!(state.storedMarks || state.selection.$from.marks()).filter((mark) => {
if (!type) {
return true;
}
return type.name === mark.type.name;
}).find((mark) => objectIncludes(mark.attrs, attributes, { strict: false }));
}
let selectionRange = 0;
const markRanges = [];
ranges.forEach(({ $from, $to }) => {
const from2 = $from.pos;
const to = $to.pos;
state.doc.nodesBetween(from2, to, (node, pos) => {
if (!node.isText && !node.marks.length) {
return;
}
const relativeFrom = Math.max(from2, pos);
const relativeTo = Math.min(to, pos + node.nodeSize);
const range2 = relativeTo - relativeFrom;
selectionRange += range2;
markRanges.push(...node.marks.map((mark) => ({
mark,
from: relativeFrom,
to: relativeTo
})));
});
});
if (selectionRange === 0) {
return false;
}
const matchedRange = markRanges.filter((markRange) => {
if (!type) {
return true;
}
return type.name === markRange.mark.type.name;
}).filter((markRange) => objectIncludes(markRange.mark.attrs, attributes, { strict: false })).reduce((sum, markRange) => sum + markRange.to - markRange.from, 0);
const excludedRange = markRanges.filter((markRange) => {
if (!type) {
return true;
}
return markRange.mark.type !== type && markRange.mark.type.excludes(type);
}).reduce((sum, markRange) => sum + markRange.to - markRange.from, 0);
const range = matchedRange > 0 ? matchedRange + excludedRange : matchedRange;
return range >= selectionRange;
}
function isActive(state, name, attributes = {}) {
if (!name) {
return isNodeActive(state, null, attributes) || isMarkActive(state, null, attributes);
}
const schemaType = getSchemaTypeNameByName(name, state.schema);
if (schemaType === "node") {
return isNodeActive(state, name, attributes);
}
if (schemaType === "mark") {
return isMarkActive(state, name, attributes);
}
return false;
}
function isList(name, extensions2) {
const { nodeExtensions } = splitExtensions(extensions2);
const extension = nodeExtensions.find((item) => item.name === name);
if (!extension) {
return false;
}
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
const group = callOrReturn(getExtensionField(extension, "group", context));
if (typeof group !== "string") {
return false;
}
return group.split(" ").includes("list");
}
function isNodeEmpty(node) {
var _a;
const defaultContent = (_a = node.type.createAndFill()) === null || _a === void 0 ? void 0 : _a.toJSON();
const content = node.toJSON();
return JSON.stringify(defaultContent) === JSON.stringify(content);
}
function canSetMark(state, tr, newMarkType) {
var _a;
const { selection } = tr;
let cursor = null;
if (isTextSelection(selection)) {
cursor = selection.$cursor;
}
if (cursor) {
const currentMarks = (_a = state.storedMarks) !== null && _a !== void 0 ? _a : cursor.marks();
return !!newMarkType.isInSet(currentMarks) || !currentMarks.some((mark) => mark.type.excludes(newMarkType));
}
const { ranges } = selection;
return ranges.some(({ $from, $to }) => {
let someNodeSupportsMark = $from.depth === 0 ? state.doc.inlineContent && state.doc.type.allowsMarkType(newMarkType) : false;
state.doc.nodesBetween($from.pos, $to.pos, (node, _pos, parent) => {
if (someNodeSupportsMark) {
return false;
}
if (node.isInline) {
const parentAllowsMarkType = !parent || parent.type.allowsMarkType(newMarkType);
const currentMarksAllowMarkType = !!newMarkType.isInSet(node.marks) || !node.marks.some((otherMark) => otherMark.type.excludes(newMarkType));
someNodeSupportsMark = parentAllowsMarkType && currentMarksAllowMarkType;
}
return !someNodeSupportsMark;
});
return someNodeSupportsMark;
});
}
const setMark = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
const { selection } = tr;
const { empty: empty2, ranges } = selection;
const type = getMarkType(typeOrName, state.schema);
if (dispatch) {
if (empty2) {
const oldAttributes = getMarkAttributes(state, type);
tr.addStoredMark(type.create({
...oldAttributes,
...attributes
}));
} else {
ranges.forEach((range) => {
const from2 = range.$from.pos;
const to = range.$to.pos;
state.doc.nodesBetween(from2, to, (node, pos) => {
const trimmedFrom = Math.max(pos, from2);
const trimmedTo = Math.min(pos + node.nodeSize, to);
const someHasMark = node.marks.find((mark) => mark.type === type);
if (someHasMark) {
node.marks.forEach((mark) => {
if (type === mark.type) {
tr.addMark(trimmedFrom, trimmedTo, type.create({
...mark.attrs,
...attributes
}));
}
});
} else {
tr.addMark(trimmedFrom, trimmedTo, type.create(attributes));
}
});
});
}
}
return canSetMark(state, tr, type);
};
const setMeta = (key, value) => ({ tr }) => {
tr.setMeta(key, value);
return true;
};
const setNode = (typeOrName, attributes = {}) => ({ state, dispatch, chain }) => {
const type = getNodeType(typeOrName, state.schema);
if (!type.isTextblock) {
console.warn('[tiptap warn]: Currently "setNode()" only supports text block nodes.');
return false;
}
return chain().command(({ commands: commands2 }) => {
const canSetBlock = setBlockType(type, attributes)(state);
if (canSetBlock) {
return true;
}
return commands2.clearNodes();
}).command(({ state: updatedState }) => {
return setBlockType(type, attributes)(updatedState, dispatch);
}).run();
};
const setNodeSelection = (position) => ({ tr, dispatch }) => {
if (dispatch) {
const { doc: doc2 } = tr;
const from2 = minMax(position, 0, doc2.content.size);
const selection = NodeSelection.create(doc2, from2);
tr.setSelection(selection);
}
return true;
};
const setTextSelection = (position) => ({ tr, dispatch }) => {
if (dispatch) {
const { doc: doc2 } = tr;
const { from: from2, to } = typeof position === "number" ? { from: position, to: position } : position;
const minPos = TextSelection.atStart(doc2).from;
const maxPos = TextSelection.atEnd(doc2).to;
const resolvedFrom = minMax(from2, minPos, maxPos);
const resolvedEnd = minMax(to, minPos, maxPos);
const selection = TextSelection.create(doc2, resolvedFrom, resolvedEnd);
tr.setSelection(selection);
}
return true;
};
const sinkListItem = (typeOrName) => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
return sinkListItem$1(type)(state, dispatch);
};
function getSplittedAttributes(extensionAttributes, typeName, attributes) {
return Object.fromEntries(Object.entries(attributes).filter(([name]) => {
const extensionAttribute = extensionAttributes.find((item) => {
return item.type === typeName && item.name === name;
});
if (!extensionAttribute) {
return false;
}
return extensionAttribute.attribute.keepOnSplit;
}));
}
function ensureMarks(state, splittableMarks) {
const marks = state.storedMarks || state.selection.$to.parentOffset && state.selection.$from.marks();
if (marks) {
const filteredMarks = marks.filter((mark) => splittableMarks === null || splittableMarks === void 0 ? void 0 : splittableMarks.includes(mark.type.name));
state.tr.ensureMarks(filteredMarks);
}
}
const splitBlock = ({ keepMarks = true } = {}) => ({ tr, state, dispatch, editor: editor2 }) => {
const { selection, doc: doc2 } = tr;
const { $from, $to } = selection;
const extensionAttributes = editor2.extensionManager.attributes;
const newAttributes = getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs);
if (selection instanceof NodeSelection && selection.node.isBlock) {
if (!$from.parentOffset || !canSplit(doc2, $from.pos)) {
return false;
}
if (dispatch) {
if (keepMarks) {
ensureMarks(state, editor2.extensionManager.splittableMarks);
}
tr.split($from.pos).scrollIntoView();
}
return true;
}
if (!$from.parent.isBlock) {
return false;
}
if (dispatch) {
const atEnd = $to.parentOffset === $to.parent.content.size;
if (selection instanceof TextSelection) {
tr.deleteSelection();
}
const deflt = $from.depth === 0 ? void 0 : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)));
let types = atEnd && deflt ? [
{
type: deflt,
attrs: newAttributes
}
] : void 0;
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types);
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : void 0)) {
can = true;
types = deflt ? [
{
type: deflt,
attrs: newAttributes
}
] : void 0;
}
if (can) {
tr.split(tr.mapping.map($from.pos), 1, types);
if (deflt && !atEnd && !$from.parentOffset && $from.parent.type !== deflt) {
const first2 = tr.mapping.map($from.before());
const $first = tr.doc.resolve(first2);
if ($from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt)) {
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt);
}
}
}
if (keepMarks) {
ensureMarks(state, editor2.extensionManager.splittableMarks);
}
tr.scrollIntoView();
}
return true;
};
const splitListItem = (typeOrName) => ({ tr, state, dispatch, editor: editor2 }) => {
var _a;
const type = getNodeType(typeOrName, state.schema);
const { $from, $to } = state.selection;
const node = state.selection.node;
if (node && node.isBlock || $from.depth < 2 || !$from.sameParent($to)) {
return false;
}
const grandParent = $from.node(-1);
if (grandParent.type !== type) {
return false;
}
const extensionAttributes = editor2.extensionManager.attributes;
if ($from.parent.content.size === 0 && $from.node(-1).childCount === $from.indexAfter(-1)) {
if ($from.depth === 2 || $from.node(-3).type !== type || $from.index(-2) !== $from.node(-2).childCount - 1) {
return false;
}
if (dispatch) {
let wrap2 = Fragment.empty;
const depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3;
for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d -= 1) {
wrap2 = Fragment.from($from.node(d).copy(wrap2));
}
const depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1 : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3;
const newNextTypeAttributes2 = getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs);
const nextType2 = ((_a = type.contentMatch.defaultType) === null || _a === void 0 ? void 0 : _a.createAndFill(newNextTypeAttributes2)) || void 0;
wrap2 = wrap2.append(Fragment.from(type.createAndFill(null, nextType2) || void 0));
const start = $from.before($from.depth - (depthBefore - 1));
tr.replace(start, $from.after(-depthAfter), new Slice(wrap2, 4 - depthBefore, 0));
let sel = -1;
tr.doc.nodesBetween(start, tr.doc.content.size, (n, pos) => {
if (sel > -1) {
return false;
}
if (n.isTextblock && n.content.size === 0) {
sel = pos + 1;
}
});
if (sel > -1) {
tr.setSelection(TextSelection.near(tr.doc.resolve(sel)));
}
tr.scrollIntoView();
}
return true;
}
const nextType = $to.pos === $from.end() ? grandParent.contentMatchAt(0).defaultType : null;
const newTypeAttributes = getSplittedAttributes(extensionAttributes, grandParent.type.name, grandParent.attrs);
const newNextTypeAttributes = getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs);
tr.delete($from.pos, $to.pos);
const types = nextType ? [
{ type, attrs: newTypeAttributes },
{ type: nextType, attrs: newNextTypeAttributes }
] : [{ type, attrs: newTypeAttributes }];
if (!canSplit(tr.doc, $from.pos, 2)) {
return false;
}
if (dispatch) {
tr.split($from.pos, 2, types).scrollIntoView();
}
return true;
};
const joinListBackwards = (tr, listType) => {
const list = findParentNode((node) => node.type === listType)(tr.selection);
if (!list) {
return true;
}
const before = tr.doc.resolve(Math.max(0, list.pos - 1)).before(list.depth);
if (before === void 0) {
return true;
}
const nodeBefore = tr.doc.nodeAt(before);
const canJoinBackwards = list.node.type === (nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.type) && canJoin(tr.doc, list.pos);
if (!canJoinBackwards) {
return true;
}
tr.join(list.pos);
return true;
};
const joinListForwards = (tr, listType) => {
const list = findParentNode((node) => node.type === listType)(tr.selection);
if (!list) {
return true;
}
const after = tr.doc.resolve(list.start).after(list.depth);
if (after === void 0) {
return true;
}
const nodeAfter = tr.doc.nodeAt(after);
const canJoinForwards = list.node.type === (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.type) && canJoin(tr.doc, after);
if (!canJoinForwards) {
return true;
}
tr.join(after);
return true;
};
const toggleList = (listTypeOrName, itemTypeOrName) => ({ editor: editor2, tr, state, dispatch, chain, commands: commands2, can }) => {
const { extensions: extensions2 } = editor2.extensionManager;
const listType = getNodeType(listTypeOrName, state.schema);
const itemType = getNodeType(itemTypeOrName, state.schema);
const { selection } = state;
const { $from, $to } = selection;
const range = $from.blockRange($to);
if (!range) {
return false;
}
const parentList = findParentNode((node) => isList(node.type.name, extensions2))(selection);
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
if (parentList.node.type === listType) {
return commands2.liftListItem(itemType);
}
if (isList(parentList.node.type.name, extensions2) && listType.validContent(parentList.node.content) && dispatch) {
return chain().command(() => {
tr.setNodeMarkup(parentList.pos, listType);
return true;
}).command(() => joinListBackwards(tr, listType)).command(() => joinListForwards(tr, listType)).run();
}
}
return chain().command(() => {
const canWrapInList = can().wrapInList(listType);
if (canWrapInList) {
return true;
}
return commands2.clearNodes();
}).wrapInList(listType).command(() => joinListBackwards(tr, listType)).command(() => joinListForwards(tr, listType)).run();
};
const toggleMark = (typeOrName, attributes = {}, options = {}) => ({ state, commands: commands2 }) => {
const { extendEmptyMarkRange = false } = options;
const type = getMarkType(typeOrName, state.schema);
const isActive2 = isMarkActive(state, type, attributes);
if (isActive2) {
return commands2.unsetMark(type, { extendEmptyMarkRange });
}
return commands2.setMark(type, attributes);
};
const toggleNode = (typeOrName, toggleTypeOrName, attributes = {}) => ({ state, commands: commands2 }) => {
const type = getNodeType(typeOrName, state.schema);
const toggleType = getNodeType(toggleTypeOrName, state.schema);
const isActive2 = isNodeActive(state, type, attributes);
if (isActive2) {
return commands2.setNode(toggleType);
}
return commands2.setNode(type, attributes);
};
const toggleWrap = (typeOrName, attributes = {}) => ({ state, commands: commands2 }) => {
const type = getNodeType(typeOrName, state.schema);
const isActive2 = isNodeActive(state, type, attributes);
if (isActive2) {
return commands2.lift(type);
}
return commands2.wrapIn(type, attributes);
};
const undoInputRule = () => ({ state, dispatch }) => {
const plugins = state.plugins;
for (let i = 0; i < plugins.length; i += 1) {
const plugin = plugins[i];
let undoable;
if (plugin.spec.isInputRules && (undoable = plugin.getState(state))) {
if (dispatch) {
const tr = state.tr;
const toUndo = undoable.transform;
for (let j = toUndo.steps.length - 1; j >= 0; j -= 1) {
tr.step(toUndo.steps[j].invert(toUndo.docs[j]));
}
if (undoable.text) {
const marks = tr.doc.resolve(undoable.from).marks();
tr.replaceWith(undoable.from, undoable.to, state.schema.text(undoable.text, marks));
} else {
tr.delete(undoable.from, undoable.to);
}
}
return true;
}
}
return false;
};
const unsetAllMarks = () => ({ tr, dispatch }) => {
const { selection } = tr;
const { empty: empty2, ranges } = selection;
if (empty2) {
return true;
}
if (dispatch) {
ranges.forEach((range) => {
tr.removeMark(range.$from.pos, range.$to.pos);
});
}
return true;
};
const unsetMark = (typeOrName, options = {}) => ({ tr, state, dispatch }) => {
var _a;
const { extendEmptyMarkRange = false } = options;
const { selection } = tr;
const type = getMarkType(typeOrName, state.schema);
const { $from, empty: empty2, ranges } = selection;
if (!dispatch) {
return true;
}
if (empty2 && extendEmptyMarkRange) {
let { from: from2, to } = selection;
const attrs = (_a = $from.marks().find((mark) => mark.type === type)) === null || _a === void 0 ? void 0 : _a.attrs;
const range = getMarkRange($from, type, attrs);
if (range) {
from2 = range.from;
to = range.to;
}
tr.removeMark(from2, to, type);
} else {
ranges.forEach((range) => {
tr.removeMark(range.$from.pos, range.$to.pos, type);
});
}
tr.removeStoredMark(type);
return true;
};
const updateAttributes = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
let nodeType = null;
let markType = null;
const schemaType = getSchemaTypeNameByName(typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema);
if (!schemaType) {
return false;
}
if (schemaType === "node") {
nodeType = getNodeType(typeOrName, state.schema);
}
if (schemaType === "mark") {
markType = getMarkType(typeOrName, state.schema);
}
if (dispatch) {
tr.selection.ranges.forEach((range) => {
const from2 = range.$from.pos;
const to = range.$to.pos;
state.doc.nodesBetween(from2, to, (node, pos) => {
if (nodeType && nodeType === node.type) {
tr.setNodeMarkup(pos, void 0, {
...node.attrs,
...attributes
});
}
if (markType && node.marks.length) {
node.marks.forEach((mark) => {
if (markType === mark.type) {
const trimmedFrom = Math.max(pos, from2);
const trimmedTo = Math.min(pos + node.nodeSize, to);
tr.addMark(trimmedFrom, trimmedTo, markType.create({
...mark.attrs,
...attributes
}));
}
});
}
});
});
}
return true;
};
const wrapIn = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
return wrapIn$1(type, attributes)(state, dispatch);
};
const wrapInList = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
return wrapInList$1(type, attributes)(state, dispatch);
};
var commands = /* @__PURE__ */ Object.freeze({
__proto__: null,
blur,
clearContent,
clearNodes,
command,
createParagraphNear,
deleteCurrentNode,
deleteNode,
deleteRange,
deleteSelection,
enter,
exitCode,
extendMarkRange,
first,
focus,
forEach,
insertContent,
insertContentAt,
joinUp,
joinDown,
joinBackward,
joinForward,
keyboardShortcut,
lift,
liftEmptyBlock,
liftListItem,
newlineInCode,
resetAttributes,
scrollIntoView,
selectAll,
selectNodeBackward,
selectNodeForward,
selectParentNode,
selectTextblockEnd,
selectTextblockStart,
setContent,
setMark,
setMeta,
setNode,
setNodeSelection,
setTextSelection,
sinkListItem,
splitBlock,
splitListItem,
toggleList,
toggleMark,
toggleNode,
toggleWrap,
undoInputRule,
unsetAllMarks,
unsetMark,
updateAttributes,
wrapIn,
wrapInList
});
const Commands = Extension.create({
name: "commands",
addCommands() {
return {
...commands
};
}
});
const Editable = Extension.create({
name: "editable",
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey("editable"),
props: {
editable: () => this.editor.options.editable
}
})
];
}
});
const FocusEvents = Extension.create({
name: "focusEvents",
addProseMirrorPlugins() {
const { editor: editor2 } = this;
return [
new Plugin({
key: new PluginKey("focusEvents"),
props: {
handleDOMEvents: {
focus: (view, event) => {
editor2.isFocused = true;
const transaction = editor2.state.tr.setMeta("focus", { event }).setMeta("addToHistory", false);
view.dispatch(transaction);
return false;
},
blur: (view, event) => {
editor2.isFocused = false;
const transaction = editor2.state.tr.setMeta("blur", { event }).setMeta("addToHistory", false);
view.dispatch(transaction);
return false;
}
}
}
})
];
}
});
const Keymap = Extension.create({
name: "keymap",
addKeyboardShortcuts() {
const handleBackspace = () => this.editor.commands.first(({ commands: commands2 }) => [
() => commands2.undoInputRule(),
// maybe convert first text block node to default node
() => commands2.command(({ tr }) => {
const { selection, doc: doc2 } = tr;
const { empty: empty2, $anchor } = selection;
const { pos, parent } = $anchor;
const isAtStart = Selection.atStart(doc2).from === pos;
if (!empty2 || !isAtStart || !parent.type.isTextblock || parent.textContent.length) {
return false;
}
return commands2.clearNodes();
}),
() => commands2.deleteSelection(),
() => commands2.joinBackward(),
() => commands2.selectNodeBackward()
]);
const handleDelete = () => this.editor.commands.first(({ commands: commands2 }) => [
() => commands2.deleteSelection(),
() => commands2.deleteCurrentNode(),
() => commands2.joinForward(),
() => commands2.selectNodeForward()
]);
const handleEnter = () => this.editor.commands.first(({ commands: commands2 }) => [
() => commands2.newlineInCode(),
() => commands2.createParagraphNear(),
() => commands2.liftEmptyBlock(),
() => commands2.splitBlock()
]);
const baseKeymap = {
Enter: handleEnter,
"Mod-Enter": () => this.editor.commands.exitCode(),
Backspace: handleBackspace,
"Mod-Backspace": handleBackspace,
"Shift-Backspace": handleBackspace,
Delete: handleDelete,
"Mod-Delete": handleDelete,
"Mod-a": () => this.editor.commands.selectAll()
};
const pcKeymap = {
...baseKeymap
};
const macKeymap = {
...baseKeymap,
"Ctrl-h": handleBackspace,
"Alt-Backspace": handleBackspace,
"Ctrl-d": handleDelete,
"Ctrl-Alt-Backspace": handleDelete,
"Alt-Delete": handleDelete,
"Alt-d": handleDelete,
"Ctrl-a": () => this.editor.commands.selectTextblockStart(),
"Ctrl-e": () => this.editor.commands.selectTextblockEnd()
};
if (isiOS() || isMacOS()) {
return macKeymap;
}
return pcKeymap;
},
addProseMirrorPlugins() {
return [
// With this plugin we check if the whole document was selected and deleted.
// In this case we will additionally call `clearNodes()` to convert e.g. a heading
// to a paragraph if necessary.
// This is an alternative to ProseMirror's `AllSelection`, which doesn’t work well
// with many other commands.
new Plugin({
key: new PluginKey("clearDocument"),
appendTransaction: (transactions, oldState, newState) => {
const docChanges = transactions.some((transaction) => transaction.docChanged) && !oldState.doc.eq(newState.doc);
if (!docChanges) {
return;
}
const { empty: empty2, from: from2, to } = oldState.selection;
const allFrom = Selection.atStart(oldState.doc).from;
const allEnd = Selection.atEnd(oldState.doc).to;
const allWasSelected = from2 === allFrom && to === allEnd;
const isEmpty = newState.doc.textBetween(0, newState.doc.content.size, " ", " ").length === 0;
if (empty2 || !allWasSelected || !isEmpty) {
return;
}
const tr = newState.tr;
const state = createChainableState({
state: newState,
transaction: tr
});
const { commands: commands2 } = new CommandManager({
editor: this.editor,
state
});
commands2.clearNodes();
if (!tr.steps.length) {
return;
}
return tr;
}
})
];
}
});
const Tabindex = Extension.create({
name: "tabindex",
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey("tabindex"),
props: {
attributes: this.editor.isEditable ? { tabindex: "0" } : {}
}
})
];
}
});
var extensions = /* @__PURE__ */ Object.freeze({
__proto__: null,
ClipboardTextSerializer,
Commands,
Editable,
FocusEvents,
Keymap,
Tabindex
});
const style = `.ProseMirror {
position: relative;
}
.ProseMirror {
word-wrap: break-word;
white-space: pre-wrap;
white-space: break-spaces;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
}
.ProseMirror [contenteditable="false"] {
white-space: normal;
}
.ProseMirror [contenteditable="false"] [contenteditable="true"] {
white-space: pre-wrap;
}
.ProseMirror pre {
white-space: pre-wrap;
}
img.ProseMirror-separator {
display: inline !important;
border: none !important;
margin: 0 !important;
width: 1px !important;
height: 1px !important;
}
.ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
margin: 0;
}
.ProseMirror-gapcursor:after {
content: "";
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid black;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}
@keyframes ProseMirror-cursor-blink {
to {
visibility: hidden;
}
}
.ProseMirror-hideselection *::selection {
background: transparent;
}
.ProseMirror-hideselection *::-moz-selection {
background: transparent;
}
.ProseMirror-hideselection * {
caret-color: transparent;
}
.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
.tippy-box[data-animation=fade][data-state=hidden] {
opacity: 0
}`;
function createStyleTag(style2, nonce) {
const tipTapStyleTag = document.querySelector("style[data-tiptap-style]");
if (tipTapStyleTag !== null) {
return tipTapStyleTag;
}
const styleNode = document.createElement("style");
if (nonce) {
styleNode.setAttribute("nonce", nonce);
}
styleNode.setAttribute("data-tiptap-style", "");
styleNode.innerHTML = style2;
document.getElementsByTagName("head")[0].appendChild(styleNode);
return styleNode;
}
class Editor extends EventEmitter {
constructor(options = {}) {
super();
this.isFocused = false;
this.extensionStorage = {};
this.options = {
element: document.createElement("div"),
content: "",
injectCSS: true,
injectNonce: void 0,
extensions: [],
autofocus: false,
editable: true,
editorProps: {},
parseOptions: {},
enableInputRules: true,
enablePasteRules: true,
enableCoreExtensions: true,
onBeforeCreate: () => null,
onCreate: () => null,
onUpdate: () => null,
onSelectionUpdate: () => null,
onTransaction: () => null,
onFocus: () => null,
onBlur: () => null,
onDestroy: () => null
};
this.isCapturingTransaction = false;
this.capturedTransaction = null;
this.setOptions(options);
this.createExtensionManager();
this.createCommandManager();
this.createSchema();
this.on("beforeCreate", this.options.onBeforeCreate);
this.emit("beforeCreate", { editor: this });
this.createView();
this.injectCSS();
this.on("create", this.options.onCreate);
this.on("update", this.options.onUpdate);
this.on("selectionUpdate", this.options.onSelectionUpdate);
this.on("transaction", this.options.onTransaction);
this.on("focus", this.options.onFocus);
this.on("blur", this.options.onBlur);
this.on("destroy", this.options.onDestroy);
window.setTimeout(() => {
if (this.isDestroyed) {
return;
}
this.commands.focus(this.options.autofocus);
this.emit("create", { editor: this });
}, 0);
}
/**
* Returns the editor storage.
*/
get storage() {
return this.extensionStorage;
}
/**
* An object of all registered commands.
*/
get commands() {
return this.commandManager.commands;
}
/**
* Create a command chain to call multiple commands at once.
*/
chain() {
return this.commandManager.chain();
}
/**
* Check if a command or a command chain can be executed. Without executing it.
*/
can() {
return this.commandManager.can();
}
/**
* Inject CSS styles.
*/
injectCSS() {
if (this.options.injectCSS && document) {
this.css = createStyleTag(style, this.options.injectNonce);
}
}
/**
* Update editor options.
*
* @param options A list of options
*/
setOptions(options = {}) {
this.options = {
...this.options,
...options
};
if (!this.view || !this.state || this.isDestroyed) {
return;
}
if (this.options.editorProps) {
this.view.setProps(this.options.editorProps);
}
this.view.updateState(this.state);
}
/**
* Update editable state of the editor.
*/
setEditable(editable, emitUpdate = true) {
this.setOptions({ editable });
if (emitUpdate) {
this.emit("update", { editor: this, transaction: this.state.tr });
}
}
/**
* Returns whether the editor is editable.
*/
get isEditable() {
return this.options.editable && this.view && this.view.editable;
}
/**
* Returns the editor state.
*/
get state() {
return this.view.state;
}
/**
* Register a ProseMirror plugin.
*
* @param plugin A ProseMirror plugin
* @param handlePlugins Control how to merge the plugin into the existing plugins.
*/
registerPlugin(plugin, handlePlugins) {
const plugins = isFunction(handlePlugins) ? handlePlugins(plugin, [...this.state.plugins]) : [...this.state.plugins, plugin];
const state = this.state.reconfigure({ plugins });
this.view.updateState(state);
}
/**
* Unregister a ProseMirror plugin.
*
* @param nameOrPluginKey The plugins name
*/
unregisterPlugin(nameOrPluginKey) {
if (this.isDestroyed) {
return;
}
const name = typeof nameOrPluginKey === "string" ? `${nameOrPluginKey}$` : nameOrPluginKey.key;
const state = this.state.reconfigure({
// @ts-ignore
plugins: this.state.plugins.filter((plugin) => !plugin.key.startsWith(name))
});
this.view.updateState(state);
}
/**
* Creates an extension manager.
*/
createExtensionManager() {
const coreExtensions = this.options.enableCoreExtensions ? Object.values(extensions) : [];
const allExtensions = [...coreExtensions, ...this.options.extensions].filter((extension) => {
return ["extension", "node", "mark"].includes(extension === null || extension === void 0 ? void 0 : extension.type);
});
this.extensionManager = new ExtensionManager(allExtensions, this);
}
/**
* Creates an command manager.
*/
createCommandManager() {
this.commandManager = new CommandManager({
editor: this
});
}
/**
* Creates a ProseMirror schema.
*/
createSchema() {
this.schema = this.extensionManager.schema;
}
/**
* Creates a ProseMirror view.
*/
createView() {
const doc2 = createDocument(this.options.content, this.schema, this.options.parseOptions);
const selection = resolveFocusPosition(doc2, this.options.autofocus);
this.view = new EditorView(this.options.element, {
...this.options.editorProps,
dispatchTransaction: this.dispatchTransaction.bind(this),
state: EditorState.create({
doc: doc2,
selection: selection || void 0
})
});
const newState = this.state.reconfigure({
plugins: this.extensionManager.plugins
});
this.view.updateState(newState);
this.createNodeViews();
const dom = this.view.dom;
dom.editor = this;
}
/**
* Creates all node views.
*/
createNodeViews() {
this.view.setProps({
nodeViews: this.extensionManager.nodeViews
});
}
captureTransaction(fn) {
this.isCapturingTransaction = true;
fn();
this.isCapturingTransaction = false;
const tr = this.capturedTransaction;
this.capturedTransaction = null;
return tr;
}
/**
* The callback over which to send transactions (state updates) produced by the view.
*
* @param transaction An editor state transaction
*/
dispatchTransaction(transaction) {
if (this.isCapturingTransaction) {
if (!this.capturedTransaction) {
this.capturedTransaction = transaction;
return;
}
transaction.steps.forEach((step) => {
var _a;
return (_a = this.capturedTransaction) === null || _a === void 0 ? void 0 : _a.step(step);
});
return;
}
const state = this.state.apply(transaction);
const selectionHasChanged = !this.state.selection.eq(state.selection);
this.view.updateState(state);
this.emit("transaction", {
editor: this,
transaction
});
if (selectionHasChanged) {
this.emit("selectionUpdate", {
editor: this,
transaction
});
}
const focus2 = transaction.getMeta("focus");
const blur2 = transaction.getMeta("blur");
if (focus2) {
this.emit("focus", {
editor: this,
event: focus2.event,
transaction
});
}
if (blur2) {
this.emit("blur", {
editor: this,
event: blur2.event,
transaction
});
}
if (!transaction.docChanged || transaction.getMeta("preventUpdate")) {
return;
}
this.emit("update", {
editor: this,
transaction
});
}
/**
* Get attributes of the currently selected node or mark.
*/
getAttributes(nameOrType) {
return getAttributes(this.state, nameOrType);
}
isActive(nameOrAttributes, attributesOrUndefined) {
const name = typeof nameOrAttributes === "string" ? nameOrAttributes : null;
const attributes = typeof nameOrAttributes === "string" ? attributesOrUndefined : nameOrAttributes;
return isActive(this.state, name, attributes);
}
/**
* Get the document as JSON.
*/
getJSON() {
return this.state.doc.toJSON();
}
/**
* Get the document as HTML.
*/
getHTML() {
return getHTMLFromFragment(this.state.doc.content, this.schema);
}
/**
* Get the document as text.
*/
getText(options) {
const { blockSeparator = "\n\n", textSerializers = {} } = options || {};
return getText(this.state.doc, {
blockSeparator,
textSerializers: {
...textSerializers,
...getTextSerializersFromSchema(this.schema)
}
});
}
/**
* Check if there is no content.
*/
get isEmpty() {
return isNodeEmpty(this.state.doc);
}
/**
* Get the number of characters for the current document.
*
* @deprecated
*/
getCharacterCount() {
console.warn('[tiptap warn]: "editor.getCharacterCount()" is deprecated. Please use "editor.storage.characterCount.characters()" instead.');
return this.state.doc.content.size - 2;
}
/**
* Destroy the editor.
*/
destroy() {
this.emit("destroy");
if (this.view) {
this.view.destroy();
}
this.removeAllListeners();
}
/**
* Check if the editor is already destroyed.
*/
get isDestroyed() {
var _a;
return !((_a = this.view) === null || _a === void 0 ? void 0 : _a.docView);
}
}
function markInputRule(config) {
return new InputRule({
find: config.find,
handler: ({ state, range, match }) => {
const attributes = callOrReturn(config.getAttributes, void 0, match);
if (attributes === false || attributes === null) {
return null;
}
const { tr } = state;
const captureGroup = match[match.length - 1];
const fullMatch = match[0];
let markEnd = range.to;
if (captureGroup) {
const startSpaces = fullMatch.search(/\S/);
const textStart = range.from + fullMatch.indexOf(captureGroup);
const textEnd = textStart + captureGroup.length;
const excludedMarks = getMarksBetween(range.from, range.to, state.doc).filter((item) => {
const excluded = item.mark.type.excluded;
return excluded.find((type) => type === config.type && type !== item.mark.type);
}).filter((item) => item.to > textStart);
if (excludedMarks.length) {
return null;
}
if (textEnd < range.to) {
tr.delete(textEnd, range.to);
}
if (textStart > range.from) {
tr.delete(range.from + startSpaces, textStart);
}
markEnd = range.from + startSpaces + captureGroup.length;
tr.addMark(range.from + startSpaces, markEnd, config.type.create(attributes || {}));
tr.removeStoredMark(config.type);
}
}
});
}
function nodeInputRule(config) {
return new InputRule({
find: config.find,
handler: ({ state, range, match }) => {
const attributes = callOrReturn(config.getAttributes, void 0, match) || {};
const { tr } = state;
const start = range.from;
let end = range.to;
if (match[1]) {
const offset = match[0].lastIndexOf(match[1]);
let matchStart = start + offset;
if (matchStart > end) {
matchStart = end;
} else {
end = matchStart + match[1].length;
}
const lastChar = match[0][match[0].length - 1];
tr.insertText(lastChar, start + match[0].length - 1);
tr.replaceWith(matchStart, end, config.type.create(attributes));
} else if (match[0]) {
tr.replaceWith(start, end, config.type.create(attributes));
}
}
});
}
function textblockTypeInputRule(config) {
return new InputRule({
find: config.find,
handler: ({ state, range, match }) => {
const $start = state.doc.resolve(range.from);
const attributes = callOrReturn(config.getAttributes, void 0, match) || {};
if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), config.type)) {
return null;
}
state.tr.delete(range.from, range.to).setBlockType(range.from, range.from, config.type, attributes);
}
});
}
function wrappingInputRule(config) {
return new InputRule({
find: config.find,
handler: ({ state, range, match }) => {
const attributes = callOrReturn(config.getAttributes, void 0, match) || {};
const tr = state.tr.delete(range.from, range.to);
const $start = tr.doc.resolve(range.from);
const blockRange = $start.blockRange();
const wrapping = blockRange && findWrapping(blockRange, config.type, attributes);
if (!wrapping) {
return null;
}
tr.wrap(blockRange, wrapping);
const before = tr.doc.resolve(range.from - 1).nodeBefore;
if (before && before.type === config.type && canJoin(tr.doc, range.from - 1) && (!config.joinPredicate || config.joinPredicate(match, before))) {
tr.join(range.from - 1);
}
}
});
}
class Mark2 {
constructor(config = {}) {
this.type = "mark";
this.name = "mark";
this.parent = null;
this.child = null;
this.config = {
name: this.name,
defaultOptions: {}
};
this.config = {
...this.config,
...config
};
this.name = this.config.name;
if (config.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`);
}
this.options = this.config.defaultOptions;
if (this.config.addOptions) {
this.options = callOrReturn(getExtensionField(this, "addOptions", {
name: this.name
}));
}
this.storage = callOrReturn(getExtensionField(this, "addStorage", {
name: this.name,
options: this.options
})) || {};
}
static create(config = {}) {
return new Mark2(config);
}
configure(options = {}) {
const extension = this.extend();
extension.options = mergeDeep(this.options, options);
extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
name: extension.name,
options: extension.options
}));
return extension;
}
extend(extendedConfig = {}) {
const extension = new Mark2(extendedConfig);
extension.parent = this;
this.child = extension;
extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name;
if (extendedConfig.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`);
}
extension.options = callOrReturn(getExtensionField(extension, "addOptions", {
name: extension.name
}));
extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
name: extension.name,
options: extension.options
}));
return extension;
}
static handleExit({ editor: editor2, mark }) {
const { tr } = editor2.state;
const currentPos = editor2.state.selection.$from;
const isAtEnd = currentPos.pos === currentPos.end();
if (isAtEnd) {
const currentMarks = currentPos.marks();
const isInMark = !!currentMarks.find((m) => (m === null || m === void 0 ? void 0 : m.type.name) === mark.name);
if (!isInMark) {
return false;
}
const removeMark2 = currentMarks.find((m) => (m === null || m === void 0 ? void 0 : m.type.name) === mark.name);
if (removeMark2) {
tr.removeStoredMark(removeMark2);
}
tr.insertText(" ", currentPos.pos);
editor2.view.dispatch(tr);
return true;
}
return false;
}
}
class Node2 {
constructor(config = {}) {
this.type = "node";
this.name = "node";
this.parent = null;
this.child = null;
this.config = {
name: this.name,
defaultOptions: {}
};
this.config = {
...this.config,
...config
};
this.name = this.config.name;
if (config.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`);
}
this.options = this.config.defaultOptions;
if (this.config.addOptions) {
this.options = callOrReturn(getExtensionField(this, "addOptions", {
name: this.name
}));
}
this.storage = callOrReturn(getExtensionField(this, "addStorage", {
name: this.name,
options: this.options
})) || {};
}
static create(config = {}) {
return new Node2(config);
}
configure(options = {}) {
const extension = this.extend();
extension.options = mergeDeep(this.options, options);
extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
name: extension.name,
options: extension.options
}));
return extension;
}
extend(extendedConfig = {}) {
const extension = new Node2(extendedConfig);
extension.parent = this;
this.child = extension;
extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name;
if (extendedConfig.defaultOptions) {
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`);
}
extension.options = callOrReturn(getExtensionField(extension, "addOptions", {
name: extension.name
}));
extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
name: extension.name,
options: extension.options
}));
return extension;
}
}
function markPasteRule(config) {
return new PasteRule({
find: config.find,
handler: ({ state, range, match }) => {
const attributes = callOrReturn(config.getAttributes, void 0, match);
if (attributes === false || attributes === null) {
return null;
}
const { tr } = state;
const captureGroup = match[match.length - 1];
const fullMatch = match[0];
let markEnd = range.to;
if (captureGroup) {
const startSpaces = fullMatch.search(/\S/);
const textStart = range.from + fullMatch.indexOf(captureGroup);
const textEnd = textStart + captureGroup.length;
const excludedMarks = getMarksBetween(range.from, range.to, state.doc).filter((item) => {
const excluded = item.mark.type.excluded;
return excluded.find((type) => type === config.type && type !== item.mark.type);
}).filter((item) => item.to > textStart);
if (excludedMarks.length) {
return null;
}
if (textEnd < range.to) {
tr.delete(textEnd, range.to);
}
if (textStart > range.from) {
tr.delete(range.from + startSpaces, textStart);
}
markEnd = range.from + startSpaces + captureGroup.length;
tr.addMark(range.from + startSpaces, markEnd, config.type.create(attributes || {}));
tr.removeStoredMark(config.type);
}
}
});
}
const inputRegex$4 = /^\s*>\s$/;
const Blockquote = Node2.create({
name: "blockquote",
addOptions() {
return {
HTMLAttributes: {}
};
},
content: "block+",
group: "block",
defining: true,
parseHTML() {
return [
{ tag: "blockquote" }
];
},
renderHTML({ HTMLAttributes }) {
return ["blockquote", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setBlockquote: () => ({ commands: commands2 }) => {
return commands2.wrapIn(this.name);
},
toggleBlockquote: () => ({ commands: commands2 }) => {
return commands2.toggleWrap(this.name);
},
unsetBlockquote: () => ({ commands: commands2 }) => {
return commands2.lift(this.name);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Shift-b": () => this.editor.commands.toggleBlockquote()
};
},
addInputRules() {
return [
wrappingInputRule({
find: inputRegex$4,
type: this.type
})
];
}
});
const starInputRegex$1 = /(?:^|\s)((?:\*\*)((?:[^*]+))(?:\*\*))$/;
const starPasteRegex$1 = /(?:^|\s)((?:\*\*)((?:[^*]+))(?:\*\*))/g;
const underscoreInputRegex$1 = /(?:^|\s)((?:__)((?:[^__]+))(?:__))$/;
const underscorePasteRegex$1 = /(?:^|\s)((?:__)((?:[^__]+))(?:__))/g;
const Bold = Mark2.create({
name: "bold",
addOptions() {
return {
HTMLAttributes: {}
};
},
parseHTML() {
return [
{
tag: "strong"
},
{
tag: "b",
getAttrs: (node) => node.style.fontWeight !== "normal" && null
},
{
style: "font-weight",
getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
}
];
},
renderHTML({ HTMLAttributes }) {
return ["strong", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setBold: () => ({ commands: commands2 }) => {
return commands2.setMark(this.name);
},
toggleBold: () => ({ commands: commands2 }) => {
return commands2.toggleMark(this.name);
},
unsetBold: () => ({ commands: commands2 }) => {
return commands2.unsetMark(this.name);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-b": () => this.editor.commands.toggleBold(),
"Mod-B": () => this.editor.commands.toggleBold()
};
},
addInputRules() {
return [
markInputRule({
find: starInputRegex$1,
type: this.type
}),
markInputRule({
find: underscoreInputRegex$1,
type: this.type
})
];
},
addPasteRules() {
return [
markPasteRule({
find: starPasteRegex$1,
type: this.type
}),
markPasteRule({
find: underscorePasteRegex$1,
type: this.type
})
];
}
});
const inputRegex$3 = /^\s*([-+*])\s$/;
const BulletList = Node2.create({
name: "bulletList",
addOptions() {
return {
itemTypeName: "listItem",
HTMLAttributes: {}
};
},
group: "block list",
content() {
return `${this.options.itemTypeName}+`;
},
parseHTML() {
return [
{ tag: "ul" }
];
},
renderHTML({ HTMLAttributes }) {
return ["ul", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
toggleBulletList: () => ({ commands: commands2 }) => {
return commands2.toggleList(this.name, this.options.itemTypeName);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Shift-8": () => this.editor.commands.toggleBulletList()
};
},
addInputRules() {
return [
wrappingInputRule({
find: inputRegex$3,
type: this.type
})
];
}
});
const inputRegex$2 = /(?:^|\s)((?:`)((?:[^`]+))(?:`))$/;
const pasteRegex$1 = /(?:^|\s)((?:`)((?:[^`]+))(?:`))/g;
const Code = Mark2.create({
name: "code",
addOptions() {
return {
HTMLAttributes: {}
};
},
excludes: "_",
code: true,
exitable: true,
parseHTML() {
return [
{ tag: "code" }
];
},
renderHTML({ HTMLAttributes }) {
return ["code", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setCode: () => ({ commands: commands2 }) => {
return commands2.setMark(this.name);
},
toggleCode: () => ({ commands: commands2 }) => {
return commands2.toggleMark(this.name);
},
unsetCode: () => ({ commands: commands2 }) => {
return commands2.unsetMark(this.name);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-e": () => this.editor.commands.toggleCode()
};
},
addInputRules() {
return [
markInputRule({
find: inputRegex$2,
type: this.type
})
];
},
addPasteRules() {
return [
markPasteRule({
find: pasteRegex$1,
type: this.type
})
];
}
});
const backtickInputRegex = /^```([a-z]+)?[\s\n]$/;
const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/;
const CodeBlock = Node2.create({
name: "codeBlock",
addOptions() {
return {
languageClassPrefix: "language-",
exitOnTripleEnter: true,
exitOnArrowDown: true,
HTMLAttributes: {}
};
},
content: "text*",
marks: "",
group: "block",
code: true,
defining: true,
addAttributes() {
return {
language: {
default: null,
parseHTML: (element) => {
var _a;
const { languageClassPrefix } = this.options;
const classNames = [...((_a = element.firstElementChild) === null || _a === void 0 ? void 0 : _a.classList) || []];
const languages = classNames.filter((className) => className.startsWith(languageClassPrefix)).map((className) => className.replace(languageClassPrefix, ""));
const language = languages[0];
if (!language) {
return null;
}
return language;
},
rendered: false
}
};
},
parseHTML() {
return [
{
tag: "pre",
preserveWhitespace: "full"
}
];
},
renderHTML({ node, HTMLAttributes }) {
return [
"pre",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
[
"code",
{
class: node.attrs.language ? this.options.languageClassPrefix + node.attrs.language : null
},
0
]
];
},
addCommands() {
return {
setCodeBlock: (attributes) => ({ commands: commands2 }) => {
return commands2.setNode(this.name, attributes);
},
toggleCodeBlock: (attributes) => ({ commands: commands2 }) => {
return commands2.toggleNode(this.name, "paragraph", attributes);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Alt-c": () => this.editor.commands.toggleCodeBlock(),
// remove code block when at start of document or code block is empty
Backspace: () => {
const { empty: empty2, $anchor } = this.editor.state.selection;
const isAtStart = $anchor.pos === 1;
if (!empty2 || $anchor.parent.type.name !== this.name) {
return false;
}
if (isAtStart || !$anchor.parent.textContent.length) {
return this.editor.commands.clearNodes();
}
return false;
},
// exit node on triple enter
Enter: ({ editor: editor2 }) => {
if (!this.options.exitOnTripleEnter) {
return false;
}
const { state } = editor2;
const { selection } = state;
const { $from, empty: empty2 } = selection;
if (!empty2 || $from.parent.type !== this.type) {
return false;
}
const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
const endsWithDoubleNewline = $from.parent.textContent.endsWith("\n\n");
if (!isAtEnd || !endsWithDoubleNewline) {
return false;
}
return editor2.chain().command(({ tr }) => {
tr.delete($from.pos - 2, $from.pos);
return true;
}).exitCode().run();
},
// exit node on arrow down
ArrowDown: ({ editor: editor2 }) => {
if (!this.options.exitOnArrowDown) {
return false;
}
const { state } = editor2;
const { selection, doc: doc2 } = state;
const { $from, empty: empty2 } = selection;
if (!empty2 || $from.parent.type !== this.type) {
return false;
}
const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
if (!isAtEnd) {
return false;
}
const after = $from.after();
if (after === void 0) {
return false;
}
const nodeAfter = doc2.nodeAt(after);
if (nodeAfter) {
return false;
}
return editor2.commands.exitCode();
}
};
},
addInputRules() {
return [
textblockTypeInputRule({
find: backtickInputRegex,
type: this.type,
getAttributes: (match) => ({
language: match[1]
})
}),
textblockTypeInputRule({
find: tildeInputRegex,
type: this.type,
getAttributes: (match) => ({
language: match[1]
})
})
];
},
addProseMirrorPlugins() {
return [
// this plugin creates a code block for pasted content from VS Code
// we can also detect the copied code language
new Plugin({
key: new PluginKey("codeBlockVSCodeHandler"),
props: {
handlePaste: (view, event) => {
if (!event.clipboardData) {
return false;
}
if (this.editor.isActive(this.type.name)) {
return false;
}
const text2 = event.clipboardData.getData("text/plain");
const vscode = event.clipboardData.getData("vscode-editor-data");
const vscodeData = vscode ? JSON.parse(vscode) : void 0;
const language = vscodeData === null || vscodeData === void 0 ? void 0 : vscodeData.mode;
if (!text2 || !language) {
return false;
}
const { tr } = view.state;
tr.replaceSelectionWith(this.type.create({ language }));
tr.setSelection(TextSelection.near(tr.doc.resolve(Math.max(0, tr.selection.from - 2))));
tr.insertText(text2.replace(/\r\n?/g, "\n"));
tr.setMeta("paste", true);
view.dispatch(tr);
return true;
}
}
})
];
}
});
const Document = Node2.create({
name: "doc",
topNode: true,
content: "block+"
});
function dropCursor(options = {}) {
return new Plugin({
view(editorView) {
return new DropCursorView(editorView, options);
}
});
}
class DropCursorView {
constructor(editorView, options) {
this.editorView = editorView;
this.cursorPos = null;
this.element = null;
this.timeout = -1;
this.width = options.width || 1;
this.color = options.color || "black";
this.class = options.class;
this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((name) => {
let handler = (e) => {
this[name](e);
};
editorView.dom.addEventListener(name, handler);
return { name, handler };
});
}
destroy() {
this.handlers.forEach(({ name, handler }) => this.editorView.dom.removeEventListener(name, handler));
}
update(editorView, prevState) {
if (this.cursorPos != null && prevState.doc != editorView.state.doc) {
if (this.cursorPos > editorView.state.doc.content.size)
this.setCursor(null);
else
this.updateOverlay();
}
}
setCursor(pos) {
if (pos == this.cursorPos)
return;
this.cursorPos = pos;
if (pos == null) {
this.element.parentNode.removeChild(this.element);
this.element = null;
} else {
this.updateOverlay();
}
}
updateOverlay() {
let $pos = this.editorView.state.doc.resolve(this.cursorPos);
let isBlock = !$pos.parent.inlineContent, rect;
if (isBlock) {
let before = $pos.nodeBefore, after = $pos.nodeAfter;
if (before || after) {
let node = this.editorView.nodeDOM(this.cursorPos - (before ? before.nodeSize : 0));
if (node) {
let nodeRect = node.getBoundingClientRect();
let top = before ? nodeRect.bottom : nodeRect.top;
if (before && after)
top = (top + this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top) / 2;
rect = { left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2 };
}
}
}
if (!rect) {
let coords = this.editorView.coordsAtPos(this.cursorPos);
rect = { left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom };
}
let parent = this.editorView.dom.offsetParent;
if (!this.element) {
this.element = parent.appendChild(document.createElement("div"));
if (this.class)
this.element.className = this.class;
this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none; background-color: " + this.color;
}
this.element.classList.toggle("prosemirror-dropcursor-block", isBlock);
this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock);
let parentLeft, parentTop;
if (!parent || parent == document.body && getComputedStyle(parent).position == "static") {
parentLeft = -pageXOffset;
parentTop = -pageYOffset;
} else {
let rect2 = parent.getBoundingClientRect();
parentLeft = rect2.left - parent.scrollLeft;
parentTop = rect2.top - parent.scrollTop;
}
this.element.style.left = rect.left - parentLeft + "px";
this.element.style.top = rect.top - parentTop + "px";
this.element.style.width = rect.right - rect.left + "px";
this.element.style.height = rect.bottom - rect.top + "px";
}
scheduleRemoval(timeout) {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => this.setCursor(null), timeout);
}
dragover(event) {
if (!this.editorView.editable)
return;
let pos = this.editorView.posAtCoords({ left: event.clientX, top: event.clientY });
let node = pos && pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside);
let disableDropCursor = node && node.type.spec.disableDropCursor;
let disabled = typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor;
if (pos && !disabled) {
let target = pos.pos;
if (this.editorView.dragging && this.editorView.dragging.slice) {
target = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice);
if (target == null)
return this.setCursor(null);
}
this.setCursor(target);
this.scheduleRemoval(5e3);
}
}
dragend() {
this.scheduleRemoval(20);
}
drop() {
this.scheduleRemoval(20);
}
dragleave(event) {
if (event.target == this.editorView.dom || !this.editorView.dom.contains(event.relatedTarget))
this.setCursor(null);
}
}
const Dropcursor = Extension.create({
name: "dropCursor",
addOptions() {
return {
color: "currentColor",
width: 1,
class: void 0
};
},
addProseMirrorPlugins() {
return [
dropCursor(this.options)
];
}
});
class GapCursor extends Selection {
/**
Create a gap cursor.
*/
constructor($pos) {
super($pos, $pos);
}
map(doc2, mapping) {
let $pos = doc2.resolve(mapping.map(this.head));
return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos);
}
content() {
return Slice.empty;
}
eq(other) {
return other instanceof GapCursor && other.head == this.head;
}
toJSON() {
return { type: "gapcursor", pos: this.head };
}
/**
@internal
*/
static fromJSON(doc2, json) {
if (typeof json.pos != "number")
throw new RangeError("Invalid input for GapCursor.fromJSON");
return new GapCursor(doc2.resolve(json.pos));
}
/**
@internal
*/
getBookmark() {
return new GapBookmark(this.anchor);
}
/**
@internal
*/
static valid($pos) {
let parent = $pos.parent;
if (parent.isTextblock || !closedBefore($pos) || !closedAfter($pos))
return false;
let override = parent.type.spec.allowGapCursor;
if (override != null)
return override;
let deflt = parent.contentMatchAt($pos.index()).defaultType;
return deflt && deflt.isTextblock;
}
/**
@internal
*/
static findGapCursorFrom($pos, dir, mustMove = false) {
search:
for (; ; ) {
if (!mustMove && GapCursor.valid($pos))
return $pos;
let pos = $pos.pos, next = null;
for (let d = $pos.depth; ; d--) {
let parent = $pos.node(d);
if (dir > 0 ? $pos.indexAfter(d) < parent.childCount : $pos.index(d) > 0) {
next = parent.child(dir > 0 ? $pos.indexAfter(d) : $pos.index(d) - 1);
break;
} else if (d == 0) {
return null;
}
pos += dir;
let $cur = $pos.doc.resolve(pos);
if (GapCursor.valid($cur))
return $cur;
}
for (; ; ) {
let inside = dir > 0 ? next.firstChild : next.lastChild;
if (!inside) {
if (next.isAtom && !next.isText && !NodeSelection.isSelectable(next)) {
$pos = $pos.doc.resolve(pos + next.nodeSize * dir);
mustMove = false;
continue search;
}
break;
}
next = inside;
pos += dir;
let $cur = $pos.doc.resolve(pos);
if (GapCursor.valid($cur))
return $cur;
}
return null;
}
}
}
GapCursor.prototype.visible = false;
GapCursor.findFrom = GapCursor.findGapCursorFrom;
Selection.jsonID("gapcursor", GapCursor);
class GapBookmark {
constructor(pos) {
this.pos = pos;
}
map(mapping) {
return new GapBookmark(mapping.map(this.pos));
}
resolve(doc2) {
let $pos = doc2.resolve(this.pos);
return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos);
}
}
function closedBefore($pos) {
for (let d = $pos.depth; d >= 0; d--) {
let index = $pos.index(d), parent = $pos.node(d);
if (index == 0) {
if (parent.type.spec.isolating)
return true;
continue;
}
for (let before = parent.child(index - 1); ; before = before.lastChild) {
if (before.childCount == 0 && !before.inlineContent || before.isAtom || before.type.spec.isolating)
return true;
if (before.inlineContent)
return false;
}
}
return true;
}
function closedAfter($pos) {
for (let d = $pos.depth; d >= 0; d--) {
let index = $pos.indexAfter(d), parent = $pos.node(d);
if (index == parent.childCount) {
if (parent.type.spec.isolating)
return true;
continue;
}
for (let after = parent.child(index); ; after = after.firstChild) {
if (after.childCount == 0 && !after.inlineContent || after.isAtom || after.type.spec.isolating)
return true;
if (after.inlineContent)
return false;
}
}
return true;
}
function gapCursor() {
return new Plugin({
props: {
decorations: drawGapCursor,
createSelectionBetween(_view, $anchor, $head) {
return $anchor.pos == $head.pos && GapCursor.valid($head) ? new GapCursor($head) : null;
},
handleClick,
handleKeyDown,
handleDOMEvents: { beforeinput }
}
});
}
const handleKeyDown = keydownHandler({
"ArrowLeft": arrow("horiz", -1),
"ArrowRight": arrow("horiz", 1),
"ArrowUp": arrow("vert", -1),
"ArrowDown": arrow("vert", 1)
});
function arrow(axis, dir) {
const dirStr = axis == "vert" ? dir > 0 ? "down" : "up" : dir > 0 ? "right" : "left";
return function(state, dispatch, view) {
let sel = state.selection;
let $start = dir > 0 ? sel.$to : sel.$from, mustMove = sel.empty;
if (sel instanceof TextSelection) {
if (!view.endOfTextblock(dirStr) || $start.depth == 0)
return false;
mustMove = false;
$start = state.doc.resolve(dir > 0 ? $start.after() : $start.before());
}
let $found = GapCursor.findGapCursorFrom($start, dir, mustMove);
if (!$found)
return false;
if (dispatch)
dispatch(state.tr.setSelection(new GapCursor($found)));
return true;
};
}
function handleClick(view, pos, event) {
if (!view || !view.editable)
return false;
let $pos = view.state.doc.resolve(pos);
if (!GapCursor.valid($pos))
return false;
let clickPos = view.posAtCoords({ left: event.clientX, top: event.clientY });
if (clickPos && clickPos.inside > -1 && NodeSelection.isSelectable(view.state.doc.nodeAt(clickPos.inside)))
return false;
view.dispatch(view.state.tr.setSelection(new GapCursor($pos)));
return true;
}
function beforeinput(view, event) {
if (event.inputType != "insertCompositionText" || !(view.state.selection instanceof GapCursor))
return false;
let { $from } = view.state.selection;
let insert = $from.parent.contentMatchAt($from.index()).findWrapping(view.state.schema.nodes.text);
if (!insert)
return false;
let frag = Fragment.empty;
for (let i = insert.length - 1; i >= 0; i--)
frag = Fragment.from(insert[i].createAndFill(null, frag));
let tr = view.state.tr.replace($from.pos, $from.pos, new Slice(frag, 0, 0));
tr.setSelection(TextSelection.near(tr.doc.resolve($from.pos + 1)));
view.dispatch(tr);
return false;
}
function drawGapCursor(state) {
if (!(state.selection instanceof GapCursor))
return null;
let node = document.createElement("div");
node.className = "ProseMirror-gapcursor";
return DecorationSet.create(state.doc, [Decoration.widget(state.selection.head, node, { key: "gapcursor" })]);
}
const Gapcursor = Extension.create({
name: "gapCursor",
addProseMirrorPlugins() {
return [
gapCursor()
];
},
extendNodeSchema(extension) {
var _a;
const context = {
name: extension.name,
options: extension.options,
storage: extension.storage
};
return {
allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) !== null && _a !== void 0 ? _a : null
};
}
});
const HardBreak = Node2.create({
name: "hardBreak",
addOptions() {
return {
keepMarks: true,
HTMLAttributes: {}
};
},
inline: true,
group: "inline",
selectable: false,
parseHTML() {
return [
{ tag: "br" }
];
},
renderHTML({ HTMLAttributes }) {
return ["br", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
},
renderText() {
return "\n";
},
addCommands() {
return {
setHardBreak: () => ({ commands: commands2, chain, state, editor: editor2 }) => {
return commands2.first([
() => commands2.exitCode(),
() => commands2.command(() => {
const { selection, storedMarks } = state;
if (selection.$from.parent.type.spec.isolating) {
return false;
}
const { keepMarks } = this.options;
const { splittableMarks } = editor2.extensionManager;
const marks = storedMarks || selection.$to.parentOffset && selection.$from.marks();
return chain().insertContent({ type: this.name }).command(({ tr, dispatch }) => {
if (dispatch && marks && keepMarks) {
const filteredMarks = marks.filter((mark) => splittableMarks.includes(mark.type.name));
tr.ensureMarks(filteredMarks);
}
return true;
}).run();
})
]);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Enter": () => this.editor.commands.setHardBreak(),
"Shift-Enter": () => this.editor.commands.setHardBreak()
};
}
});
const Heading = Node2.create({
name: "heading",
addOptions() {
return {
levels: [1, 2, 3, 4, 5, 6],
HTMLAttributes: {}
};
},
content: "inline*",
group: "block",
defining: true,
addAttributes() {
return {
level: {
default: 1,
rendered: false
}
};
},
parseHTML() {
return this.options.levels.map((level) => ({
tag: `h${level}`,
attrs: { level }
}));
},
renderHTML({ node, HTMLAttributes }) {
const hasLevel = this.options.levels.includes(node.attrs.level);
const level = hasLevel ? node.attrs.level : this.options.levels[0];
return [`h${level}`, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setHeading: (attributes) => ({ commands: commands2 }) => {
if (!this.options.levels.includes(attributes.level)) {
return false;
}
return commands2.setNode(this.name, attributes);
},
toggleHeading: (attributes) => ({ commands: commands2 }) => {
if (!this.options.levels.includes(attributes.level)) {
return false;
}
return commands2.toggleNode(this.name, "paragraph", attributes);
}
};
},
addKeyboardShortcuts() {
return this.options.levels.reduce((items, level) => ({
...items,
...{
[`Mod-Alt-${level}`]: () => this.editor.commands.toggleHeading({ level })
}
}), {});
},
addInputRules() {
return this.options.levels.map((level) => {
return textblockTypeInputRule({
find: new RegExp(`^(#{1,${level}})\\s$`),
type: this.type,
getAttributes: {
level
}
});
});
}
});
var GOOD_LEAF_SIZE = 200;
var RopeSequence = function RopeSequence2() {
};
RopeSequence.prototype.append = function append(other) {
if (!other.length) {
return this;
}
other = RopeSequence.from(other);
return !this.length && other || other.length < GOOD_LEAF_SIZE && this.leafAppend(other) || this.length < GOOD_LEAF_SIZE && other.leafPrepend(this) || this.appendInner(other);
};
RopeSequence.prototype.prepend = function prepend(other) {
if (!other.length) {
return this;
}
return RopeSequence.from(other).append(this);
};
RopeSequence.prototype.appendInner = function appendInner(other) {
return new Append(this, other);
};
RopeSequence.prototype.slice = function slice(from2, to) {
if (from2 === void 0)
from2 = 0;
if (to === void 0)
to = this.length;
if (from2 >= to) {
return RopeSequence.empty;
}
return this.sliceInner(Math.max(0, from2), Math.min(this.length, to));
};
RopeSequence.prototype.get = function get(i) {
if (i < 0 || i >= this.length) {
return void 0;
}
return this.getInner(i);
};
RopeSequence.prototype.forEach = function forEach2(f, from2, to) {
if (from2 === void 0)
from2 = 0;
if (to === void 0)
to = this.length;
if (from2 <= to) {
this.forEachInner(f, from2, to, 0);
} else {
this.forEachInvertedInner(f, from2, to, 0);
}
};
RopeSequence.prototype.map = function map(f, from2, to) {
if (from2 === void 0)
from2 = 0;
if (to === void 0)
to = this.length;
var result = [];
this.forEach(function(elt, i) {
return result.push(f(elt, i));
}, from2, to);
return result;
};
RopeSequence.from = function from(values) {
if (values instanceof RopeSequence) {
return values;
}
return values && values.length ? new Leaf(values) : RopeSequence.empty;
};
var Leaf = /* @__PURE__ */ function(RopeSequence3) {
function Leaf2(values) {
RopeSequence3.call(this);
this.values = values;
}
if (RopeSequence3)
Leaf2.__proto__ = RopeSequence3;
Leaf2.prototype = Object.create(RopeSequence3 && RopeSequence3.prototype);
Leaf2.prototype.constructor = Leaf2;
var prototypeAccessors = { length: { configurable: true }, depth: { configurable: true } };
Leaf2.prototype.flatten = function flatten() {
return this.values;
};
Leaf2.prototype.sliceInner = function sliceInner(from2, to) {
if (from2 == 0 && to == this.length) {
return this;
}
return new Leaf2(this.values.slice(from2, to));
};
Leaf2.prototype.getInner = function getInner(i) {
return this.values[i];
};
Leaf2.prototype.forEachInner = function forEachInner(f, from2, to, start) {
for (var i = from2; i < to; i++) {
if (f(this.values[i], start + i) === false) {
return false;
}
}
};
Leaf2.prototype.forEachInvertedInner = function forEachInvertedInner(f, from2, to, start) {
for (var i = from2 - 1; i >= to; i--) {
if (f(this.values[i], start + i) === false) {
return false;
}
}
};
Leaf2.prototype.leafAppend = function leafAppend(other) {
if (this.length + other.length <= GOOD_LEAF_SIZE) {
return new Leaf2(this.values.concat(other.flatten()));
}
};
Leaf2.prototype.leafPrepend = function leafPrepend(other) {
if (this.length + other.length <= GOOD_LEAF_SIZE) {
return new Leaf2(other.flatten().concat(this.values));
}
};
prototypeAccessors.length.get = function() {
return this.values.length;
};
prototypeAccessors.depth.get = function() {
return 0;
};
Object.defineProperties(Leaf2.prototype, prototypeAccessors);
return Leaf2;
}(RopeSequence);
RopeSequence.empty = new Leaf([]);
var Append = /* @__PURE__ */ function(RopeSequence3) {
function Append2(left, right) {
RopeSequence3.call(this);
this.left = left;
this.right = right;
this.length = left.length + right.length;
this.depth = Math.max(left.depth, right.depth) + 1;
}
if (RopeSequence3)
Append2.__proto__ = RopeSequence3;
Append2.prototype = Object.create(RopeSequence3 && RopeSequence3.prototype);
Append2.prototype.constructor = Append2;
Append2.prototype.flatten = function flatten() {
return this.left.flatten().concat(this.right.flatten());
};
Append2.prototype.getInner = function getInner(i) {
return i < this.left.length ? this.left.get(i) : this.right.get(i - this.left.length);
};
Append2.prototype.forEachInner = function forEachInner(f, from2, to, start) {
var leftLen = this.left.length;
if (from2 < leftLen && this.left.forEachInner(f, from2, Math.min(to, leftLen), start) === false) {
return false;
}
if (to > leftLen && this.right.forEachInner(f, Math.max(from2 - leftLen, 0), Math.min(this.length, to) - leftLen, start + leftLen) === false) {
return false;
}
};
Append2.prototype.forEachInvertedInner = function forEachInvertedInner(f, from2, to, start) {
var leftLen = this.left.length;
if (from2 > leftLen && this.right.forEachInvertedInner(f, from2 - leftLen, Math.max(to, leftLen) - leftLen, start + leftLen) === false) {
return false;
}
if (to < leftLen && this.left.forEachInvertedInner(f, Math.min(from2, leftLen), to, start) === false) {
return false;
}
};
Append2.prototype.sliceInner = function sliceInner(from2, to) {
if (from2 == 0 && to == this.length) {
return this;
}
var leftLen = this.left.length;
if (to <= leftLen) {
return this.left.slice(from2, to);
}
if (from2 >= leftLen) {
return this.right.slice(from2 - leftLen, to - leftLen);
}
return this.left.slice(from2, leftLen).append(this.right.slice(0, to - leftLen));
};
Append2.prototype.leafAppend = function leafAppend(other) {
var inner = this.right.leafAppend(other);
if (inner) {
return new Append2(this.left, inner);
}
};
Append2.prototype.leafPrepend = function leafPrepend(other) {
var inner = this.left.leafPrepend(other);
if (inner) {
return new Append2(inner, this.right);
}
};
Append2.prototype.appendInner = function appendInner2(other) {
if (this.left.depth >= Math.max(this.right.depth, other.depth) + 1) {
return new Append2(this.left, new Append2(this.right, other));
}
return new Append2(this, other);
};
return Append2;
}(RopeSequence);
var ropeSequence = RopeSequence;
const max_empty_items = 500;
class Branch {
constructor(items, eventCount) {
this.items = items;
this.eventCount = eventCount;
}
// Pop the latest event off the branch's history and apply it
// to a document transform.
popEvent(state, preserveItems) {
if (this.eventCount == 0)
return null;
let end = this.items.length;
for (; ; end--) {
let next = this.items.get(end - 1);
if (next.selection) {
--end;
break;
}
}
let remap, mapFrom;
if (preserveItems) {
remap = this.remapping(end, this.items.length);
mapFrom = remap.maps.length;
}
let transform = state.tr;
let selection, remaining;
let addAfter = [], addBefore = [];
this.items.forEach((item, i) => {
if (!item.step) {
if (!remap) {
remap = this.remapping(end, i + 1);
mapFrom = remap.maps.length;
}
mapFrom--;
addBefore.push(item);
return;
}
if (remap) {
addBefore.push(new Item(item.map));
let step = item.step.map(remap.slice(mapFrom)), map2;
if (step && transform.maybeStep(step).doc) {
map2 = transform.mapping.maps[transform.mapping.maps.length - 1];
addAfter.push(new Item(map2, void 0, void 0, addAfter.length + addBefore.length));
}
mapFrom--;
if (map2)
remap.appendMap(map2, mapFrom);
} else {
transform.maybeStep(item.step);
}
if (item.selection) {
selection = remap ? item.selection.map(remap.slice(mapFrom)) : item.selection;
remaining = new Branch(this.items.slice(0, end).append(addBefore.reverse().concat(addAfter)), this.eventCount - 1);
return false;
}
}, this.items.length, 0);
return { remaining, transform, selection };
}
// Create a new branch with the given transform added.
addTransform(transform, selection, histOptions, preserveItems) {
let newItems = [], eventCount = this.eventCount;
let oldItems = this.items, lastItem = !preserveItems && oldItems.length ? oldItems.get(oldItems.length - 1) : null;
for (let i = 0; i < transform.steps.length; i++) {
let step = transform.steps[i].invert(transform.docs[i]);
let item = new Item(transform.mapping.maps[i], step, selection), merged;
if (merged = lastItem && lastItem.merge(item)) {
item = merged;
if (i)
newItems.pop();
else
oldItems = oldItems.slice(0, oldItems.length - 1);
}
newItems.push(item);
if (selection) {
eventCount++;
selection = void 0;
}
if (!preserveItems)
lastItem = item;
}
let overflow = eventCount - histOptions.depth;
if (overflow > DEPTH_OVERFLOW) {
oldItems = cutOffEvents(oldItems, overflow);
eventCount -= overflow;
}
return new Branch(oldItems.append(newItems), eventCount);
}
remapping(from2, to) {
let maps = new Mapping();
this.items.forEach((item, i) => {
let mirrorPos = item.mirrorOffset != null && i - item.mirrorOffset >= from2 ? maps.maps.length - item.mirrorOffset : void 0;
maps.appendMap(item.map, mirrorPos);
}, from2, to);
return maps;
}
addMaps(array) {
if (this.eventCount == 0)
return this;
return new Branch(this.items.append(array.map((map2) => new Item(map2))), this.eventCount);
}
// When the collab module receives remote changes, the history has
// to know about those, so that it can adjust the steps that were
// rebased on top of the remote changes, and include the position
// maps for the remote changes in its array of items.
rebased(rebasedTransform, rebasedCount) {
if (!this.eventCount)
return this;
let rebasedItems = [], start = Math.max(0, this.items.length - rebasedCount);
let mapping = rebasedTransform.mapping;
let newUntil = rebasedTransform.steps.length;
let eventCount = this.eventCount;
this.items.forEach((item) => {
if (item.selection)
eventCount--;
}, start);
let iRebased = rebasedCount;
this.items.forEach((item) => {
let pos = mapping.getMirror(--iRebased);
if (pos == null)
return;
newUntil = Math.min(newUntil, pos);
let map2 = mapping.maps[pos];
if (item.step) {
let step = rebasedTransform.steps[pos].invert(rebasedTransform.docs[pos]);
let selection = item.selection && item.selection.map(mapping.slice(iRebased + 1, pos));
if (selection)
eventCount++;
rebasedItems.push(new Item(map2, step, selection));
} else {
rebasedItems.push(new Item(map2));
}
}, start);
let newMaps = [];
for (let i = rebasedCount; i < newUntil; i++)
newMaps.push(new Item(mapping.maps[i]));
let items = this.items.slice(0, start).append(newMaps).append(rebasedItems);
let branch = new Branch(items, eventCount);
if (branch.emptyItemCount() > max_empty_items)
branch = branch.compress(this.items.length - rebasedItems.length);
return branch;
}
emptyItemCount() {
let count = 0;
this.items.forEach((item) => {
if (!item.step)
count++;
});
return count;
}
// Compressing a branch means rewriting it to push the air (map-only
// items) out. During collaboration, these naturally accumulate
// because each remote change adds one. The `upto` argument is used
// to ensure that only the items below a given level are compressed,
// because `rebased` relies on a clean, untouched set of items in
// order to associate old items with rebased steps.
compress(upto = this.items.length) {
let remap = this.remapping(0, upto), mapFrom = remap.maps.length;
let items = [], events = 0;
this.items.forEach((item, i) => {
if (i >= upto) {
items.push(item);
if (item.selection)
events++;
} else if (item.step) {
let step = item.step.map(remap.slice(mapFrom)), map2 = step && step.getMap();
mapFrom--;
if (map2)
remap.appendMap(map2, mapFrom);
if (step) {
let selection = item.selection && item.selection.map(remap.slice(mapFrom));
if (selection)
events++;
let newItem = new Item(map2.invert(), step, selection), merged, last = items.length - 1;
if (merged = items.length && items[last].merge(newItem))
items[last] = merged;
else
items.push(newItem);
}
} else if (item.map) {
mapFrom--;
}
}, this.items.length, 0);
return new Branch(ropeSequence.from(items.reverse()), events);
}
}
Branch.empty = new Branch(ropeSequence.empty, 0);
function cutOffEvents(items, n) {
let cutPoint;
items.forEach((item, i) => {
if (item.selection && n-- == 0) {
cutPoint = i;
return false;
}
});
return items.slice(cutPoint);
}
class Item {
constructor(map2, step, selection, mirrorOffset) {
this.map = map2;
this.step = step;
this.selection = selection;
this.mirrorOffset = mirrorOffset;
}
merge(other) {
if (this.step && other.step && !other.selection) {
let step = other.step.merge(this.step);
if (step)
return new Item(step.getMap().invert(), step, this.selection);
}
}
}
class HistoryState {
constructor(done, undone, prevRanges, prevTime) {
this.done = done;
this.undone = undone;
this.prevRanges = prevRanges;
this.prevTime = prevTime;
}
}
const DEPTH_OVERFLOW = 20;
function applyTransaction(history2, state, tr, options) {
let historyTr = tr.getMeta(historyKey), rebased;
if (historyTr)
return historyTr.historyState;
if (tr.getMeta(closeHistoryKey))
history2 = new HistoryState(history2.done, history2.undone, null, 0);
let appended = tr.getMeta("appendedTransaction");
if (tr.steps.length == 0) {
return history2;
} else if (appended && appended.getMeta(historyKey)) {
if (appended.getMeta(historyKey).redo)
return new HistoryState(history2.done.addTransform(tr, void 0, options, mustPreserveItems(state)), history2.undone, rangesFor(tr.mapping.maps[tr.steps.length - 1]), history2.prevTime);
else
return new HistoryState(history2.done, history2.undone.addTransform(tr, void 0, options, mustPreserveItems(state)), null, history2.prevTime);
} else if (tr.getMeta("addToHistory") !== false && !(appended && appended.getMeta("addToHistory") === false)) {
let newGroup = history2.prevTime == 0 || !appended && (history2.prevTime < (tr.time || 0) - options.newGroupDelay || !isAdjacentTo(tr, history2.prevRanges));
let prevRanges = appended ? mapRanges(history2.prevRanges, tr.mapping) : rangesFor(tr.mapping.maps[tr.steps.length - 1]);
return new HistoryState(history2.done.addTransform(tr, newGroup ? state.selection.getBookmark() : void 0, options, mustPreserveItems(state)), Branch.empty, prevRanges, tr.time);
} else if (rebased = tr.getMeta("rebased")) {
return new HistoryState(history2.done.rebased(tr, rebased), history2.undone.rebased(tr, rebased), mapRanges(history2.prevRanges, tr.mapping), history2.prevTime);
} else {
return new HistoryState(history2.done.addMaps(tr.mapping.maps), history2.undone.addMaps(tr.mapping.maps), mapRanges(history2.prevRanges, tr.mapping), history2.prevTime);
}
}
function isAdjacentTo(transform, prevRanges) {
if (!prevRanges)
return false;
if (!transform.docChanged)
return true;
let adjacent = false;
transform.mapping.maps[0].forEach((start, end) => {
for (let i = 0; i < prevRanges.length; i += 2)
if (start <= prevRanges[i + 1] && end >= prevRanges[i])
adjacent = true;
});
return adjacent;
}
function rangesFor(map2) {
let result = [];
map2.forEach((_from, _to, from2, to) => result.push(from2, to));
return result;
}
function mapRanges(ranges, mapping) {
if (!ranges)
return null;
let result = [];
for (let i = 0; i < ranges.length; i += 2) {
let from2 = mapping.map(ranges[i], 1), to = mapping.map(ranges[i + 1], -1);
if (from2 <= to)
result.push(from2, to);
}
return result;
}
function histTransaction(history2, state, dispatch, redo2) {
let preserveItems = mustPreserveItems(state);
let histOptions = historyKey.get(state).spec.config;
let pop = (redo2 ? history2.undone : history2.done).popEvent(state, preserveItems);
if (!pop)
return;
let selection = pop.selection.resolve(pop.transform.doc);
let added = (redo2 ? history2.done : history2.undone).addTransform(pop.transform, state.selection.getBookmark(), histOptions, preserveItems);
let newHist = new HistoryState(redo2 ? added : pop.remaining, redo2 ? pop.remaining : added, null, 0);
dispatch(pop.transform.setSelection(selection).setMeta(historyKey, { redo: redo2, historyState: newHist }).scrollIntoView());
}
let cachedPreserveItems = false, cachedPreserveItemsPlugins = null;
function mustPreserveItems(state) {
let plugins = state.plugins;
if (cachedPreserveItemsPlugins != plugins) {
cachedPreserveItems = false;
cachedPreserveItemsPlugins = plugins;
for (let i = 0; i < plugins.length; i++)
if (plugins[i].spec.historyPreserveItems) {
cachedPreserveItems = true;
break;
}
}
return cachedPreserveItems;
}
const historyKey = new PluginKey("history");
const closeHistoryKey = new PluginKey("closeHistory");
function history(config = {}) {
config = {
depth: config.depth || 100,
newGroupDelay: config.newGroupDelay || 500
};
return new Plugin({
key: historyKey,
state: {
init() {
return new HistoryState(Branch.empty, Branch.empty, null, 0);
},
apply(tr, hist, state) {
return applyTransaction(hist, state, tr, config);
}
},
config,
props: {
handleDOMEvents: {
beforeinput(view, e) {
let inputType = e.inputType;
let command2 = inputType == "historyUndo" ? undo : inputType == "historyRedo" ? redo : null;
if (!command2)
return false;
e.preventDefault();
return command2(view.state, view.dispatch);
}
}
}
});
}
const undo = (state, dispatch) => {
let hist = historyKey.getState(state);
if (!hist || hist.done.eventCount == 0)
return false;
if (dispatch)
histTransaction(hist, state, dispatch, false);
return true;
};
const redo = (state, dispatch) => {
let hist = historyKey.getState(state);
if (!hist || hist.undone.eventCount == 0)
return false;
if (dispatch)
histTransaction(hist, state, dispatch, true);
return true;
};
const History = Extension.create({
name: "history",
addOptions() {
return {
depth: 100,
newGroupDelay: 500
};
},
addCommands() {
return {
undo: () => ({ state, dispatch }) => {
return undo(state, dispatch);
},
redo: () => ({ state, dispatch }) => {
return redo(state, dispatch);
}
};
},
addProseMirrorPlugins() {
return [
history(this.options)
];
},
addKeyboardShortcuts() {
return {
"Mod-z": () => this.editor.commands.undo(),
"Mod-y": () => this.editor.commands.redo(),
"Shift-Mod-z": () => this.editor.commands.redo(),
// Russian keyboard layouts
"Mod-я": () => this.editor.commands.undo(),
"Shift-Mod-я": () => this.editor.commands.redo()
};
}
});
const HorizontalRule = Node2.create({
name: "horizontalRule",
addOptions() {
return {
HTMLAttributes: {}
};
},
group: "block",
parseHTML() {
return [{ tag: "hr" }];
},
renderHTML({ HTMLAttributes }) {
return ["hr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
},
addCommands() {
return {
setHorizontalRule: () => ({ chain }) => {
return chain().insertContent({ type: this.name }).command(({ tr, dispatch }) => {
var _a;
if (dispatch) {
const { $to } = tr.selection;
const posAfter = $to.end();
if ($to.nodeAfter) {
tr.setSelection(TextSelection.create(tr.doc, $to.pos));
} else {
const node = (_a = $to.parent.type.contentMatch.defaultType) === null || _a === void 0 ? void 0 : _a.create();
if (node) {
tr.insert(posAfter, node);
tr.setSelection(TextSelection.create(tr.doc, posAfter));
}
}
tr.scrollIntoView();
}
return true;
}).run();
}
};
},
addInputRules() {
return [
nodeInputRule({
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
type: this.type
})
];
}
});
const starInputRegex = /(?:^|\s)((?:\*)((?:[^*]+))(?:\*))$/;
const starPasteRegex = /(?:^|\s)((?:\*)((?:[^*]+))(?:\*))/g;
const underscoreInputRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))$/;
const underscorePasteRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))/g;
const Italic = Mark2.create({
name: "italic",
addOptions() {
return {
HTMLAttributes: {}
};
},
parseHTML() {
return [
{
tag: "em"
},
{
tag: "i",
getAttrs: (node) => node.style.fontStyle !== "normal" && null
},
{
style: "font-style=italic"
}
];
},
renderHTML({ HTMLAttributes }) {
return ["em", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setItalic: () => ({ commands: commands2 }) => {
return commands2.setMark(this.name);
},
toggleItalic: () => ({ commands: commands2 }) => {
return commands2.toggleMark(this.name);
},
unsetItalic: () => ({ commands: commands2 }) => {
return commands2.unsetMark(this.name);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-i": () => this.editor.commands.toggleItalic(),
"Mod-I": () => this.editor.commands.toggleItalic()
};
},
addInputRules() {
return [
markInputRule({
find: starInputRegex,
type: this.type
}),
markInputRule({
find: underscoreInputRegex,
type: this.type
})
];
},
addPasteRules() {
return [
markPasteRule({
find: starPasteRegex,
type: this.type
}),
markPasteRule({
find: underscorePasteRegex,
type: this.type
})
];
}
});
const ListItem = Node2.create({
name: "listItem",
addOptions() {
return {
HTMLAttributes: {}
};
},
content: "paragraph block*",
defining: true,
parseHTML() {
return [
{
tag: "li"
}
];
},
renderHTML({ HTMLAttributes }) {
return ["li", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addKeyboardShortcuts() {
return {
Enter: () => this.editor.commands.splitListItem(this.name),
Tab: () => this.editor.commands.sinkListItem(this.name),
"Shift-Tab": () => this.editor.commands.liftListItem(this.name)
};
}
});
const inputRegex$1 = /^(\d+)\.\s$/;
const OrderedList = Node2.create({
name: "orderedList",
addOptions() {
return {
itemTypeName: "listItem",
HTMLAttributes: {}
};
},
group: "block list",
content() {
return `${this.options.itemTypeName}+`;
},
addAttributes() {
return {
start: {
default: 1,
parseHTML: (element) => {
return element.hasAttribute("start") ? parseInt(element.getAttribute("start") || "", 10) : 1;
}
}
};
},
parseHTML() {
return [
{
tag: "ol"
}
];
},
renderHTML({ HTMLAttributes }) {
const { start, ...attributesWithoutStart } = HTMLAttributes;
return start === 1 ? ["ol", mergeAttributes(this.options.HTMLAttributes, attributesWithoutStart), 0] : ["ol", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
toggleOrderedList: () => ({ commands: commands2 }) => {
return commands2.toggleList(this.name, this.options.itemTypeName);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Shift-7": () => this.editor.commands.toggleOrderedList()
};
},
addInputRules() {
return [
wrappingInputRule({
find: inputRegex$1,
type: this.type,
getAttributes: (match) => ({ start: +match[1] }),
joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1]
})
];
}
});
const Paragraph = Node2.create({
name: "paragraph",
priority: 1e3,
addOptions() {
return {
HTMLAttributes: {}
};
},
group: "block",
content: "inline*",
parseHTML() {
return [
{ tag: "p" }
];
},
renderHTML({ HTMLAttributes }) {
return ["p", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setParagraph: () => ({ commands: commands2 }) => {
return commands2.setNode(this.name);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Alt-0": () => this.editor.commands.setParagraph()
};
}
});
const inputRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))$/;
const pasteRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))/g;
const Strike = Mark2.create({
name: "strike",
addOptions() {
return {
HTMLAttributes: {}
};
},
parseHTML() {
return [
{
tag: "s"
},
{
tag: "del"
},
{
tag: "strike"
},
{
style: "text-decoration",
consuming: false,
getAttrs: (style2) => style2.includes("line-through") ? {} : false
}
];
},
renderHTML({ HTMLAttributes }) {
return ["s", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addCommands() {
return {
setStrike: () => ({ commands: commands2 }) => {
return commands2.setMark(this.name);
},
toggleStrike: () => ({ commands: commands2 }) => {
return commands2.toggleMark(this.name);
},
unsetStrike: () => ({ commands: commands2 }) => {
return commands2.unsetMark(this.name);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-Shift-x": () => this.editor.commands.toggleStrike()
};
},
addInputRules() {
return [
markInputRule({
find: inputRegex,
type: this.type
})
];
},
addPasteRules() {
return [
markPasteRule({
find: pasteRegex,
type: this.type
})
];
}
});
const Text = Node2.create({
name: "text",
group: "inline"
});
const StarterKit = Extension.create({
name: "starterKit",
addExtensions() {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
const extensions2 = [];
if (this.options.blockquote !== false) {
extensions2.push(Blockquote.configure((_a = this.options) === null || _a === void 0 ? void 0 : _a.blockquote));
}
if (this.options.bold !== false) {
extensions2.push(Bold.configure((_b = this.options) === null || _b === void 0 ? void 0 : _b.bold));
}
if (this.options.bulletList !== false) {
extensions2.push(BulletList.configure((_c = this.options) === null || _c === void 0 ? void 0 : _c.bulletList));
}
if (this.options.code !== false) {
extensions2.push(Code.configure((_d = this.options) === null || _d === void 0 ? void 0 : _d.code));
}
if (this.options.codeBlock !== false) {
extensions2.push(CodeBlock.configure((_e = this.options) === null || _e === void 0 ? void 0 : _e.codeBlock));
}
if (this.options.document !== false) {
extensions2.push(Document.configure((_f = this.options) === null || _f === void 0 ? void 0 : _f.document));
}
if (this.options.dropcursor !== false) {
extensions2.push(Dropcursor.configure((_g = this.options) === null || _g === void 0 ? void 0 : _g.dropcursor));
}
if (this.options.gapcursor !== false) {
extensions2.push(Gapcursor.configure((_h = this.options) === null || _h === void 0 ? void 0 : _h.gapcursor));
}
if (this.options.hardBreak !== false) {
extensions2.push(HardBreak.configure((_j = this.options) === null || _j === void 0 ? void 0 : _j.hardBreak));
}
if (this.options.heading !== false) {
extensions2.push(Heading.configure((_k = this.options) === null || _k === void 0 ? void 0 : _k.heading));
}
if (this.options.history !== false) {
extensions2.push(History.configure((_l = this.options) === null || _l === void 0 ? void 0 : _l.history));
}
if (this.options.horizontalRule !== false) {
extensions2.push(HorizontalRule.configure((_m = this.options) === null || _m === void 0 ? void 0 : _m.horizontalRule));
}
if (this.options.italic !== false) {
extensions2.push(Italic.configure((_o = this.options) === null || _o === void 0 ? void 0 : _o.italic));
}
if (this.options.listItem !== false) {
extensions2.push(ListItem.configure((_p = this.options) === null || _p === void 0 ? void 0 : _p.listItem));
}
if (this.options.orderedList !== false) {
extensions2.push(OrderedList.configure((_q = this.options) === null || _q === void 0 ? void 0 : _q.orderedList));
}
if (this.options.paragraph !== false) {
extensions2.push(Paragraph.configure((_r = this.options) === null || _r === void 0 ? void 0 : _r.paragraph));
}
if (this.options.strike !== false) {
extensions2.push(Strike.configure((_s = this.options) === null || _s === void 0 ? void 0 : _s.strike));
}
if (this.options.text !== false) {
extensions2.push(Text.configure((_t = this.options) === null || _t === void 0 ? void 0 : _t.text));
}
return extensions2;
}
});
function editor(element, value) {
let editor2 = new Editor({
element,
extensions: [StarterKit],
content: value,
editorProps: {
attributes: {
spellcheck: "false"
}
}
});
return {
editor: editor2,
// Anything before this promise resolves will happen before timing starts
ready: Promise.resolve(),
getScrollHeight() {
return element.scrollHeight;
},
getScrollTop() {
return element.scrollTop;
},
setScrollTop(value2) {
element.scrollTop = value2;
},
setValue(value2) {
editor2.chain().focus().setContent(value2).setTextSelection(0).run();
element.scrollTop = 0;
},
format(on) {
if (on)
editor2.chain().focus().selectAll().setBold().setTextSelection(0).run();
else
editor2.chain().focus().selectAll().unsetBold().setTextSelection(0).run();
}
};
}
let editorContainer = document.querySelector("#editor");
let editorInstance = null;
let buttons = {
create: document.querySelector("#create"),
highlight: document.querySelector("#highlight"),
unhighlight: document.querySelector("#unhighlight"),
long: document.querySelector("#long"),
short: document.querySelector("#short"),
scroll: document.querySelector("#scroll"),
layout: document.querySelector("#layout")
};
buttons.scroll.addEventListener("click", scroll);
buttons.highlight.addEventListener("click", highlight);
buttons.unhighlight.addEventListener("click", unhighlight);
buttons.long.addEventListener("click", long);
buttons.short.addEventListener("click", short);
buttons.layout.addEventListener("click", layout);
buttons.create.addEventListener("click", (e) => {
if (!editorInstance) {
editorInstance = editor(editorContainer);
editorInstance.ready.then(() => {
buttons.unhighlight.classList.add("active", "true");
buttons.create.setAttribute("disabled", "true");
});
}
});
function layout() {
const body = document.body.getBoundingClientRect();
layout.e = document.elementFromPoint(body.width / 2 | 0, body.height / 2 | 0);
}
function highlight() {
buttons.unhighlight.classList.toggle("active", false);
buttons.highlight.classList.toggle("active", true);
editorInstance.format(true);
}
function unhighlight() {
buttons.unhighlight.classList.toggle("active", true);
buttons.highlight.classList.toggle("active", false);
editorInstance.format(false);
}
function long() {
buttons.short.classList.toggle("active", false);
buttons.long.classList.toggle("active", true);
editorInstance.setValue(longtext);
}
function short() {
buttons.short.classList.toggle("active", true);
buttons.long.classList.toggle("active", false);
editorInstance.setValue(text);
}
function scroll() {
let isTop = editorInstance.getScrollTop() == 0;
editorInstance.setScrollTop(isTop ? editorInstance.getScrollHeight() : 0);
}