Hyphenopoly.js

Hyphenation for node and Polyfill for client-side hyphenation.

README

Hyphenopoly.js


CircleCI
Coverage Status
npms score
Codacy Badge

Hyphenopoly.js is a __JavaScript-polyfill for hyphenation in HTML__: it hyphenates text if the user agent does not support CSS-hyphenation at all or not for the required languages and it is a __Node.js-module__.

The package consists of the following parts:
-   _Hyphenopoly_Loader.js_ (11KB unpacked, 2KB minified and compressed): feature-checks the client and loads other resources if necessary.
-   _Hyphenopoly.js_ (36KB unpacked, 5KB minified and compressed): does the whole DOM-foo and wraps wasm.
-   _wasm-Modules_ (sizes differ! e.g. en-us.wasm: 21KB uncompressed, 15KB compressed): core hyphenation functions and hyphenation patterns in a space saving binary format (including pattern license).
-   _hyphenopoly.module.js_: the node module to hyphenate plain text strings.

Usage (Browser)

Place all the code for Hyphenopoly at the top of the header (immediately after the `` tag) to ensure resources are loaded as early as possible.<div class="base"><br></div><div class="base">You'll have to insert two script blocks. In the first block, load Hyphenopoly_Loader.js as an external script.</div><div class="base">In the second block, provide the initial configurations for Hyphenopoly_Loader as an inline script. This also triggers all further steps.</div><div class="base"><br></div><div class="base">Also, don't forget to enable CSS hyphenation.</div><div class="base"><br></div><div class="base"><a href="http://mnater.github.io/Hyphenopoly/min/example.html" title="Example" target="_blank" rel="nofollow noopener noreferrer">Example</a>:</div><div class="code"><ol start="0"><li><span>```html</span></li><li><span class="html-doctype"><!DOCTYPE html></span></li><li><span class="html-label"><html</span><span class="html-label">></span></li><li><span>    </span><span class="html-label"><head</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"><meta</span><span class="html-type"> http-equiv</span><span>=</span><span class="html-value">"content-type"</span><span class="html-type"> content</span><span>=</span><span class="html-value">"text/html; charset=UTF-8"</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"><title</span><span class="html-label">></span><span>Example 1</span><span class="html-label"></title</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"><script</span><span class="html-type"> src</span><span>=</span><span class="html-value">"./Hyphenopoly_Loader.js"</span><span class="html-label">></span><span class="html-label"></script</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"><script</span><span class="html-label">></span></li><li><span>        Hyphenopoly.config({</span></li><li><span>            require: {</span></li><li><span>                "la": "honorificabilitudinitas",</span></li><li><span>                "de": "Silbentrennungsalgorithmus",</span></li><li><span>                "en-us": "Supercalifragilisticexpialidocious"</span></li><li><span>            },</span></li><li><span>            setup: {</span></li><li><span>                selectors: {</span></li><li><span>                    ".container": {}</span></li><li><span>                }</span></li><li><span>            }</span></li><li><span>        });</span></li><li><span>        </span><span class="html-label"></script</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"><style</span><span class="html-type"> type</span><span>=</span><span class="html-value">"text/css"</span><span class="html-label">></span></li><li><span>            body {</span></li><li><span>                width:60%;</span></li><li><span>                margin-left:20%;</span></li><li><span>            }</span></li><li><span>            p {</span></li><li><span>                text-align: justify;</span></li><li><span>                margin: 0 2em 0 0;</span></li><li><span>            }</span></li><li><span>            .container {</span></li><li><span>                display: flex;</span></li><li><span>                hyphens: auto;</span></li><li><span>                -ms-hyphens: auto;</span></li><li><span>                -moz-hyphens: auto;</span></li><li><span>                -webkit-hyphens: auto;</span></li><li><span>            }</span></li><li><span>        </span><span class="html-label"></style</span><span class="html-label">></span></li><li><span>    </span><span class="html-label"></head</span><span class="html-label">></span></li><li><span>    </span><span class="html-label"><body</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"><h</span><span class="html-label">1></span><span>Example 1</span><span class="html-label"></h</span><span class="html-label">1></span></li><li><span>        </span><span class="html-label"><div</span><span class="html-type"> class</span><span>=</span><span class="html-value">"container"</span><span class="html-label">></span></li><li><span>            </span><span class="html-label"><p</span><span class="html-type"> lang</span><span>=</span><span class="html-value">"la"</span><span class="html-label">></span><span>Qua de causa Helvetii quoque reliquos Gallos virtute praecedunt, quod fere cotidianis proeliis cum Germanis contendunt, cum aut suis finibus eos prohibent aut ipsi in eorum finibus bellum gerunt.</span><span class="html-label"></p</span><span class="html-label">></span></li><li><span>            </span><span class="html-label"><p</span><span class="html-type"> lang</span><span>=</span><span class="html-value">"en-us"</span><span class="html-label">></span><span>For which reason the Helvetii also surpass the rest of the Gauls in valor, as they contend with the Germans in almost daily battles, when they either repel them from their own territories, or themselves wage war on their frontiers.</span><span class="html-label"></p</span><span class="html-label">></span></li><li><span>            </span><span class="html-label"><p</span><span class="html-type"> lang</span><span>=</span><span class="html-value">"de"</span><span class="html-label">></span><span>Aus diesem Grund übertreffen auch die Helvetier die übrigen Gallier an Tapferkeit, weil sie sich in fast täglichen Gefechten mit den Germanen messen, wobei sie diese entweder von ihrem Gebiet fernhalten oder selbst in deren Gebiet kämpfen.</span><span class="html-label"></p</span><span class="html-label">></span></li><li><span>        </span><span class="html-label"></div</span><span class="html-label">></span></li><li><span>    </span><span class="html-label"></body</span><span class="html-label">></span></li><li><span class="html-label"></html</span><span class="html-label">></span></li><li><span>```</span></li></ol></div><div class="base">Let's go through this example step by step:</div><div class="base"><br></div><div class="base"><h3>UTF-8</h3></div><div class="base">Make sure your page is encoded as utf-8.</div><div class="base"><br></div><div class="base"><h3>script blocks – load, configure and run Hyphenopoly_Loader.js</h3></div><div class="base">Hyphenopoly_Loader.js needs some information to run. This information is provided as a parameter object to the function <span class="edit-backquote">Hyphenopoly.config()</span>. This information is stored in a globally accessible Object called <span class="edit-backquote">window.Hyphenopoly</span>. Hyphenopoly_Loader.js and (if necessary) Hyphenopoly.js will add other methods and properties only to this object – there will be no other global variables or functions beyond this object.</div><div class="base"><br></div><div class="base"><h4>require</h4></div><div class="base">The configuration object must have exactly one property called <span class="edit-backquote">require</span> which itself is an object containing at least one nameValuePair where the name is a language code string (Some languages are region-specific. See the patterns directory for supported languages. E.g. just using <span class="edit-backquote">en</span> won't work, use <span class="edit-backquote">en-us</span>or <span class="edit-backquote">en-gb</span>) and the value is a long word string in that language (preferably more than 12 characters long).</div><div class="base"><br></div><div class="base">If you want to force the usage of Hyphenopoly.js for a language (e.g. for testing purposes), write <span class="edit-backquote">"FORCEHYPHENOPOLY"</span> instead of the long word.</div><div class="base"><br></div><div class="base">Hyphenopoly_Loader.js tests if the client (aka browser, aka user agent) supports CSS hyphenation for the language(s) given in <span class="edit-backquote">require</span>. In the example above, it will test if the client supports CSS-hyphenation for Latin, German and US-English.</div><div class="base"><br></div><div class="base">If one of the given languages isn't supported, it automatically hides the document's contents and loads Hyphenopoly.js and the necessary WebAssembly modules.</div><div class="base"><br></div><div class="base">Hyphenopoly.js – once loaded – will hyphenate the elements according to the settings and unhide the document when it's done.</div><div class="base"><br></div><div class="base">If something goes wrong and Hyphenopoly.js is unable to unhide the document, Hyphenopoly_Loader.js has a timeout that kicks in after some time (defaults to 1000ms) and unhides the document and writes a message to the console.</div><div class="base"><br></div><div class="base">If the browser supports all required languages, the script deletes the <span class="edit-backquote">Hyphenopoly</span>-object and terminates without further ado.</div><div class="base"><br></div><div class="base"><h3>enable CSS-hyphenation</h3></div><div class="base">Hyphenopoly by default hyphenates elements (and their children) with the classname <span class="edit-backquote">.hyphenate</span>. Don't forget to enable CSS-hyphenation for the classes eventually handled by Hyphenopoly.</div><div class="base"><br></div><div class="base"><h2>Usage (node)</h2></div><div class="base"><a href="https://npm.runkit.com/hyphenopoly" title="Try hyphenopoly on RunKit" target="_blank" rel="nofollow noopener noreferrer"><img src="https://badge.runkitcdn.com/hyphenopoly.svg" alt="Try hyphenopoly on RunKit"/></a></div><div class="base"><br></div><div class="base">Install:</div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span>`</span><span class="js-variable">shell</span></li><li><span class="js-variable">npm</span><span> </span><span class="js-variable">i</span><span> </span><span class="js-variable">hyphenopoly</span></li><li><span>`</span><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span>`</span><span class="js-variable">javascript</span></li><li><span class="js-keyword">import</span><span> </span><span class="js-variable">hyphenopoly</span><span> </span><span class="js-keyword">from</span><span> </span><span class="js-string">"hyphenopoly"</span><span>;</span></li><li><br></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">hyphenator</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">hyphenopoly</span><span class="js-func">.config</span><span>(</span><span>{</span></li><li><span>    </span><span class="js-string">"require"</span><span>:</span><span> </span><span>[</span><span class="js-string">"de"</span><span>,</span><span> </span><span class="js-string">"en-us"</span><span>]</span><span>,</span></li><li><span>    </span><span class="js-string">"hyphen"</span><span>:</span><span> </span><span class="js-string">"•"</span><span>,</span></li><li><span>    </span><span class="js-string">"loader"</span><span>:</span><span> </span><span class="js-func">async</span><span> </span><span>(</span><span class="js-variable">file</span><span>)</span><span> </span><span class="js-operator">=></span><span> </span><span>{</span></li><li><span>        </span><span class="js-keyword">const</span><span> </span><span>{</span><span class="js-variable">readFile</span><span>}</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">await</span><span> </span><span class="js-keyword">import</span><span>(</span><span class="js-string">"node:fs/promises"</span><span>)</span><span>;</span></li><li><span>        </span><span class="js-keyword">const</span><span> </span><span>{</span><span class="js-variable">dirname</span><span>}</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">await</span><span> </span><span class="js-keyword">import</span><span>(</span><span class="js-string">"node:path"</span><span>)</span><span>;</span></li><li><span>        </span><span class="js-keyword">const</span><span> </span><span>{</span><span class="js-variable">fileURLToPath</span><span>}</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">await</span><span> </span><span class="js-keyword">import</span><span>(</span><span class="js-string">"node:url"</span><span>)</span><span>;</span></li><li><span>        </span><span class="js-keyword">const</span><span> </span><span class="js-variable">cwd</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-func">dirname</span><span>(</span><span class="js-func">fileURLToPath</span><span>(</span><span class="js-keyword">import</span><span class="js-variable">.meta</span><span class="js-variable">.url</span><span>)</span><span>)</span><span>;</span></li><li><span>        </span><span class="js-keyword">return</span><span> </span><span class="js-func">readFile</span><span>(</span><span>`</span><span>$</span><span>{</span><span class="js-variable">cwd</span><span>}</span><span class="js-reg">/../</span><span class="js-variable">patterns</span><span>/</span><span>$</span><span>{</span><span class="js-variable">file</span><span>}</span><span>`</span><span>)</span><span>;</span></li><li><span>    </span><span>}</span><span>,</span></li><li><span>    </span><span class="js-string">"exceptions"</span><span>:</span><span> </span><span>{</span></li><li><span>        </span><span class="js-string">"en-us"</span><span>:</span><span> </span><span class="js-string">"en-han-ces"</span></li><li><span>    </span><span>}</span></li><li><span>}</span><span>)</span><span>;</span></li><li><br></li><li><span class="js-variable">async</span><span> </span><span class="js-keyword">function</span><span> </span><span class="js-func">hyphenate_en</span><span>(</span><span class="js-variable">text</span><span>)</span><span> </span><span>{</span></li><li><span>    </span><span class="js-keyword">const</span><span> </span><span class="js-variable">hyphenateText</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">await</span><span> </span><span class="js-variable">hyphenator</span><span class="js-func">.get</span><span>(</span><span class="js-string">"en-us"</span><span>)</span><span>;</span></li><li><span>    </span><span class="js-obj">console</span><span class="js-func">.log</span><span>(</span><span class="js-func">hyphenateText</span><span>(</span><span class="js-variable">text</span><span>)</span><span>)</span><span>;</span></li><li><span>}</span></li><li><br></li><li><span class="js-variable">async</span><span> </span><span class="js-keyword">function</span><span> </span><span class="js-func">hyphenate_de</span><span>(</span><span class="js-variable">text</span><span>)</span><span> </span><span>{</span></li><li><span>    </span><span class="js-keyword">const</span><span> </span><span class="js-variable">hyphenateText</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">await</span><span> </span><span class="js-variable">hyphenator</span><span class="js-func">.get</span><span>(</span><span class="js-string">"de"</span><span>)</span><span>;</span></li><li><span>    </span><span class="js-obj">console</span><span class="js-func">.log</span><span>(</span><span class="js-func">hyphenateText</span><span>(</span><span class="js-variable">text</span><span>)</span><span>)</span><span>;</span></li><li><span>}</span></li><li><br></li><li><span class="js-func">hyphenate_en</span><span>(</span><span class="js-string">"hyphenation enhances justification."</span><span>)</span><span>;</span></li><li><span class="js-func">hyphenate_de</span><span>(</span><span class="js-string">"Silbentrennung verbessert den Blocksatz."</span><span>)</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base"><h2>Support this project</h2></div><div class="base"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SYNZKB8Z2FRQY" title="PayPal" target="_blank" rel="nofollow noopener noreferrer"><img src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG_global.gif" alt="PayPal"/></a></div><div class="base"><br></div><a href="https://opencollective.com/hyphenopoly/donate" target="_blank" rel="nofollow noopener noreferrer" target="_blank"> <img src="https://opencollective.com/hyphenopoly/donate/button@2x.png?color=blue" width=300 /></a><div class="base"><br></div><div class="base"><h2>Automatic hyphenation</h2></div><div class="base">The algorithm used for hyphenation was developed by Franklin M. Liang for TeX. It works more or less like this:</div><div class="base"><br></div><div class="base">1.  Load a set of precomputed language specific patterns. The patterns are stored in a structure called a trie, which is very efficient for this task.</div><div class="base">2.  Collect all patterns that are a substring of the word to be hyphenated.</div><div class="base">3.  Combine the numerical values between characters: higher values overwrite lower values.</div><div class="base">4.  Odd values are hyphenation points (except if the hyphenation point is left from <span class="edit-backquote">leftmin</span> and right from <span class="edit-backquote">rightmin</span>), replace them with a soft hyphen and drop the other values.</div><div class="base">5.  Repeat steps 2. - 4. for all words longer than minWordLength</div><div class="base"><br></div><div class="base">Example:</div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span>`</span><span class="js-variable">text</span></li><li><span class="js-func">Hyphenation</span></li><li><span class="js-variable">h</span><span> </span><span class="js-variable">y</span><span> </span><span class="js-variable">p</span><span> </span><span class="js-variable">h</span><span> </span><span class="js-variable">e</span><span> </span><span class="js-variable">n</span><span> </span><span class="js-variable">a</span><span> </span><span class="js-variable">t</span><span> </span><span class="js-variable">i</span><span> </span><span class="js-variable">o</span><span> </span><span class="js-variable">n</span></li><li><span class="js-variable">h</span><span> </span><span class="js-variable">y3p</span><span> </span><span class="js-variable">h</span></li><li><span>      </span><span class="js-variable">h</span><span> </span><span class="js-variable">e2n</span></li><li><span>      </span><span class="js-variable">h</span><span> </span><span class="js-variable">e</span><span> </span><span class="js-variable">n</span><span> </span><span class="js-variable">a4</span></li><li><span>      </span><span class="js-variable">h</span><span> </span><span class="js-variable">e</span><span> </span><span class="js-variable">n5a</span><span> </span><span class="js-variable">t</span></li><li><span>         </span><span class="js-variable">1n</span><span> </span><span class="js-variable">a</span></li><li><span>          </span><span class="js-variable">n2a</span><span> </span><span class="js-variable">t</span></li><li><span>             </span><span class="js-variable">1t</span><span> </span><span class="js-variable">i</span><span> </span><span class="js-variable">o</span></li><li><span>               </span><span class="js-variable">2i</span><span> </span><span class="js-variable">o</span></li><li><span>                  </span><span class="js-variable">o2n</span></li><li><span class="js-variable">h0y3p0h0e2n5a4t2i0o2n</span></li><li><span class="js-func">Hy</span><span class="js-operator">-</span><span class="js-variable">phen</span><span class="js-operator">-</span><span class="js-variable">ation</span></li><li><span>`</span><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">The patterns are precomputed and available for many languages on <a href="https://www.ctan.org/tex-archive/language/hyphenation/" title="CTAN" target="_blank" rel="nofollow noopener noreferrer">CTAN</a> and <a href="https://github.com/hyphenation/tex-hyphen" title="tex-hyphen" target="_blank" rel="nofollow noopener noreferrer">tex-hyphen</a>. For Hyphenopoly.js they are converted to a succinct trie data structure (including pattern license, metadata, and the patterns).</div><div class="base"><br></div><div class="base">The original patterns are computed from a large list of hyphenated words by a program called <span class="edit-backquote">patgen</span>. They aim to find some hyphenation points – not all – because it's better to miss a hyphenation point than to have some false hyphenation points. Most patterns are really good, but none are error free.</div><div class="base"><br></div><div class="base">These patterns vary in size. This is mostly due to the different linguistic characteristics of the languages.</div><div class="base"><br></div><div class="base"><h2>Contributors ✨</h2></div><div class="base"><br></div><div class="base">Thanks goes to these wonderful people (<a href="https://allcontributors.org/docs/en/emoji-key" title="emoji key" target="_blank" rel="nofollow noopener noreferrer">emoji key</a>):</div><div class="base"><br></div><!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --><!-- prettier-ignore-start --><!-- markdownlint-disable --><table> <tbody> <tr> <td align="center"><a href="https://github.com/StephanHoyer" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars.githubusercontent.com/u/54701?v=4?s=100" width="100px;" alt="Stephan Hoyer"/><br /><sub><b>Stephan Hoyer</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=StephanHoyer" target="_blank" rel="nofollow noopener noreferrer" title="Documentation">📖</a> <a href="https://github.com/mnater/Hyphenopoly/commits?author=StephanHoyer" target="_blank" rel="nofollow noopener noreferrer" title="Code">💻</a></td> <td align="center"><a href="http://thomasbroadley.com" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars0.githubusercontent.com/u/8731922?v=4?s=100" width="100px;" alt="Thomas Broadley"/><br /><sub><b>Thomas Broadley</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=tbroadley" target="_blank" rel="nofollow noopener noreferrer" title="Documentation">📖</a></td> <td align="center"><a href="https://kailueke.gitlab.io/" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars0.githubusercontent.com/u/1189130?v=4?s=100" width="100px;" alt="Kai Lüke"/><br /><sub><b>Kai Lüke</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=pothos" target="_blank" rel="nofollow noopener noreferrer" title="Code">💻</a></td> <td align="center"><a href="http://www.data-factory.net/" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars2.githubusercontent.com/u/998558?v=4?s=100" width="100px;" alt="Sebastian Blank"/><br /><sub><b>Sebastian Blank</b></sub></a><br /><a href="#example-blankse" title="Examples">💡</a></td> <td align="center"><a href="https://www.ghsvs.de" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars2.githubusercontent.com/u/20780646?v=4?s=100" width="100px;" alt="ReLater"/><br /><sub><b>ReLater</b></sub></a><br /><a href="#maintenance-ReLater" title="Maintenance">🚧</a></td> <td align="center"><a href="https://github.com/julian-zatloukal" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars3.githubusercontent.com/u/58230917?v=4?s=100" width="100px;" alt="julian-zatloukal"/><br /><sub><b>julian-zatloukal</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=julian-zatloukal" target="_blank" rel="nofollow noopener noreferrer" title="Documentation">📖</a></td> <td align="center"><a href="http://www.neoskop.de/" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars1.githubusercontent.com/u/1702250?v=4?s=100" width="100px;" alt="Maik Jablonski"/><br /><sub><b>Maik Jablonski</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=jablonski" target="_blank" rel="nofollow noopener noreferrer" title="Documentation">📖</a></td> </tr> <tr> <td align="center"><a href="https://github.com/yashha" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars0.githubusercontent.com/u/4728786?v=4?s=100" width="100px;" alt="yashha"/><br /><sub><b>yashha</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=yashha" target="_blank" rel="nofollow noopener noreferrer" title="Code">💻</a></td> <td align="center"><a href="http://danburzo.ro/" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars3.githubusercontent.com/u/205375?v=4?s=100" width="100px;" alt="Dan Burzo"/><br /><sub><b>Dan Burzo</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=danburzo" target="_blank" rel="nofollow noopener noreferrer" title="Code">💻</a></td> <td align="center"><a href="https://github.com/CommanderRoot" target="_blank" rel="nofollow noopener noreferrer"><img src="https://avatars.githubusercontent.com/u/4395417?v=4?s=100" width="100px;" alt="Tobias Speicher"/><br /><sub><b>Tobias Speicher</b></sub></a><br /><a href="https://github.com/mnater/Hyphenopoly/commits?author=CommanderRoot" target="_blank" rel="nofollow noopener noreferrer" title="Code">💻</a></td> </tr> </tbody> <tfoot><div class="base">    </div> </tfoot></table><div class="base"><br></div><!-- markdownlint-restore --><!-- prettier-ignore-end --><div class="base"><br></div><!-- ALL-CONTRIBUTORS-LIST:END --><div class="base"><br></div><div class="base">This project follows the <a href="https://github.com/all-contributors/all-contributors" title="all-contributors" target="_blank" rel="nofollow noopener noreferrer">all-contributors</a> specification. Contributions of any kind welcome!</div><div class="base"><br></div></div></div></div><div id="footer"><div class="footer-box"><div class="footer-info"><div class="fi-logo"><img src="/img/footer-logo.png" width="65"/></div><div class="fi-intro"><strong>为什么是最好的 GitHub 项目?</strong><p>技术的发展速度比以往任何时候都快,技术正在全速创新。几乎每天都会发布惊人的开源项目。</p><p><br/></p><strong>如何了解最新趋势?</strong><p>如何快速检查真正重要的项目,现在而不是 6 个月前?</p><p>探客时代每天拍摄 GitHub stars 的 “快照”,以获得 2000 多个项目的精选列表,以检测过去几个月的趋势。</p><p><br/></p><strong>你想要更多的项目吗?</strong><p>我们不会扫描 GitHub 上的所有现有项目,而是根据我们的经验和我们在 Internet 上阅读的内容,专注于我们发现 “有趣” 的精选项目列表。想要了解的项目可以提交给我们。</p></div><div class="fi-qrcode"><h3>关注公众号</h3><img src="/img/qrcode.jpg" width="272" height="272"/></div></div><div class="footer-copyright">© 2022, 北京探客时代网络科技有限公司 <br/><a href="http://beian.miit.gov.cn" target="_blank" rel="nofollow noopener noreferrer">京 ICP 备 2022008592 号</a> <a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11011402012574" target="_blank" rel="nofollow noopener noreferrer">京公网安备 11011402012574 号</a> <a href="contact.html" target="_blank" rel="nofollow noopener noreferrer">信息举报</a></div></div></div><script>function request(t,e,n){$.ajax({url:t,type:"get",data:e,dataType:"json",success:function(t){n(t.data)}})}function levenshtein(t,e){t=t.toLowerCase(),e=e.toLowerCase();for(var n=t.length,o=e.length,i=[],a=0;a<n+1;a++)i.push(new Array(o+1));for(a=0;a<=n;a++)i[a][0]=a;for(a=0;a<=o;a++)i[0][a]=a;var l,s=t.split(""),r=e.split("");for(a=1;a<=n;a++)for(var c=1;c<=o;c++){l=s[a-1]==r[c-1]?0:1;var u=i[a-1][c-1]+l,h=i[a][c-1]+1,f=i[a-1][c]+1;i[a][c]=Math.min(u,h,f)}return 1-i[n][o]/Math.max(t.length,e.length)}$(function(){var t=[],e=[];request("https://api.tkcnn.com/gh/tag/all",null,function(e){t=e;var n=window.location.href.match(/([\w-]+)\.html/);if(n){var o=t.find(t=>t.code===n[1]);o&&$(".select-set").val(o.name)}}),$(".select-set").on("click input",function(){var n=$(this).val();if(n=n.replace(/(^\s+|\s+$)/g,""),e=[],""===n.replace(/s/g,""))e=t;else{for(var o,i=0;i<t.length;i++)(o=levenshtein(n,t[i].name))>=.1&&e.push({name:t[i].name,code:t[i].code,count:t[i].count,similar:o});e.sort(function(t,e){return e.similar-t.similar})}var a="";for(i=0;i<e.length;i++)a+=`<button>${e[i].name} (${e[i].count})</button>`;$(".select-list").html(a)}),$(".select-list").on("click","button",function(t){$(".select-list").hide(),setTimeout(function(){$(".select-list").css("display","")},200),window.location.href=window.location.origin+`/repo/${e[$(this).index()].code}.html`})});</script></body></html>