scrapbook 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19b4fdaf3007e60e0cd40c09d70fe04ee9c9b522552dc0060f718aba02fcaadd
4
- data.tar.gz: 015d92510a0e2eacff22b81d163983de1ff88d2bc60f7f523e69dbfbc841d591
3
+ metadata.gz: ef6dc0f95ede6ecc1b68f75aaac825101f84252d5f7b8724e2b59534d40c083f
4
+ data.tar.gz: 6bd20440efd3e0ed318a5d1103c364600163fd48e3fd72f497e221ae07a08cc0
5
5
  SHA512:
6
- metadata.gz: c23dc644bb467bd0655a781615095e6cb74fd7ccc47dc0281a1877089bf42617fae23cbc182d290b437f96669328a899cda705a7ea69c299df0fb9f0e58f39c3
7
- data.tar.gz: 379b7626edd2752d95c039d6daaf947f668c0dc4a1571685b58879a11509987df0da474184a4f37896c1585135e0ea7bc7be9c044e80bc78385a2f0566c287dc
6
+ metadata.gz: e031db1caa0fdc1aea56a3efc5a969b8990305bf3263c7f8445bc7b66908390008465488031819774cfc3404298f9c1eac3cb18ec1c82b122e210f964652c33a
7
+ data.tar.gz: ff32ea7dad5cadb5f6d88ca444bb803033681f7845c99d4371645e0000da87de6d40609cf46d4c9fa4121cbca36cd2678ba519a49c127ade364fd6616e9d49ae
data/README.md CHANGED
@@ -31,8 +31,9 @@ $> bundle install
31
31
  $> bundle exec rails generate scrapbook:install
32
32
  ```
33
33
 
34
- This will install the gem and setup the default Scrapbook route and create the default
35
- scrapbook directory structure at the root of your Rails application.
34
+ This will install the gem and then setup a scrapbook directory structure with a folder named
35
+ "scrapbook" at the root of your Rails application as well as modify your routes to make the
36
+ scrapbook available.
36
37
 
37
38
  ## Usage
38
39
 
@@ -48,38 +49,32 @@ the main display area describing how to customize what is displayed when a folde
48
49
  selected. In short, you need to create a template file with the same base name as the folder
49
50
  in the same directory as the folder. So if you had a folder at "scrapbook/pages/scratch",
50
51
  then you would need to create a file named something like "scrapbook/pages/scratch.html.erb"
51
- with the custom display you want to see when you click on the "scratch" folder in the file
52
+ with the custom view you want to see when you click on the "scratch" folder in the file
52
53
  browser. This also works for the main screen you see at the base scrapbook URL. You can
53
54
  customize that screen by creating a "pages.html" template file in the root scrapbook folder.
54
55
  (The default installation creates a "pages.html.erb" template file for the basic welcome
55
56
  message.)
56
57
 
57
- To view a scrapbook, navigate to its base URL. The default scarpbook can always be accessed
58
- at the mountpoint setup in the routes file (this is "/scrapbook" by default). The base URL
59
- for other scrapbooks is that path followed by the name of the scrapbook's root folder (the
60
- folder that contains the "pages" folder). By default, only one scrapbook is configured and
61
- it's name is "scrapbook" which means that both "/scrapbook" and "/scrapbook/scrapbook" point
62
- to the same scrapbook root page.
58
+ To view a scrapbook, navigate to its base URL setup by the route file configuration. (See
59
+ the documentation for the `scrapbook` route helper for more information on how to conigure
60
+ this route.)
63
61
 
64
62
  ## Configuration
65
63
 
66
- You can configure the path that Scrapbook runs on in your "config/routes.rb" file. The
67
- default configuration is to mount Scrapbook on "/scrapbook", but you can use whatever
68
- path you want: `mount Scrapbook::Engine => "/scrappy"`.
64
+ The installation task adds `extend Scrapbook::Routing` to the routes configuration block
65
+ which gives it access to the `scrapbook` route helper. It also addds a scrapbook named
66
+ "scrapbook" using that helper (`scrapbook('scrapbook')`). You can modify that configuration,
67
+ specifying the location of the folder root or the URL path to mount the scrapbook at. You
68
+ can also add additional scrapbooks using this helper. For example:
69
69
 
70
- You can configure one or more folders to be separate root scrapbooks via the
71
- `config.scrapbook.paths` option. This option defaults to an empty array which allows for
72
- using the shovel operator to add your paths:
73
70
  ```ruby
74
- Rails.application.configure do
75
- config.scrapbook.paths << Rails.root.join("scrapbooks/main")
76
- config.scrapbook.paths << Rails.root.join("scrapbooks/scratch")
71
+ Rails.application.routes.draw do
72
+ extend Scrapbook::Routing
73
+
74
+ scrapbook('main', at: '/scrapbook', folder_root: 'scrapbooks/main')
75
+ scrapbook('scratch', folder_root: 'scrapbooks/scratch')
77
76
  end
