lookbook 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +233 -5
  3. data/app/assets/lookbook/css/app.css +15 -3
  4. data/app/assets/lookbook/js/app.js +8 -0
  5. data/app/assets/lookbook/js/components/app.js +55 -0
  6. data/app/assets/lookbook/js/components/embed.js +89 -0
  7. data/app/assets/lookbook/js/components/filter.js +13 -2
  8. data/app/assets/lookbook/js/components/nav-group.js +3 -6
  9. data/app/assets/lookbook/js/components/nav-item.js +3 -1
  10. data/app/assets/lookbook/js/components/nav.js +8 -15
  11. data/app/assets/lookbook/js/components/page.js +19 -41
  12. data/app/assets/lookbook/js/components/sidebar.js +16 -1
  13. data/app/assets/lookbook/js/components/splitter.js +1 -1
  14. data/app/assets/lookbook/js/components/tabs.js +3 -1
  15. data/app/assets/lookbook/js/embed.js +1 -0
  16. data/app/assets/lookbook/js/lib/split.js +0 -6
  17. data/app/assets/lookbook/js/stores/layout.js +3 -0
  18. data/app/assets/lookbook/js/stores/pages.js +5 -0
  19. data/app/assets/lookbook/js/stores/sidebar.js +2 -1
  20. data/app/controllers/lookbook/application_controller.rb +28 -0
  21. data/app/controllers/lookbook/page_controller.rb +20 -0
  22. data/app/controllers/lookbook/pages_controller.rb +40 -0
  23. data/app/controllers/lookbook/{app_controller.rb → previews_controller.rb} +39 -67
  24. data/app/helpers/lookbook/application_helper.rb +8 -61
  25. data/app/helpers/lookbook/component_helper.rb +40 -0
  26. data/app/helpers/lookbook/output_helper.rb +15 -0
  27. data/app/helpers/lookbook/page_helper.rb +46 -0
  28. data/app/helpers/lookbook/preview_helper.rb +1 -1
  29. data/app/views/layouts/lookbook/application.html.erb +30 -0
  30. data/app/views/layouts/lookbook/basic.html.erb +7 -0
  31. data/app/views/layouts/lookbook/preview.html.erb +5 -1
  32. data/app/views/layouts/lookbook/skeleton.html.erb +28 -0
  33. data/app/views/lookbook/components/_branding.html.erb +8 -0
  34. data/app/views/lookbook/components/_code.html.erb +2 -2
  35. data/app/views/lookbook/components/_copy_button.html.erb +11 -0
  36. data/app/views/lookbook/components/_drawer.html.erb +4 -16
  37. data/app/views/lookbook/components/_embed.html.erb +39 -0
  38. data/app/views/lookbook/components/_filter.html.erb +8 -5
  39. data/app/views/lookbook/components/_header.html.erb +2 -4
  40. data/app/views/lookbook/components/_icon.html.erb +2 -2
  41. data/app/views/lookbook/components/_nav.html.erb +7 -8
  42. data/app/views/lookbook/components/_nav_group.html.erb +1 -1
  43. data/app/views/lookbook/components/_nav_item.html.erb +4 -3
  44. data/app/views/lookbook/components/_nav_page.html.erb +22 -0
  45. data/app/views/lookbook/components/_nav_preview.html.erb +1 -1
  46. data/app/views/lookbook/components/_not_found.html.erb +11 -0
  47. data/app/views/lookbook/components/_param.html.erb +1 -1
  48. data/app/views/lookbook/components/_preview.html.erb +16 -10
  49. data/app/views/lookbook/components/_sidebar.html.erb +55 -0
  50. data/app/views/lookbook/pages/not_found.html.erb +15 -0
  51. data/app/views/lookbook/pages/show.html.erb +71 -0
  52. data/app/views/lookbook/previews/error.html.erb +1 -0
  53. data/app/views/lookbook/{inputs → previews/inputs}/_select.html.erb +0 -0
  54. data/app/views/lookbook/{inputs → previews/inputs}/_text.html.erb +0 -0
  55. data/app/views/lookbook/{inputs → previews/inputs}/_textarea.html.erb +0 -0
  56. data/app/views/lookbook/{inputs → previews/inputs}/_toggle.html.erb +0 -0
  57. data/app/views/lookbook/{not_found.html.erb → previews/not_found.html.erb} +2 -2
  58. data/app/views/lookbook/{panels → previews/panels}/_notes.html.erb +2 -2
  59. data/app/views/lookbook/{panels → previews/panels}/_output.html.erb +0 -0
  60. data/app/views/lookbook/{panels → previews/panels}/_params.html.erb +0 -0
  61. data/app/views/lookbook/{panels → previews/panels}/_preview.html.erb +0 -0
  62. data/app/views/lookbook/{panels → previews/panels}/_source.html.erb +0 -0
  63. data/app/views/lookbook/{show.html.erb → previews/show.html.erb} +0 -0
  64. data/config/routes.rb +9 -4
  65. data/lib/lookbook/code_formatter.rb +22 -1
  66. data/lib/lookbook/code_inspector.rb +73 -0
  67. data/lib/lookbook/collection.rb +110 -8
  68. data/lib/lookbook/engine.rb +59 -32
  69. data/lib/lookbook/features.rb +1 -1
  70. data/lib/lookbook/markdown.rb +31 -0
  71. data/lib/lookbook/page.rb +146 -0
  72. data/lib/lookbook/page_collection.rb +11 -0
  73. data/lib/lookbook/parser.rb +2 -0
  74. data/lib/lookbook/preview.rb +52 -56
  75. data/lib/lookbook/preview_collection.rb +15 -0
  76. data/lib/lookbook/preview_example.rb +27 -29
  77. data/lib/lookbook/preview_group.rb +12 -6
  78. data/lib/lookbook/utils.rb +74 -0
  79. data/lib/lookbook/version.rb +1 -1
  80. data/lib/lookbook.rb +18 -1
  81. data/public/lookbook-assets/css/app.css +1 -1
  82. data/public/lookbook-assets/css/app.css.map +1 -1
  83. data/public/lookbook-assets/js/app.js +1 -1
  84. data/public/lookbook-assets/js/app.js.map +1 -1
  85. data/public/lookbook-assets/js/embed.js +2 -0
  86. data/public/lookbook-assets/js/embed.js.map +1 -0
  87. metadata +44 -18
  88. data/app/assets/lookbook/js/lib/utils.js +0 -3
  89. data/app/views/layouts/lookbook/app.html.erb +0 -60
  90. data/app/views/lookbook/error.html.erb +0 -1
  91. data/lib/lookbook/taggable.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca448bf7c2f60a27c0cec712165fb3d6eb230739c93e9cee559ebd81a5dcd2ad
