| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace App\Docsets; |
||||
| 4 | |||||
| 5 | use Godbout\DashDocsetBuilder\Docsets\BaseDocset; |
||||
| 6 | use Illuminate\Support\Collection; |
||||
| 7 | use Illuminate\Support\Facades\Storage; |
||||
| 8 | use Illuminate\Support\Str; |
||||
| 9 | use Wa72\HtmlPageDom\HtmlPageCrawler; |
||||
| 10 | |||||
| 11 | class TailwindCSS extends BaseDocset |
||||
| 12 | { |
||||
| 13 | public const CODE = 'tailwindcss'; |
||||
| 14 | public const NAME = 'Tailwind CSS'; |
||||
| 15 | public const URL = 'tailwindcss.com'; |
||||
| 16 | public const INDEX = 'docs/installation.html'; |
||||
| 17 | public const PLAYGROUND = 'https://play.tailwindcss.com/'; |
||||
| 18 | public const ICON_16 = 'favicon-16x16.png'; |
||||
| 19 | public const ICON_32 = 'favicon-32x32.png'; |
||||
| 20 | public const EXTERNAL_DOMAINS = [ |
||||
| 21 | ]; |
||||
| 22 | |||||
| 23 | |||||
| 24 | public function grab(): bool |
||||
| 25 | { |
||||
| 26 | $toIgnore = implode('|', [ |
||||
| 27 | 'blog.tailwindcss.com', |
||||
| 28 | 'v1.tailwindcss.com', |
||||
| 29 | 'v2.tailwindcss.com' |
||||
| 30 | ]); |
||||
| 31 | |||||
| 32 | system( |
||||
| 33 | "echo; wget tailwindcss.com/docs \ |
||||
| 34 | --mirror \ |
||||
| 35 | --trust-server-names \ |
||||
| 36 | --reject-regex='{$toIgnore}' \ |
||||
| 37 | --page-requisites \ |
||||
| 38 | --adjust-extension \ |
||||
| 39 | --convert-links \ |
||||
| 40 | --span-hosts \ |
||||
| 41 | --domains={$this->externalDomains()} \ |
||||
| 42 | --directory-prefix=storage/{$this->downloadedDirectory()} \ |
||||
| 43 | -e robots=off \ |
||||
| 44 | --quiet \ |
||||
| 45 | --show-progress", |
||||
| 46 | $result |
||||
| 47 | ); |
||||
| 48 | |||||
| 49 | return $result === 0; |
||||
| 50 | } |
||||
| 51 | |||||
| 52 | 16 | public function entries(string $file): Collection |
|||
| 53 | { |
||||
| 54 | 16 | $crawler = HtmlPageCrawler::create(Storage::get($file)); |
|||
| 55 | |||||
| 56 | 16 | $entries = collect(); |
|||
| 57 | |||||
| 58 | 16 | $entries = $entries->union($this->resourceEntries($crawler, $file)); |
|||
| 59 | 16 | $entries = $entries->union($this->guideEntries($crawler, $file)); |
|||
| 60 | 16 | $entries = $entries->union($this->sectionEntries($crawler, $file)); |
|||
| 61 | |||||
| 62 | 16 | return $entries; |
|||
| 63 | } |
||||
| 64 | |||||
| 65 | 16 | protected function resourceEntries(HtmlPageCrawler $crawler, string $file) |
|||
| 66 | { |
||||
| 67 | 16 | $entries = collect(); |
|||
| 68 | |||||
| 69 | 16 | if (Str::contains($file, "{$this->url()}/resources.html")) { |
|||
| 70 | 16 | $crawler->filter('h2')->each(function (HtmlPageCrawler $node) use ($entries, $file) { |
|||
| 71 | 16 | $entries->push([ |
|||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 72 | 16 | 'name' => $this->cleanAnchorText($node->text()), |
|||
| 73 | 16 | 'type' => 'Resource', |
|||
| 74 | 16 | 'path' => Str::after($file . '#' . Str::slug($node->text()), $this->innerDirectory()), |
|||
| 75 | 8 | ]); |
|||
| 76 | 16 | }); |
|||
| 77 | |||||
| 78 | 16 | $crawler->filter('h3')->each(function (HtmlPageCrawler $node) use ($entries, $file) { |
|||
| 79 | 16 | $entries->push([ |
|||
|
0 ignored issues
–
show
array('name' => $this->c...his->innerDirectory())) of type array<string,string> is incompatible with the type Illuminate\Support\TValue expected by parameter $values of Illuminate\Support\Collection::push().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 80 | 16 | 'name' => $this->cleanAnchorText($node->text()), |
|||
| 81 | 16 | 'type' => 'Section', |
|||
| 82 | 16 | 'path' => Str::after($file . '#' . Str::slug($node->text()), $this->innerDirectory()), |
|||
| 83 | 8 | ]); |
|||
| 84 | 16 | }); |
|||
| 85 | |||||
| 86 | 16 | return $entries; |
|||
| 87 | } |
||||
| 88 | 8 | } |
|||
| 89 | |||||
| 90 | 16 | protected function guideEntries(HtmlPageCrawler $crawler, string $file) |
|||
| 91 | { |
||||
| 92 | 16 | $entries = collect(); |
|||
| 93 | |||||
| 94 | 16 | if (Str::contains($file, "{$this->url()}/docs.html")) { |
|||
| 95 | 8 | $itemsToIgnore = collect(['Release Notes', 'Typography', 'Forms', 'Aspect Ratio', 'Line Clamp']); |
|||
|
0 ignored issues
–
show
array('Release Notes', '...t Ratio', 'Line Clamp') of type array<integer,string> is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 96 | |||||
| 97 | 4 | $crawler |
|||
| 98 | 8 | ->filter('nav#nav li.mt-8 a') |
|||
| 99 | 8 | ->each(function (HtmlPageCrawler $node) use ($entries, $itemsToIgnore) { |
|||
| 100 | 8 | if (! $itemsToIgnore->contains($node->text())) { |
|||
| 101 | 8 | $entries->push([ |
|||
|
0 ignored issues
–
show
array('name' => trim($no... . $node->attr('href')) of type array<string,string> is incompatible with the type Illuminate\Support\TValue expected by parameter $values of Illuminate\Support\Collection::push().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 102 | 8 | 'name' => trim($node->text()), |
|||
| 103 | 8 | 'type' => 'Guide', |
|||
| 104 | 8 | 'path' => $this->url() . '/' . $node->attr('href'), |
|||
| 105 | 4 | ]); |
|||
| 106 | } |
||||
| 107 | 8 | }); |
|||
| 108 | } |
||||
| 109 | |||||
| 110 | 16 | return $entries; |
|||
| 111 | } |
||||
| 112 | |||||
| 113 | 16 | protected function sectionEntries(HtmlPageCrawler $crawler, string $file) |
|||
| 114 | { |
||||
| 115 | 16 | $entries = collect(); |
|||
| 116 | |||||
| 117 | 16 | $crawler->filter('h2')->each(function (HtmlPageCrawler $node) use ($entries, $file) { |
|||
| 118 | 16 | $entries->push([ |
|||
|
0 ignored issues
–
show
array('name' => $this->c...his->innerDirectory())) of type array<string,string> is incompatible with the type Illuminate\Support\TValue expected by parameter $values of Illuminate\Support\Collection::push().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 119 | 16 | 'name' => $this->cleanAnchorText($node->text()), |
|||
| 120 | 16 | 'type' => 'Section', |
|||
| 121 | 16 | 'path' => Str::after($file . '#' . Str::slug($node->text()), $this->innerDirectory()), |
|||
| 122 | 8 | ]); |
|||
| 123 | 16 | }); |
|||
| 124 | |||||
| 125 | 16 | return $entries; |
|||
| 126 | } |
||||
| 127 | |||||
| 128 | 16 | public function format(string $file): string |
|||
| 129 | { |
||||
| 130 | 16 | $crawler = HtmlPageCrawler::create(Storage::get($file)); |
|||
| 131 | |||||
| 132 | 16 | $this->removeTopbar($crawler); |
|||
| 133 | 16 | $this->removeLeftSidebar($crawler); |
|||
| 134 | 16 | $this->removeRightSidebar($crawler); |
|||
| 135 | 16 | $this->removeClassesOverflow($crawler); |
|||
| 136 | |||||
| 137 | 16 | $this->updateContainerWidth($crawler); |
|||
| 138 | 16 | $this->tweakFooter($crawler); |
|||
| 139 | |||||
| 140 | 16 | $this->removeDarkModeInHTMLTag($crawler); |
|||
| 141 | 16 | $this->ignoreDarkModeForSomeColors($crawler); |
|||
| 142 | |||||
| 143 | 16 | $this->removeUnwantedJavaScript($crawler); |
|||
| 144 | |||||
| 145 | 16 | $this->insertOnlineRedirection($crawler, $file); |
|||
| 146 | 16 | $this->insertDashTableOfContents($crawler, $file); |
|||
| 147 | |||||
| 148 | 16 | return $crawler->saveHTML(); |
|||
| 149 | } |
||||
| 150 | |||||
| 151 | 16 | protected function removeTopbar(HtmlPageCrawler $crawler) |
|||
| 152 | { |
||||
| 153 | 16 | $crawler->filter('div.sticky.top-0')->remove(); |
|||
| 154 | 8 | } |
|||
| 155 | |||||
| 156 | 16 | protected function removeLeftSidebar(HtmlPageCrawler $crawler) |
|||
| 157 | { |
||||
| 158 | 16 | $crawler->filter('div.hidden.fixed.z-20.inset-0.overflow-y-auto')->remove(); |
|||
| 159 | 8 | } |
|||
| 160 | |||||
| 161 | 16 | protected function removeRightSidebar(HtmlPageCrawler $crawler) |
|||
| 162 | { |
||||
| 163 | 16 | $crawler->filter('div.fixed.z-20.bottom-0.py-10.px-8.overflow-y-auto.hidden')->remove(); |
|||
| 164 | 8 | } |
|||
| 165 | |||||
| 166 | 16 | protected function removeClassesOverflow(HtmlPageCrawler $crawler) |
|||
| 167 | { |
||||
| 168 | 16 | $crawler->filter('#class-reference + div') |
|||
| 169 | 16 | ->removeClass('overflow-hidden') |
|||
| 170 | 16 | ->removeClass('lg:overflow-auto') |
|||
| 171 | 16 | ->addClass('overflow-auto') |
|||
| 172 | 8 | ; |
|||
| 173 | |||||
| 174 | 16 | $crawler->filter('#class-reference ~ div:nth-of-type(2)') |
|||
| 175 | 16 | ->remove() |
|||
| 176 | 8 | ; |
|||
| 177 | 8 | } |
|||
| 178 | |||||
| 179 | 16 | protected function updateContainerWidth(HtmlPageCrawler $crawler) |
|||
| 180 | { |
||||
| 181 | 16 | $crawler->filter('div.max-w-8xl.mx-auto.px-4 > div:first-child') |
|||
| 182 | 16 | ->removeClass('lg:pl-[19.5rem]') |
|||
| 183 | 8 | ; |
|||
| 184 | 16 | $crawler->filter('div.max-w-3xl.mx-auto.pt-10') |
|||
| 185 | 16 | ->removeClass('max-w-3xl') |
|||
| 186 | 16 | ->removeClass('mx-auto') |
|||
| 187 | 16 | ->removeClass('xl:max-w-none') |
|||
| 188 | 16 | ->removeClass('xl:mr-[15.5rem]') |
|||
| 189 | 16 | ->removeClass('xl:pr-16') |
|||
| 190 | 8 | ; |
|||
| 191 | 8 | } |
|||
| 192 | |||||
| 193 | 16 | protected function tweakFooter(HtmlPageCrawler $crawler) |
|||
| 194 | { |
||||
| 195 | 16 | $crawler->filter('footer div:first-child') |
|||
| 196 | 16 | ->removeClass('mb-10') |
|||
| 197 | 16 | ->addClass('pb-10') |
|||
| 198 | 8 | ; |
|||
| 199 | |||||
| 200 | 16 | $crawler->filter('footer div:last-child') |
|||
| 201 | 16 | ->remove() |
|||
| 202 | 8 | ; |
|||
| 203 | 8 | } |
|||
| 204 | |||||
| 205 | 16 | protected function removeDarkModeInHTMLTag(HtmlPageCrawler $crawler) |
|||
| 206 | { |
||||
| 207 | 16 | $crawler->filter('html') |
|||
| 208 | 16 | ->removeClass('dark') |
|||
| 209 | 8 | ; |
|||
| 210 | 8 | } |
|||
| 211 | |||||
| 212 | 16 | protected function ignoreDarkModeForSomeColors(HtmlPageCrawler $crawler) |
|||
| 213 | { |
||||
| 214 | 16 | $this->ignoreDarkModeForDefaultColorPaletteSection($crawler); |
|||
| 215 | 16 | $this->ignoreDarkModeForVariousColorTables($crawler); |
|||
| 216 | 8 | } |
|||
| 217 | |||||
| 218 | 16 | protected function ignoreDarkModeForDefaultColorPaletteSection(HtmlPageCrawler $crawler) |
|||
| 219 | { |
||||
| 220 | 16 | $crawler->filter('div.h-10.w-full.rounded.ring-1.ring-inset')->addClass('dash-ignore-dark-mode'); |
|||
| 221 | 8 | } |
|||
| 222 | |||||
| 223 | 16 | protected function ignoreDarkModeForVariousColorTables(HtmlPageCrawler $crawler) |
|||
| 224 | { |
||||
| 225 | 16 | $crawler->filter('h2 + div td:last-child')->addClass('dash-ignore-dark-mode'); |
|||
| 226 | 8 | } |
|||
| 227 | |||||
| 228 | 16 | protected function removeUnwantedJavaScript(HtmlPageCrawler $crawler) |
|||
| 229 | { |
||||
| 230 | 16 | $crawler->filter('script')->remove(); |
|||
| 231 | 8 | } |
|||
| 232 | |||||
| 233 | 16 | protected function insertOnlineRedirection(HtmlPageCrawler $crawler, string $file) |
|||
| 234 | { |
||||
| 235 | 16 | $onlineUrl = Str::substr(Str::after($file, $this->innerDirectory()), 1, -5); |
|||
| 236 | |||||
| 237 | 16 | $crawler->filter('html')->prepend("<!-- Online page at https://$onlineUrl -->"); |
|||
| 238 | 8 | } |
|||
| 239 | |||||
| 240 | 16 | protected function insertDashTableOfContents(HtmlPageCrawler $crawler, string $file) |
|||
| 241 | { |
||||
| 242 | 16 | if (! Str::contains($file, "{$this->url()}/docs.html")) { |
|||
| 243 | 16 | $crawler->filter('body') |
|||
| 244 | 16 | ->before('<a name="//apple_ref/cpp/Section/Top" class="dashAnchor"></a>'); |
|||
| 245 | |||||
| 246 | 16 | $crawler->filter('h2, h3')->each(function (HtmlPageCrawler $node) { |
|||
| 247 | 16 | $node->prepend( |
|||
| 248 | 16 | '<a id="' . Str::slug($node->text()) . '" name="//apple_ref/cpp/Section/' . rawurlencode($this->cleanAnchorText($node->text())) . '" class="dashAnchor"></a>' |
|||
| 249 | 8 | ); |
|||
| 250 | 16 | }); |
|||
| 251 | } |
||||
| 252 | 8 | } |
|||
| 253 | |||||
| 254 | 24 | protected function cleanAnchorText($anchorText) |
|||
| 255 | { |
||||
| 256 | 24 | return trim(preg_replace('/\s+/', ' ', $anchorText)); |
|||
| 257 | } |
||||
| 258 | } |
||||
| 259 |