78
77
  ```
79
- If no paths have been added, Scrapbook will use `Rails.root.join('scrapbook')` as the
80
- location it expects the scrapbook to be at. The first folder path in the array is considered
81
- the default scrapbook and will be available at the root mount point configured in the routes
82
- file.
83
78
 
84
79
  We recommend only running Scrapbook in development / non-production Rails environments.
85
80
  However, if you find yourself needing to be able to run it in an environment precompiles its
@@ -122,6 +117,12 @@ $> npx tailwindcss -i app/assets/stylesheets/scrapbook/application.tailwind.css
122
117
  (Note, the "tailwindcss-rails" gem currently doesn't support Rails engines, so we have to
123
118
  install and run Tailwind manually.)
124
119
 
120
+ ## Updating the Turbo javascript
121
+
122
+ - Download the compiled JS file from the turbo rails gem. (The releases on the Turbo website
123
+ didn't include it.)
124
+ - Comment out or remove the `export` statement towards the end.
125
+
125
126
  ## License
126
127
 
127
128
  Copyright 2022 Brad Lindsay
@@ -1 +1 @@
1
- /*! tailwindcss v3.0.23 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:#0000;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}body>nav li{cursor:default}body>nav a,body>nav a:active,body>nav a:hover,body>nav a:visited{color:#0f172a;cursor:default;text-decoration:none;transition:background-color 80ms linear;font-weight:400;font-size:unset;line-height:inherit}body>nav a:hover{background-color:#f0f9ff}body>nav a:active{background-color:#bae6fd}*,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.block{display:block}.flex{display:flex}.contents{display:contents}.h-screen{height:100vh}.h-full{height:100%}.w-screen{width:100vw}.w-full{width:100%}.min-w-\[20ch\]{min-width:20ch}.max-w-\[50\%\]{max-width:50%}.flex-none{flex:none}.overflow-scroll{overflow:scroll}.border-0{border-width:0}.border-r{border-right-width:1px}.border-solid{border-style:solid}.border-sky-700{--tw-border-opacity:1;border-color:rgb(3 105 161/var(--tw-border-opacity))}.bg-sky-100{--tw-bg-opacity:1;background-color:rgb(224 242 254/var(--tw-bg-opacity))}.\!px-0{padding-left:0!important;padding-right:0!important}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}.children\:px-4>*{padding-left:1rem;padding-right:1rem}
1
+ /*! tailwindcss v3.2.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:#0000;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}body>nav li{cursor:default}body>nav a,body>nav a:active,body>nav a:hover,body>nav a:visited{color:#0f172a;cursor:default;text-decoration:none;transition:background-color 80ms linear;font-weight:400;font-size:unset;line-height:inherit}turbo-frame{display:block}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.block{display:block}.flex{display:flex}.contents{display:contents}.h-screen{height:100vh}.h-full{height:100%}.w-screen{width:100vw}.w-full{width:100%}.min-w-\[20ch\]{min-width:20ch}.max-w-\[50\%\]{max-width:50%}.flex-none{flex:none}.grow{flex-grow:1}.overflow-scroll{overflow:scroll}.border-0{border-width:0}.border-r{border-right-width:1px}.border-solid{border-style:solid}.border-sky-700{--tw-border-opacity:1;border-color:rgb(3 105 161/var(--tw-border-opacity))}.\!px-0{padding-left:0!important;padding-right:0!important}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}summary{list-style:none}summary::-webkit-details-marker{display:none}details>summary:before{content:"▶︎";font-size:.8rem;flex:none;line-height:1.6rem;margin-right:.4rem;transition:rotate .2s ease-in-out}details[open]>summary:before{rotate:90deg;transition:rotate .2s ease-in-out}.hover\:bg-sky-50:hover{--tw-bg-opacity:1;background-color:rgb(240 249 255/var(--tw-bg-opacity))}.active\:bg-sky-200:active{--tw-bg-opacity:1;background-color:rgb(186 230 253/var(--tw-bg-opacity))}.children\:px-4>*{padding-left:1rem;padding-right:1rem}.\[\&\:has\(\[aria-current\=page\]\)\]\:bg-sky-100:has([aria-current=page]){--tw-bg-opacity:1;background-color:rgb(224 242 254/var(--tw-bg-opacity))}
@@ -1 +1,2 @@
1
1
  //= link_directory ../stylesheets/scrapbook .css
2
+ //= link_directory ../javascripts .js
@@ -0,0 +1,115 @@
1
+ //= require scrapbook/turbo.min
2
+
3
+ function ready(fn) {
4
+ if (document.readyState !== 'loading') {
5
+ fn()
6
+ } else {
7
+ document.addEventListener('DOMContentLoaded', fn)
8
+ }
9
+ }
10
+ ready(() => {
11
+ const scrapbook_root_path = document.head.querySelector("meta[name=scrapbook-root]").content
12
+
13
+ function buildInitialTree() {
14
+ const paths = window.location.pathname.substring(scrapbook_root_path.length).split("/")
15
+ if (paths.length === 1 && paths[0] === "") { return }
16
+
17
+ fetch(scrapbook_root_path)
18
+ .then((response) => response.text())
19
+ .then((data) => {
20
+ const root = document.createElement('html')
21
+ const nav = document.getElementById('folder_listing')
22
+ const frame = nav.querySelector('turbo-frame')
23
+
24
+ root.innerHTML = data
25
+ // Keep the nav element to keep listeners on it intact.
26
+ nav.innerHTML = root.querySelector('#folder_listing').innerHTML
27
+ setCurrent()
28
+
29
+ openFolderPath(window.location.pathname, scrapbook_root_path + paths[0])
30
+ })
31
+ .catch((error) => {
32
+ console.error("Unknown error:", error)
33
+ })
34
+ }
35
+
36
+ function openFolderPath(path, currentSubPath = scrapbook_root_path) {
37
+ const frame = document.querySelector('turbo-frame[src="' + currentSubPath + '"]')
38
+ if (!frame) { return }
39
+
40
+ if (path !== currentSubPath)
41
+ frame.addEventListener('turbo:frame-render', () => {
42
+ nextSegment = path.replace(currentSubPath + '/', '').split('/')[0]
43
+ openFolderPath(path, currentSubPath + '/' + nextSegment)
44
+ }, {once: true})
45
+ frame.parentElement.open = true
46
+ }
47
+
48
+ function runOpenFor(ids) {
49
+ const idsLeft = []
50
+ const framesFound = []
51
+
52
+ ids.forEach((id) => {
53
+ let frame = document.getElementById(id)
54
+ !frame ? idsLeft.push(id) : framesFound.push(frame)
55
+ })
56
+
57
+ framesFound.forEach((frame) => {
58
+ frame.addEventListener('turbo:frame-render', () => {
59
+ runOpenFor(idsLeft)
60
+ }, {once: true})
61
+ frame.parentElement.open = true
62
+ })
63
+ }
64
+
65
+ function setCurrent(link = document.querySelector('#folder_listing a[href="' + window.location.pathname + '"]')) {
66
+ if (link === null) { return }
67
+
68
+ oldSelection = document.querySelector('#folder_listing a[aria-current="page"]')
69
+ if (oldSelection == link) { return }
70
+
71
+ if (oldSelection) {
72
+ oldSelection.removeAttribute('aria-current', 'page')
73
+ }
74
+
75
+ link.setAttribute('aria-current', 'page')
76
+ }
77
+
78
+
79
+ buildInitialTree()
80
+ folderListingElem = document.getElementById("folder_listing")
81
+
82
+ // Update selection when a link is clicked
83
+ folderListingElem.addEventListener('click', (e) => {
84
+ const target = e.target
85
+ if (target.tagName != 'A') { return }
86
+
87
+ setCurrent(target)
88
+ })
89
+
90
+ // Every time a folder is opened, it's possible to be re-revealing the selected page.
91
+ folderListingElem.addEventListener('turbo:frame-render', () => {
92
+ setCurrent()
93
+ })
94
+
95
+ // Refresh trees when a folder is toggled back open
96
+ // The first time a details element is toggled open, the turbo frame lazily loads. This
97
+ // adds support for reloading whenever the element is closed and then re-opened. All
98
+ // turbo frames of any nested folders are also refreshed.
99
+ folderListingElem.addEventListener('toggle', (e) => {
100
+ if (!e.target.open) { return }
101
+ if ('loadedOnce' in e.target.dataset === false) {
102
+ e.target.dataset.loadedOnce = true
103
+ return
104
+ }
105
+
106
+ const idsToOpen = []
107
+ e.target.querySelectorAll('details[open] > turbo-frame').forEach(elem => idsToOpen.push(elem.id))
108
+
109
+ const firstFrame = document.getElementById(idsToOpen.shift())
110
+ firstFrame.addEventListener('turbo:frame-render', () => {
111
+ runOpenFor(idsToOpen)
112
+ }, {once: true})
113
+ firstFrame.reload()
114
+ }, {capture: true})
115
+ })
@@ -22,11 +22,24 @@
22
22
  line-height:inherit;
23
23
  }
24
24
 
25
- body > nav a:hover {
26
- background-color: theme('colors.sky.50');
25
+ turbo-frame {
26
+ display: block;
27
27
  }
28
+ }
28
29
 
29
- body > nav a:active {
30
- background-color: theme('colors.sky.200');
31
- }
30
+ summary { list-style: none; }
31
+ summary::-webkit-details-marker { display: none; }
32
+
33
+ details > summary::before {
34
+ content: "▶︎";
35
+ font-size: 0.8rem;
36
+ flex: none;
37
+ line-height: 1.6rem;
38
+ margin-right: 0.4rem;
39
+ transition: rotate 0.2s ease-in-out;
40
+ }
41
+
42
+ details[open] > summary::before {
43
+ rotate: 90deg;
44
+ transition: rotate 0.2s ease-in-out;
32
45
  }
@@ -5,26 +5,16 @@ module Scrapbook
5
5
  class PagesController < ApplicationController
6
6
  self.view_paths = Engine.config.paths['app/views'].to_a
7
7
 
8
- def index
9
- return head(:not_found) if (scrapbook = find_scrapbook).nil?
10
- return head(:not_found) unless (pathname = calculate_pathname(scrapbook, params[:path])).directory?
11
-
12
- if pathname == scrapbook.pages_pathname
13
- prepend_view_path(scrapbook.root)
14
- render template: 'pages', locals: {scrapbook: scrapbook, pathname: pathname}
15
- else
16
- render locals: {scrapbook: scrapbook, pathname: pathname}
17
- end
18
- end
8
+ layout -> { false if request.headers.include?('Turbo-Frame') }
19
9
 
20
10
  def show
21
11
  return head(:not_found) if (scrapbook = find_scrapbook).nil?
22
12
 
23
13
  pathname = calculate_pathname(scrapbook, params[:id])
24
- template = params[:id].delete_suffix('.html')
25
14
 
26
- if !scrapbook_template_exists?(scrapbook, template) && pathname.directory?
27
- render 'scrapbook/pages/index', locals: {scrapbook: scrapbook, pathname: pathname}
15
+ if request.headers['Turbo-Frame']&.start_with?('path_')
16
+ render partial: 'layouts/scrapbook/folder_listing',
17
+ locals: {scrapbook: scrapbook, pathname: pathname}
28
18
  else
29
19
  render locals: {scrapbook: scrapbook, pathname: pathname}, formats: [:html]
30
20
  end
@@ -34,13 +24,17 @@ module Scrapbook
34
24
  return head(:not_found) if (scrapbook = find_scrapbook).nil?
35
25
 
36
26
  pathname = calculate_pathname(scrapbook, params[:id])
37
- template = params[:id].delete_suffix('.html')
27
+ template = calculate_template
38
28
 
39
29
  if scrapbook_template_exists?(scrapbook, template)
40
- prepend_view_path(scrapbook.pages_pathname)
30
+ prepend_view_path(scrapbook.root)
41
31
  render template: template,
42
32
  locals: {scrapbook: scrapbook, pathname: pathname},
43
33
  layout: 'layouts/scrapbook/host_application'
34
+ elsif pathname.directory?
35
+ render '/pages',
36
+ locals: {scrapbook: scrapbook, pathname: pathname},
37
+ layout: 'layouts/scrapbook/host_application'
44
38
  elsif pathname.exist?
45
39
  render file: pathname
46
40
  else
@@ -51,25 +45,31 @@ module Scrapbook
51
45
  private
52
46
 
53
47
  def find_scrapbook
54
- scrapbook_path = if params[:book].present?
55
- ::Scrapbook::Engine.config.scrapbook.paths.find { File.basename(_1) == params[:book] }
56
- else
57
- ::Scrapbook::Engine.config.scrapbook.paths.first
58
- end
48
+ return nil if book_name.blank?
59
49
 
50
+ scrapbook_path = Engine.config.scrapbook.paths[book_name]
60
51
  scrapbook_path && Scrapbook.new(scrapbook_path)
61
52
  end
62
53
 
63
54
  def calculate_pathname(scrapbook, path)
64
- if path.present?
65
- scrapbook.pages_pathname.join(path)
66
- else
67
- scrapbook.pages_pathname
68
- end
55
+ scrapbook.pages_pathname.join(path || '')
56
+ end
57
+
58
+ def calculate_template
59
+ return 'pages' if params[:id].blank?
60
+
61
+ "pages/#{params[:id].delete_suffix('.html')}"
69
62
  end
70
63
 
71
64
  def scrapbook_template_exists?(scrapbook, template)
72
- EmptyController.new.tap { |c| c.prepend_view_path(scrapbook.pages_pathname) }.template_exists?(template)
65
+ # It's deprecated, but Rails 6 allows for templates to be specified with extensions.
66
+ return false if Rails.version.to_i == 6 && template.include?('.')
67
+
68
+ EmptyController.new.tap { |c| c.prepend_view_path(scrapbook.root) }.template_exists?(template)
69
+ end
70
+
71
+ def book_name
72
+ params[:'.book']
73
73
  end
74
74
  end
75
75
  end
@@ -3,12 +3,13 @@
3
3
  module Scrapbook
4
4
  # Model to assest list a folder's contents
5
5
  class FolderListingViewModel
6
- attr_reader :view, :scrapbook, :pathname, :files, :folders
6
+ attr_reader :view, :scrapbook, :pathname, :files, :folders, :depth
7
7
 
8
8
  def initialize(view, scrapbook, pathname)
9
9
  self.view = view
10
10
  self.scrapbook = scrapbook
11
11
  self.pathname = pathname.directory? ? pathname : pathname.dirname
12
+ self.depth = calculate_depth
12
13
  self.folders, self.files = split_files_and_folders
13
14
  end
14
15
 
@@ -24,12 +25,18 @@ module Scrapbook
24
25
  end
25
26
 
26
27
  def header_name
27
- root? ? "/#{scrapbook.name}" : "/#{pathname.basename}/"
28
+ root? ? scrapbook.name : pathname.basename.to_s
28
29
  end
29
30
 
30
31
  private
31
32
 
32
- attr_writer :view, :scrapbook, :pathname, :files, :folders
33
+ attr_writer :view, :scrapbook, :pathname, :files, :folders, :depth
34
+
35
+ def calculate_depth
36
+ return 0 if pathname == scrapbook.pages_pathname
37
+
38
+ scrapbook.relative_page_pathname_for(pathname).descend.count
39
+ end
33
40
 
34
41
  def split_files_and_folders
35
42
  helper = HelperForView.new(view)
@@ -10,12 +10,24 @@ module Scrapbook
10
10
 
11
11
  def short_path_to(pathname, scrapbook = nil)
12
12
  scrapbook ||= Scrapbook.find_scrapbook_for(pathname)
13
+ view.short_page_path(scrapbook.relative_page_path_for(pathname))
14
+ end
15
+
16
+ def nav_link_for(scrapbook:, pathname:, is_current: false, depth: 0, **kwargs)
17
+ link_name = pathname == scrapbook.pages_pathname ? scrapbook.name : pathname.basename
18
+ link_attrs = {
19
+ data: {'turbo-frame': 'page_content'},
20
+ class: %w[block w-100],
21
+ aria: {}
22
+ }
23
+ link_attrs[:style] = "padding-left: #{depth}rem;" if depth != 0
24
+ link_attrs[:aria][:current] = 'page' if is_current
25
+ link_attrs[:class].concat(Array(kwargs.delete(:class))) if kwargs.include?(:class)
26
+ link_attrs[:data].merge!(kwargs.delete(:data)) if kwargs.include?(:data)
27
+ link_attrs[:aria].merge!(kwargs.delete(:aria)) if kwargs.include?(:aria)
28
+ link_attrs.merge!(kwargs)
13
29
 
14
- if scrapbook.root == ::Scrapbook::Engine.config.scrapbook.paths.first
15
- view.short_page_path(scrapbook.relative_page_path_for(pathname))
16
- else
17
- view.book_page_path(scrapbook.relative_page_path_for(pathname), book: scrapbook.name)
18
- end
30
+ view.link_to(link_name, short_path_to(pathname, scrapbook), **link_attrs)
19
31
  end
20
32
 
21
33
  def remove_handler_exts_from(pathname)
@@ -9,11 +9,11 @@ module Scrapbook
9
9
 
10
10
  def self.find_scrapbook_for(pathname)
11
11
  scrapbooks = ::Scrapbook::Engine.config.scrapbook.paths
12
- candidates = scrapbooks.each_with_index.filter_map do |pname, index|
12
+ candidates = scrapbooks.filter_map do |book, pname|
13
13
  relative_path = pathname.relative_path_from(pname)
14
14
  next if relative_path.to_s.start_with?('..')
15
15
 
16
- [index, relative_path.each_filename.count]
16
+ [book, relative_path.each_filename.count]
17
17
  end
18
18
  raise NotFoundError if candidates.empty?
19
19
 
@@ -25,7 +25,7 @@ module Scrapbook
25
25
  end
26
26
 
27
27
  def name
28
- root.basename
28
+ root.basename.to_s
29
29
  end
30
30
 
31
31
  def pages_pathname
@@ -43,6 +43,15 @@ module Scrapbook
43
43
  relative_path
44
44
  end
45
45
 
46
+ def relative_page_pathname_for(pathname)
47
+ relative_path = pathname.relative_path_from(pages_pathname)
48
+ if relative_path.to_s.start_with?('..')
49
+ raise ArgumentError, "Pathname isn't inside the scrapbook pages: #{relative_path}"
50
+ end
51
+
52
+ relative_path
53
+ end
54
+
46
55
  def ==(other)
47
56
  other.class == self.class && other.root == root
48
57
  end
@@ -2,8 +2,29 @@
2
2
  helper = Scrapbook::HelperForView.new(self)
3
3
  listing = Scrapbook::FolderListingViewModel.new(self, scrapbook, pathname)
4
4
  pathname_without_handler_exts = helper.remove_handler_exts_from(pathname)
5
+ indentation_depth = (local_assigns[:nav_depth] || listing.depth) + 1
6
+ selection_classes = "active:bg-sky-200 hover:bg-sky-50 [&:has([aria-current=page])]:bg-sky-100"
5
7
  %>
6
- <% unless listing.root? %><%= link_to "‹ #{listing.parent_display_name}", helper.short_path_to(listing.pathname.parent), class: 'back-to-parent block w-100' %><% end %>
7
- <header aria-label="Which folder's contents" class="text-2xl <%= listing.pathname == pathname ? 'bg-sky-100' : '!px-0 children:px-4' %>"><% if listing.pathname == pathname %><span aria-current="page"><%= listing.header_name %></span><% else %><%= link_to(listing.header_name, helper.short_path_to(listing.pathname), class: 'block w-100') %><% end %></header>
8
- <ul class="!px-0" aria-label="Sub-folders"><% listing.folders.each do |folder| %><li class="children:px-4"><%= link_to "#{folder.basename}/", helper.short_path_to(folder, scrapbook), class: 'block w-100' %></li><% end %></ul>
9
- <ul class="!px-0" aria-label="Sub-files"><% listing.files.each do |file| %><% next if listing.folders.include?(file) %><li class="children:px-4"><% if file == pathname_without_handler_exts %><span class="block w-100 bg-sky-100" aria-current="page"><%= file.basename %></span><% else %><%= link_to file.basename, helper.short_path_to(file, scrapbook), class: 'block w-100' %><% end %></li><% end %></ul>
8
+ <% unless listing.root? %><%= link_to "‹ #{listing.parent_display_name}", helper.short_path_to(listing.pathname.parent, scrapbook), class: 'back-to-parent block w-100' %><% end %>
9
+ <header aria-label="Which folder's contents" class="text-2xl !px-0 children:px-4 <%= selection_classes %>">
10
+ <%= helper.nav_link_for(scrapbook: scrapbook, pathname: listing.pathname, is_current: pathname == listing.pathname) %>
11
+ </header>
12
+ <turbo-frame id="path_<%= pathname.hash %>">
13
+ <ul aria-label="Sub-folders">
14
+ <% listing.folders.each do |folder| %>
15
+ <li>
16
+ <details>
17
+ <summary class="flex <%= selection_classes %>" style="padding-left: <%= indentation_depth %>rem;">
18
+ <%= helper.nav_link_for(scrapbook: scrapbook, pathname: folder, class: 'grow') %>
19
+ </summary>
20
+ <turbo-frame id="path_<%= folder.hash %>" src="<%= helper.short_path_to(folder, scrapbook) %>" loading="lazy"></turbo-frame>
21
+ </details>
22
+ </li>
23
+ <% end %>
24
+ </ul>
25
+ <ul aria-label="Sub-files">
26
+ <% listing.files.each do |file| %><% next if listing.folders.include?(file) %>
27
+ <li class="<%= selection_classes %>"><%= helper.nav_link_for(scrapbook: scrapbook, pathname: file, is_current: file == pathname_without_handler_exts, depth: indentation_depth) %></li>
28
+ <% end %>
29
+ </ul>
30
+ </turbo-frame>
@@ -1,14 +1,19 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title><%= local_assigns[:scrapbook].name || 'Scrapbook' %>: <%= content_for?(:title) ? yield(:title) : local_assigns[:pathname].basename %></title>
4
+ <title><%= local_assigns[:scrapbook]&.name || 'Scrapbook' %>: <%= content_for?(:title) ? yield(:title) : local_assigns[:pathname]&.basename %></title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
8
  <%= stylesheet_link_tag "scrapbook/application", media: "all", "data-turbo-track": "reload" %>
9
+ <%= javascript_include_tag "scrapbook/application" %>
10
+ <meta name="scrapbook-root" content="<%= root_path %>">
9
11
  </head>
10
12
  <body class="flex">
11
- <nav class="flex-none min-w-[20ch] max-w-[50%] h-screen overflow-scroll children:px-4 py-2 border-solid border-sky-700 border-0 border-r"><%= render 'layouts/scrapbook/folder_listing', scrapbook: local_assigns[:scrapbook], pathname: local_assigns[:pathname] %></nav>
13
+ <nav id="folder_listing" class="flex-none min-w-[20ch] max-w-[50%] h-screen overflow-scroll border-solid border-sky-700 border-0 border-r"><%= render 'layouts/scrapbook/folder_listing', scrapbook: local_assigns[:scrapbook], pathname: local_assigns[:pathname], nav_depth: 0 %></nav>
12
14
  <main class="py-2 px-4 h-screen w-screen overflow-scroll"><%= yield %></main>
15
+ <script type="text/javascript">
16
+
17
+ </script>
13
18
  </body>
14
19
  </html>
@@ -1 +1,2 @@
1
- This directory has no corresponding template. You can add a template named "pages" to the Scrapbook root directory and its contents will show up here.
1
+ <% if pathname == scrapbook.pages_pathname %>This directory has no corresponding template. You can add a template named "pages" to the Scrapbook root directory and its contents will show up here.
2
+ <% else %>This directory has no corresponding template. You can add a template with the same name as the directory as a sibling to the directory and its contents will show up here.<% end %>
@@ -1 +1,3 @@
1
- <iframe src="<%= raw_page_path(book: scrapbook.name, id: scrapbook.relative_page_path_for(pathname)) %>" class="w-full h-full"></iframe>
1
+ <turbo-frame id="page_content" class="w-full h-full" data-turbo-action="advance">
2
+ <iframe src="<%= raw_page_path(scrapbook.relative_page_path_for(pathname)) %>" class="w-full h-full"></iframe>
3
+ </turbo-frame>
data/config/routes.rb CHANGED
@@ -1,16 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Scrapbook::Engine.routes.draw do
4
- book_regex = /#{Scrapbook::Engine.config.scrapbook.paths.map { File.basename(_1) }.join('|')}/
4
+ # TODO: Future plans
5
+ # scope path: '/.editor' do
6
+ # resources :pages, id: /.+/, only: %i[new create edit update]
7
+ # end
5
8
 
6
- resources :pages, id: /.+/
7
- resources :pages, path: ':book/pages', id: /.+/, constraints: {book: book_regex}
9
+ root 'pages#show'
8
10
 
9
- get ':book', to: 'pages#index', constraints: {book: book_regex}
10
- root 'pages#index'
11
-
12
- get '.raw/:book/pages/*id', to: 'pages#raw', as: :raw_page,
13
- constraints: {book: book_regex, id: /.*/}, defaults: {raw: true}
14
- get ':book/*id', to: 'pages#show', constraints: {book: book_regex, id: /.*/}, as: :book_page
11
+ get '.raw/pages(/*id)', to: 'pages#raw', constraints: {id: /.*/}, as: :raw_page
15
12
  get '*id', to: 'pages#show', constraints: {id: /.*/}, as: :short_page
16
13
  end
@@ -5,11 +5,12 @@ require 'rails/generators'
5
5
  module Scrapbook
6
6
  # Initial default setup of Scrapbook
7
7
  class InstallGenerator < Rails::Generators::Base
8
- class_option 'url-path', default: '/scrapbook'
9
8
  class_option 'path-with-name', default: 'scrapbook'
10
9
 
11
10
  def install
12
- generate 'scrapbook:routes', options.fetch('url-path')
11
+ name = Pathname.new(options.fetch('path-with-name')).basename.to_s
12
+
13
+ generate 'scrapbook:routes', name
13
14
  generate 'scrapbook:new', options.fetch('path-with-name')
14
15
  sprockets_support
15
16
  end
@@ -20,7 +21,8 @@ module Scrapbook
20
21
  relative_path = 'app/assets/config/manifest.js'
21
22
  return unless FileTest.exist?(File.expand_path(relative_path, destination_root))
22
23
 
23
- insert_into_file(relative_path, '//= link scrapbook/application.css')
24
+ insert_into_file(relative_path, "//= link scrapbook/application.css\n")
25
+ insert_into_file(relative_path, "//= link scrapbook/application.js\n")
24
26
  end
25
27
  end
26
28
  end
@@ -3,19 +3,21 @@
3
3
  require 'rails/generators'
4
4
 
5
5
  module Scrapbook
6
- # A generator to create a new scrapbook at either the default (scrapbook) or specified
7
- # path from the Rails application root.
6
+ # A generator to create a new scrapbook with either the default (scrapbook) or specified
7
+ # name. (Using the default options for the URL path and folder root.)
8
8
  class RoutesGenerator < Rails::Generators::Base
9
- argument :path, optional: true, default: '/scrapbook'
9
+ argument :name, optional: true, default: 'scrapbook'
10
10
 
11
11
  def routes
12
12
  # TODO: Investigate using a Rubocop rule to determine using single or double auotes.
13
- url_path = path.start_with?('/') ? path : "/#{path}"
14
- route <<~ROUTES
15
- if Rails.env.development?
16
- mount Scrapbook::Engine => '#{url_path}'
17
- end
18
- ROUTES
13
+
14
+ inject_into_file('config/routes.rb', after: "Rails.application.routes.draw do\n") do
15
+ " extend Scrapbook::Routing\n"
16
+ end
17
+
18
+ inject_into_file('config/routes.rb', after: "extend Scrapbook::Routing\n") do
19
+ "\n scrapbook('#{name}') if Rails.env.development?\n"
20
+ end
19
21
  end
20
22
  end
21
23
  end
@@ -6,20 +6,12 @@ module Scrapbook
6
6
  isolate_namespace Scrapbook
7
7
 
8
8
  config.scrapbook = ActiveSupport::OrderedOptions.new
9
- config.scrapbook.paths ||= []
9
+ config.scrapbook.paths ||= {}
10
10
  config.scrapbook.precompile_assets = config.scrapbook.precompile_assets || false
11
11
 
12
- initializer 'scrapbook.configuration' do |app|
13
- settings = app.config.scrapbook
14
-
15
- settings.paths << Rails.root.join('scrapbook') if settings.paths.empty?
16
- end
17
-
18
12
  initializer 'scrapbook.assets' do |app|
19
13
  if app.config.scrapbook.precompile_assets && app.config.respond_to?(:assets)
20
- app.config.assets.precompile.concat %w[
21
- scrapbook/application.css
22
- ]
14
+ app.config.assets.precompile.push('scrapbook/application.css')
23
15
  end
24
16
  end
25
17
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scrapbook
4
+ # This is a helper method for making it easier to add scrapbooks to routes.
5
+ module Routing
6
+ def scrapbook(name, at: "/#{name}", folder_root: name)
7
+ folder_root = Pathname.new(folder_root)
8
+ folder_root = Rails.root.join(folder_root) if folder_root.relative?
9
+
10
+ Engine.config.scrapbook.paths[name] = folder_root
11
+ mount Engine => at, defaults: {'.book': name}, as: "#{name}_scrapbook"
12
+ end
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scrapbook
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/scrapbook.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'scrapbook/version'
4
4
  require 'scrapbook/engine'
5
+ require 'scrapbook/routing'
5
6
 
6
7
  # TODO: Figure out documentation
7
8
  # Placeholder
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scrapbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Lindsay
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-14 00:00:00.000000000 Z
11
+ date: 2023-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.0'
19
+ version: '6.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '7.1'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '7.0'
29
+ version: '6.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.1'
@@ -43,6 +43,7 @@ files:
43
43
  - Rakefile
44
44
  - app/assets/builds/scrapbook/application.css
45
45
  - app/assets/config/scrapbook_manifest.js
46
+ - app/assets/javascripts/scrapbook/application.js
46
47
  - app/assets/stylesheets/scrapbook/application.tailwind.css
47
48
  - app/controllers/scrapbook/application_controller.rb
48
49
  - app/controllers/scrapbook/empty_controller.rb
@@ -50,13 +51,11 @@ files:
50
51
  - app/helpers/scrapbook/application_helper.rb
51
52
  - app/helpers/scrapbook/folder_listing_view_model.rb
52
53
  - app/helpers/scrapbook/helper_for_view.rb
53
- - app/mailers/scrapbook/application_mailer.rb
54
54
  - app/models/scrapbook/scrapbook.rb
55
55
  - app/views/layouts/scrapbook/_folder_listing.html.erb
56
56
  - app/views/layouts/scrapbook/application.html.erb
57
57
  - app/views/layouts/scrapbook/host_application.html.erb
58
58
  - app/views/pages.html.erb
59
- - app/views/scrapbook/pages/index.html.erb
60
59
  - app/views/scrapbook/pages/show.html.erb
61
60
  - config/routes.rb
62
61
  - lib/generators/scrapbook/install_generator.rb
@@ -64,6 +63,7 @@ files:
64
63
  - lib/generators/scrapbook/routes_generator.rb
65
64
  - lib/scrapbook.rb
66
65
  - lib/scrapbook/engine.rb
66
+ - lib/scrapbook/routing.rb
67
67
  - lib/scrapbook/version.rb
68
68
  - lib/tasks/scrapbook_tasks.rake
69
69
  homepage: https://bfad.github.io/scrapbook
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Scrapbook
4
- class ApplicationMailer < ActionMailer::Base
5
- default from: 'from@example.com'
6
- layout 'mailer'
7
- end
8
- end
@@ -1 +0,0 @@
1
- This directory has no corresponding template. You can add a template with the same name as the directory as a sibling to the directory and its contents will show up here.