4
- data.tar.gz: 4ef5134c7853c526ced67e56568250575934f6b25d4e558f21275e13e968e07b
3
+ metadata.gz: 40ec6640c4c188089ff874cb11fb129ddc6c541d078681b1b3dea133f3afaedf
4
+ data.tar.gz: 331f4693438b931929b40d58c4b7c0d0cf0ece9089891d8e5863c8971f5e4138
5
5
  SHA512:
6
- metadata.gz: 75cda1bd9921636c6a76998f4228f6193ad5d8a3da10e2c7fef3434a52ffef937e40b5032245155ff47a340c8976e926db4e1d6fa267606bc923d82f4d7fadb7
7
- data.tar.gz: 2e719c65802e23e4e4dfe8308eb01208181454cc76fd043de97916537421016c8b20ef8f7cc2e944c48f04c46ffc31eda50024760425774e89d1544cf4bd5570
6
+ metadata.gz: a538f52d85d4d5e833c2549442b001e44e749b42e87a5bbe4676e718300405b7b9b32d6efd18b1ce23dedcfefce8c82a4f4128236fa83dcff570b7f609b15004
7
+ data.tar.gz: a5a6fe4dbba3e951441b5c4fb64b6a268de687c99bb7779892d5e819eeae19fbcee8d740c749808671bf65202fc6af581a958869f3fb67c6f9aac5c5af364007
data/README.md CHANGED
@@ -12,6 +12,12 @@
12
12
 
13
13
  ---
14
14
 
15
+ <div align="center">
16
+ <a href="#installing">Installing</a> • <a href="#previews">Previews</a> • <a href="#pages">Pages</a> • <a href="#config">Configuration</a>
17
+ </div>
18
+
19
+ ---
20
+
15
21
  **Lookbook gives [ViewComponent](http://viewcomponent.org/)-based projects a _ready-to-go_ development UI for navigating, inspecting and interacting with component previews.**
16
22
 
17
23
  It uses (and extends) the native [ViewComponent preview functionality](https://viewcomponent.org/guide/previews.html), so you don't need to learn a new DSL or create any extra files to get up and running.
@@ -29,6 +35,7 @@ Lookbook uses [RDoc/Yard-style comment tags](#annotating-preview-files) to exten
29
35
  - Use comment tag annotations for granular customisation of the preview experience
30
36
  - Fully compatible with standard the ViewComponent preview system
31
37
  - In-browser live-editable preview parameters (similar to basic Storybook Controls/Knobs)
38
+ - [**Experimental**] Markdown-powerered documentation pages with embeddable previews
32
39
 
33
40
  ## Lookbook demo
34
41
 
@@ -73,13 +80,13 @@ If you would like to expose the Lookbook UI in production as well as in developm
73
80
  2. Add `config.view_component.show_previews = true` to `config/environments/production.rb`
74
81
 
75
82
 
76
- ## Usage
83
+ <h2 id="previews">Previews</h2>
77
84
 
78
85
  You don't need to do anything special to see your ViewComponent previews and examples in Lookbook - just create them as normal and they'll automatically appear in the Lookbook UI. Preview templates, custom layouts and even bespoke [preview controllers](https://viewcomponent.org/guide/previews.html#configuring-preview-controller) should all work as you would expect.
79
86
 
80
87
  > If you are new to ViewComponent development, checkout the ViewComponent [documentation](https://viewcomponent.org/guide/) on how to get started developing your components and [creating previews](https://viewcomponent.org/guide/previews.html).
81
88
 
82
- ## Annotating preview files
89
+ ### Annotating preview files
83
90
 
84
91
  Lookbook parses [Yard-style comment tags](https://rubydoc.info/gems/yard/file/docs/Tags.md) in your preview classes to customise and extend the standard ViewComponent preview experience:
85
92
 
@@ -474,11 +481,217 @@ class ProfileCardComponentPreview < ViewComponent::Preview
474
481
  end
475
482
  ```
476
483
 
477
- ## Configuration
484
+ ---
485
+
486
+ <h2 id="pages">🚧 Documentation Pages [experimental]</h2>
487
+
488
+ If you need to add more long-form documentation to live alongside your component previews you can do so using Lookbook's markdown-powered `pages` system.
489
+
490
+ > ⚠️ This feature is currently flagged as an **experimental** feature which requires [feature opt-in](#experimental-features) to use. Its API and implementation may change before it is released.
491
+ >
492
+ > To enable support for pages in your project, add `config.lookbook.experimental_features = [:pages]` into your application configuration file.
493
+
494
+ ### Pages demo
495
+
496
+ For an example of some pages in Lookbook, check out the [example pages](https://lookbook-demo-app.herokuapp.com/lookbook) in the Lookbook demo app and the associated [page files](https://github.com/allmarkedup/lookbook-demo/tree/main/test/components/docs) in the demo repo.
497
+
498
+ ### Usage
499
+
500
+ By default, pages should be placed in the `test/components/docs` directory (although this can be customised) and can be nested in directories as deeply as required.
501
+
502
+ Pages must have either a `.html.erb` or a `.md.erb` file extension. All pages are rendered as ERB templates but `.md.erb` files will also additionally be run through a markdown parser.
503
+
504
+ Pages can optionally make use of a **YAML frontmatter block** to customise the behaviour and content of the page itself.
505
+
506
+ An example page might look like this:
507
+
508
+ ```markdown
509
+ ---
510
+ title: An example page
511
+ label: Nice example
512
+ ---
513
+
514
+ This is an example page. If it has a `.md.erb` file extension its
515
+ contents will be run through a Markdown parser/renderer before display.
516
+
517
+ Fenced code blocks are fully supported and will be highlighted appropriately.
518
+
519
+ ERB can be used in here.
520
+ The template will be rendered **before** being parsed as Markdown.
521
+
522
+ You can can access data about the page using the `@page` variable.
523
+ The title of this page is "<%= @page.title %>".
524
+ ```
525
+
526
+ ### YAML Frontmatter
527
+
528
+ Pages can use an optional YAML frontmatter block to configure the behaviour of the page and to provide custom data, if required.
529
+
530
+ The following page options can be customised via frontmatter:
531
+
532
+ * `id` - a custom page ID that can be used for linking to it from other pages
533
+ * `label` - The name of the page that will be displayed in the navigation (auto-generated from the file name if not set)
534
+ * `title` - The main page title displayed on the page (defaults to the label value if not set).
535
+ * `hidden` - If `false` the page will not appear in the navigation but will still be accessible at it's URL (useful for pages that are still in development) [default: `true`]
536
+ * `landing` - Set to `true` to use the page as the Lookbook landing page [default: `false`]
537
+ * `header` - Set to `false` to hide the page header containing the page title [default: `true`]
538
+ * `footer` - Set to `false` to hide the page footer containing the previous/next page links [default: `true`]
539
+ * `data` - Optional hash of custom data to make available for use in the page - see info on [page variables](#page-variables) below. [default: `{}`]
478
540
 
479
- Lookbook will use the ViewComponent [configuration](https://viewcomponent.org/api.html#configuration) for your project to find and render your previews so you generally you won't need to configure anything separately.
541
+ #### Frontmatter defaults
480
542
 
481
- However the following Lookbook-specific config options are also available:
543
+ You can set global default values for page options in the application configuration:
544
+
545
+ ```ruby
546
+ # config/application.rb
547
+ config.lookbook.page_options = {
548
+ footer: false,
549
+ data: {
550
+ brand_colors: {
551
+ red: #ff0000
552
+ }
553
+ }
554
+ }
555
+ ```
556
+
557
+ These will be merged with any page-level frontmatter data. Options set in the frontmatter will override those set at the global level (apart from `data`, which will be deep-merged with the any globally defined data).
558
+
559
+ ### Page variables
560
+
561
+ All pages have the following variables available for use in the page template:
562
+
563
+ * `@page` - The current page object
564
+ * `@next_page` - The next page object (if available)
565
+ * `@previous_page` - The previous page object (if available)
566
+ * `@pages` - Collection of all pages
567
+
568
+ Page objects have access to frontmatter variables:
569
+
570
+ ```ruby
571
+ The page title is <%= @page.title %>
572
+
573
+ Our brand color hex value is <%= @page.data[:brand_colors][:red] %>
574
+ ```
575
+
576
+ ### Ordering pages and directories
577
+
578
+ If you want to enforce a specific order for pages and directories in the Lookbook navigation you can prefix the file/directory basename with an 'order number' integer value followed by an underscore or hyphen.
579
+
580
+ For example: `01_example_page.md.erb` will be displayed first in the navigation (`01`) within the directory it is in.
581
+
582
+ The integer value will be parsed out from the filename so that it doesn't appear in navigation labels or URLs, and the value itself will be used as a 'position' number when sorting the navigation items.
583
+
584
+ For example, an ordered directory of pages might look like:
585
+
586
+ ```
587
+ test/components/docs/
588
+ ├── 01_overview.md.erb
589
+ ├── 02_implementation_notes/
590
+ │ ├── 01_slots.md.erb
591
+ │ └── 02_html_attributes.md.erb
592
+ └── 03_helpful_examples/
593
+ ├── 01_basic_components.md.erb
594
+ └── 02_complex_components.md.erb
595
+ ```
596
+
597
+ Without the number prefixes on the file names the pages may not have appeared in the navigation in the desired order.
598
+
599
+ ### Linking to other pages
600
+
601
+ You can get the path to a page using the `page_path` helper. This accepts a page `id` (as a `Symbol`) or a page object:
602
+
603
+ ```markdown
604
+ Visit the [about page](<%= page_path :about %>)
605
+
606
+ Go to the [next page](<%= page_path @next_page %>)
607
+ ```
608
+
609
+ Page ids can be set in the YAML frontmatter block for that page:
610
+
611
+ ```
612
+ ---
613
+ id: about
614
+ ---
615
+
616
+ This is the about page.
617
+ ```
618
+
619
+ ### Embedding previews
620
+
621
+ You can embed preview examples from your project directly into the documentation pages using the `embed` helper, which renders an iframe with the rendered preview in it at any point in your document.
622
+
623
+ The output looks like this:
624
+
625
+ <img src=".github/assets/preview_embed.png">
626
+
627
+ To specify which preview example to render, the helper accepts a preview class and a method name (as a symbol), like this:
628
+
629
+ ```erb
630
+ <%= embed Elements:ButtonComponentPreview, :default %>
631
+ ```
632
+
633
+ #### Preview params
634
+
635
+ If you have configured your examples to accept preview params (see the [`@param`](#param-tag) docs), then you can supply values for those params when rendering the embedded preview:
636
+
637
+ ```erb
638
+ <%= embed Elements:ButtonComponentPreview, :default, params: {
639
+ icon: "plus",
640
+ text: "Add new"
641
+ } %>
642
+ ```
643
+
644
+ ### Displaying code
645
+
646
+ You can use language-scoped [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) in the markdown file to render nicely highlighted code examples.
647
+
648
+ However, if you are not using Markdown, or need a little more control, you can use the `code` helper instead:
649
+
650
+ ```erb
651
+ <%= code do %>
652
+ # code goes here
653
+ <% end %>
654
+ ```
655
+
656
+ The default language is `ruby`. To highlight a different language you need to specify it's name as an argument:
657
+
658
+ ```erb
659
+ <%= code :html do %>
660
+ <!-- code goes here -->
661
+ <% end %>
662
+ ```
663
+
664
+ > Lookbook uses [Rouge](https://github.com/rouge-ruby/rouge) for syntax highlighting. You can find a [full list of supported languages here](https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md).
665
+
666
+ ---
667
+
668
+ ### Pages configuration
669
+
670
+ These options can be set in your application configuration files to customise the pages behaviour.
671
+
672
+ #### `page_paths`
673
+
674
+ An array of directories to look for pages in.
675
+ Default: `["test/previews/docs"]`
676
+
677
+ ```ruby
678
+ config.lookbook.page_paths = ["path/to/my/pages"]
679
+ ```
680
+
681
+ #### `page_route`
682
+
683
+ The URL segment used to prefix page routes.
684
+ Default: `pages`
685
+
686
+ ```ruby
687
+ config.lookbook.page_route = `docs`
688
+ ```
689
+
690
+ <h2 id="config">General Configuration</h2>
691
+
692
+ Lookbook will use the ViewComponent [configuration](https://viewcomponent.org/api.html#configuration) for your project to find and render your previews so you generally you won't need to configure much else separately.
693
+
694
+ However the following Lookbook-specific configuration options are also available:
482
695
 
483
696
  ### UI auto-refresh
484
697
 
@@ -508,6 +721,16 @@ config.lookbook.ui_favicon = "/path/to/my/favicon.png"
508
721
 
509
722
  > To disable the favicon entirely, set the value to `false`.
510
723
 
724
+ ### Project name
725
+
726
+ Specify a project name to display in the top left of the UI (instead of the default "Lookbook"):
727
+
728
+ ```ruby
729
+ config.lookbook.project_name = "My Project"
730
+ ```
731
+
732
+ > If you don't want to display a project name at all, set the value to `false`.
733
+
511
734
 
512
735
  <h3 id="experimental-features">Experimental features opt-in</h3>
513
736
 
@@ -523,6 +746,11 @@ To opt into individual experimental features, include the name of the feature in
523
746
  config.lookbook.experimental_features = ["feature_name"]
524
747
  ```
525
748
 
749
+ The current experimental features that can be opted into are:
750
+
751
+ - `pages`: Markdown-powered documentation pages with embeddable previews
752
+
753
+
526
754
  #### Opting into all experimental features (not recommended!)
527
755
 
528
756
  If you want to live life on the bleeding-edge you can opt-in to all current **and future** experimental features (usual caveats apply):
@@ -58,7 +58,7 @@
58
58
  }
59
59
 
60
60
  @layer components {
61
- #nav > ul > li > div {
61
+ .unsectioned > #nav > ul > li > div {
62
62
  @apply py-1 border-b border-gray-300;
63
63
  }
64
64
 
@@ -87,7 +87,7 @@
87
87
  }
88
88
 
89
89
  .code.numbered {
90
- @apply relative pt-3;
90
+ @apply relative;
91
91
  }
92
92
 
93
93
  .code.numbered:before {
@@ -97,7 +97,7 @@
97
97
  }
98
98
 
99
99
  .code.numbered .line {
100
- padding-left: calc(2.7em + 8px);
100
+ padding-left: calc(2.7em + 14px);
101
101
  @apply relative;
102
102
  }
103
103
 
@@ -115,6 +115,18 @@
115
115
  @apply flex-none pr-4;
116
116
  }
117
117
 
118
+ .prose .code {
119
+ @apply !bg-white border border-gray-300 text-gray-600 my-8 text-sm rounded-md py-4 overflow-auto max-w-3xl w-full mx-auto;
120
+ }
121
+
122
+ .prose .code:not(.numbered) .line {
123
+ @apply px-4;
124
+ }
125
+
126
+ .prose .embed {
127
+ @apply my-8;
128
+ }
129
+
118
130
  .resize-handle {
119
131
  @apply flex items-center justify-center h-full w-full border-gray-300 bg-white hover:bg-indigo-100 hover:bg-opacity-20 text-gray-400 hover:text-gray-700 transition select-none touch-none;
120
132
  }
@@ -4,11 +4,13 @@ import Persist from "@alpinejs/persist";
4
4
  import Morph from "@alpinejs/morph";
5
5
  import Tooltip from "@ryangjchandler/alpine-tooltip";
6
6
 
7
+ import app from "./components/app";
7
8
  import page from "./components/page";
8
9
  import inspector from "./components/inspector";
9
10
  import previewWindow from "./components/preview-window";
10
11
  import filter from "./components/filter";
11
12
  import param from "./components/param";
13
+ import sidebar from "./components/sidebar";
12
14
  import nav from "./components/nav";
13
15
  import navItem from "./components/nav-item";
14
16
  import navGroup from "./components/nav-group";
@@ -17,12 +19,14 @@ import tabs from "./components/tabs";
17
19
  import copy from "./components/copy";
18
20
  import code from "./components/code";
19
21
  import sizes from "./components/sizes";
22
+ import embed from "./components/embed";
20
23
 
21
24
  import initFilterStore from "./stores/filter";
22
25
  import initLayoutStore from "./stores/layout";
23
26
  import initNavStore from "./stores/nav";
24
27
  import initSidebarStore from "./stores/sidebar";
25
28
  import initInspectorStore from "./stores/inspector";
29
+ import initPagesStore from "./stores/pages";
26
30
 
27
31
  // Plugins
28
32
 
@@ -37,10 +41,13 @@ Alpine.store("layout", initLayoutStore(Alpine));
37
41
  Alpine.store("nav", initNavStore(Alpine));
38
42
  Alpine.store("sidebar", initSidebarStore(Alpine));
39
43
  Alpine.store("inspector", initInspectorStore(Alpine));
44
+ Alpine.store("pages", initPagesStore(Alpine));
40
45
 
41
46
  // Components
42
47
 
48
+ Alpine.data("app", app);
43
49
  Alpine.data("page", page);
50
+ Alpine.data("sidebar", sidebar);
44
51
  Alpine.data("splitter", splitter);
45
52
  Alpine.data("previewWindow", previewWindow);
46
53
  Alpine.data("copy", copy);
@@ -53,6 +60,7 @@ Alpine.data("nav", nav);
53
60
  Alpine.data("tabs", tabs);
54
61
  Alpine.data("navItem", navItem);
55
62
  Alpine.data("navGroup", navGroup);
63
+ Alpine.data("embed", embed);
56
64
 
57
65
  // Init
58
66
 
@@ -0,0 +1,55 @@
1
+ import createSocket from "../lib/socket";
2
+
3
+ const morphOpts = {
4
+ key(el) {
5
+ return el.getAttribute("key") ? el.getAttribute("key") : el.id;
6
+ },
7
+ lookahead: false,
8
+ updating(el, toEl, childrenOnly, skip) {
9
+ if (
10
+ el.getAttribute &&
11
+ el.getAttribute("data-morph-strategy") === "replace"
12
+ ) {
13
+ el.innerHTML = toEl.innerHTML;
14
+ return skip();
15
+ }
16
+ },
17
+ };
18
+
19
+ export default function app() {
20
+ return {
21
+ init() {
22
+ if (window.SOCKET_PATH) {
23
+ const socket = createSocket(window.SOCKET_PATH);
24
+ socket.addListener("Lookbook::ReloadChannel", () => this.refresh());
25
+ }
26
+ },
27
+ async update() {
28
+ const response = await fetch(window.document.location);
29
+ if (!response.ok) return window.location.reload();
30
+ const html = await response.text();
31
+ const newDoc = new DOMParser().parseFromString(html, "text/html");
32
+ this.morph(newDoc);
33
+ document.title = newDoc.title;
34
+ },
35
+ setLocation(loc) {
36
+ let path;
37
+ if (loc instanceof Event) {
38
+ path = loc.currentTarget.href;
39
+ loc.preventDefault();
40
+ } else {
41
+ path = loc;
42
+ }
43
+ history.pushState({}, null, path);
44
+ this.$dispatch("popstate");
45
+ },
46
+ refresh() {
47
+ this.$dispatch("refresh");
48
+ },
49
+ morph(dom) {
50
+ const pageHtml = dom.getElementById(this.$root.id).outerHTML;
51
+ Alpine.morph(this.$root, pageHtml, morphOpts);
52
+ this.$dispatch("page:morphed");
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,89 @@
1
+ import "iframe-resizer/js/iframeResizer";
2
+
3
+ export default function embed() {
4
+ return {
5
+ init() {
6
+ if (!this.$store.pages.embeds[this.$root.id]) {
7
+ this.$store.pages.embeds[this.$root.id] = {
8
+ width: "100%",
9
+ };
10
+ }
11
+ },
12
+ lastWidth: null,
13
+ reflowing: false,
14
+ get resizer() {
15
+ if (this.$refs.iframe) {
16
+ if (!this.$refs.iframe.iFrameResizer) {
17
+ window.iFrameResize(
18
+ {
19
+ heightCalculationMethod: "lowestElement",
20
+ onResized: this.onIframeResized.bind(this),
21
+ },
22
+ this.$refs.iframe
23
+ );
24
+ }
25
+ return this.$refs.iframe.iFrameResizer;
26
+ }
27
+ return null;
28
+ },
29
+ set width(value) {
30
+ this.store.width = value;
31
+ },
32
+ get width() {
33
+ return this.store.width || "100%";
34
+ },
35
+ get height() {
36
+ return this.store.height;
37
+ },
38
+ get parentWidth() {
39
+ return Math.round(this.$root.parentElement.clientWidth);
40
+ },
41
+ get maxWidth() {
42
+ return this.width === "100%" ? "100%" : `${this.width}px`;
43
+ },
44
+ get store() {
45
+ return this.$store.pages.embeds[this.$root.id];
46
+ },
47
+ recaclulateIframeHeight() {
48
+ if (this.resizer) this.resizer.resize();
49
+ },
50
+ onIframeResized({ iframe, height }) {
51
+ if (iframe.isSameNode(this.$refs.iframe)) {
52
+ this.store.height = height;
53
+ }
54
+ },
55
+ onResizeWidth(e) {
56
+ const width =
57
+ this.resizeStartWidth - (this.resizeStartPositionX - e.pageX);
58
+ const boundedWidth = Math.min(
59
+ Math.max(Math.round(width), 200),
60
+ this.parentWidth
61
+ );
62
+ this.width = boundedWidth === this.parentWidth ? "100%" : boundedWidth;
63
+ this.recaclulateIframeHeight();
64
+ },
65
+ onResizeWidthStart(e) {
66
+ this.reflowing = true;
67
+ this.onResizeWidth = this.onResizeWidth.bind(this);
68
+ this.onResizeWidthEnd = this.onResizeWidthEnd.bind(this);
69
+ this.resizeStartPositionX = e.pageX;
70
+ this.resizeStartWidth = this.$refs.resizer.clientWidth;
71
+ window.addEventListener("pointermove", this.onResizeWidth);
72
+ window.addEventListener("pointerup", this.onResizeWidthEnd);
73
+ },
74
+ onResizeWidthEnd() {
75
+ window.removeEventListener("pointermove", this.onResizeWidth);
76
+ window.removeEventListener("pointerup", this.onResizeWidthEnd);
77
+ this.reflowing = false;
78
+ },
79
+ toggleFullWidth() {
80
+ if (this.width === "100%" && this.lastWidth) {
81
+ this.width = this.lastWidth;
82
+ } else {
83
+ this.lastWidth = this.width;
84
+ this.width = "100%";
85
+ }
86
+ this.$nextTick(() => this.recaclulateIframeHeight());
87
+ },
88
+ };
89
+ }
@@ -3,6 +3,7 @@ export default function filter() {
3
3
  get active() {
4
4
  return this.$store.filter.active;
5
5
  },
6
+ focussed: false,
6
7
  checkEsc($event) {
7
8
  if ($event.key === "Escape") {
8
9
  this.active ? this.clear() : this.blur();
@@ -15,10 +16,20 @@ export default function filter() {
15
16
  if ($event && $event.target.tagName === "INPUT") {
16
17
  return;
17
18
  }
18
- setTimeout(() => this.$refs.input.focus(), 0);
19
+ setTimeout(() => {
20
+ this.$dispatch("filter:focus");
21
+ this.$nextTick(() => {
22
+ this.focussed = true;
23
+ this.$refs.input.focus();
24
+ });
25
+ }, 0);
19
26
  },
20
27
  blur() {
21
- setTimeout(() => this.$refs.input.blur(), 0);
28
+ setTimeout(() => {
29
+ this.focussed = false;
30
+ this.$refs.input.blur();
31
+ this.$dispatch("filter:blur");
32
+ }, 0);
22
33
  },
23
34
  };
24
35
  }
@@ -1,5 +1,3 @@
1
- import { getAlpineData } from "../lib/utils";
2
-
3
1
  export default function navGroup() {
4
2
  return {
5
3
  hidden: false,
@@ -32,7 +30,7 @@ export default function navGroup() {
32
30
  filter(text) {
33
31
  this.hidden = true;
34
32
  this.getChildren().forEach((child) => {
35
- const data = getAlpineData(child);
33
+ const data = Alpine.$data(child);
36
34
  data.filter(text);
37
35
  if (!data.hidden) {
38
36
  this.hidden = false;
@@ -41,9 +39,8 @@ export default function navGroup() {
41
39
  },
42
40
  firstVisibleChild() {
43
41
  return this.getChildren().find((child) => {
44
- return child._x_dataStack
45
- ? child._x_dataStack[0].hidden === false
46
- : false;
42
+ const data = Alpine.$data(child);
43
+ return data.hidden === false;
47
44
  });
48
45
  },
49
46
  };
@@ -12,7 +12,9 @@ export default function navItem(matchers) {
12
12
  },
13
13
  navigate() {
14
14
  this.setLocation(this.path);
15
- this.$store.sidebar.open = false;
15
+ if (this.$store.layout.mobile) {
16
+ this.$store.sidebar.open = false;
17
+ }
16
18
  },
17
19
  filter(text) {
18
20
  this.hidden = false;
@@ -1,19 +1,18 @@
1
- import { getAlpineData } from "../lib/utils";
2
-
3
- export default function nav() {
1
+ export default function nav(filterable = true) {
4
2
  return {
5
3
  empty: false,
6
4
  init() {
7
- this.$watch("$store.filter.text", () => this.filter());
8
- this.$nextTick(() => {
9
- this.setActive();
10
- this.filter();
11
- });
5
+ if (filterable) {
6
+ this.$watch("$store.filter.text", () => this.filter());
7
+ this.$nextTick(() => {
8
+ this.filter();
9
+ });
10
+ }
12
11
  },
13
12
  filter() {
14
13
  this.empty = true;
15
14
  this.getChildren().forEach((child) => {
16
- const data = getAlpineData(child);
15
+ const data = Alpine.$data(child);
17
16
  data.filter(this.$store.filter.text);
18
17
  if (!data.hidden) {
19
18
  this.empty = false;
@@ -25,11 +24,5 @@ export default function nav() {
25
24
  ? Array.from(this.$refs.items.querySelectorAll(":scope > li > div"))
26
25
  : [];
27
26
  },
28
- setActive() {
29
- const target = this.$el.querySelector(
30
- `[data-path="${window.location.pathname}"]`
31
- );
32
- this.$store.nav.active = target ? target.id : "";
33
- },
34
27
  };
35
28
  }