Skip to content

Commit 35457e7

Browse files
authored
Merge pull request #197 from joyofrails/feat/snippets
Support code snippets
2 parents 2dfa71d + f819db1 commit 35457e7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+944
-213
lines changed

app/content/pages/articles/custom-color-schemes-with-ruby-on-rails.html.mdrb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ CSS variables make it easy to change repeated CSS in a lot of places. You can se
5454

5555
Here’s a simplified a view of how I used CSS variables to define the main background color of the `<body>` element. Using the pseudo-class `:root` means the CSS variable can be accessed from any scope in CSS.
5656

57-
```css:{"show_header": false}
57+
```css:{"header": false}
5858
:root {
5959
--theme-color-50: var(--my-color-50, var(--default-color-50));
6060
--theme-color-100: var(--my-color-100, var(--default-color-100));
@@ -73,13 +73,13 @@ Most of the buttons in the color scheme demo are connected to `<form>` elements
7373

7474
When you click the "Save" button, [the application stores the `id` of the chosen color scheme in your Rails session](https://github.com/joyofrails/joyofrails.com/blob/a08589e1cbe2accf4a20713829df56533e31755e/app/controllers/settings/color_schemes_controller.rb#L31):
7575

76-
```ruby:{"show_header":false}
76+
```ruby:{"header":false}
7777
session[:color_scheme_id] = @color_scheme.id
7878
```
7979

8080
When the page is rendered, the application checks for the presence of the session data to query for the desired color scheme:
8181

82-
```ruby:{"show_header":false}
82+
```ruby:{"header":false}
8383
session[:color_scheme_id] && ColorScheme.find(session[:color_scheme_id])
8484
```
8585

@@ -128,7 +128,7 @@ I’m not using any hooks to manage local state. I’m not making any explicit c
128128

129129
In fact, I wrote only a one line of custom JavaScript to make the color scheme preview selection work: on an `onchange` handler:
130130

131-
```html:{"show_header": false}
131+
```html:{"header": false}
132132
<select onchange="this.form.requestSubmit()">
133133
<!-- options -->
134134
</select>

app/content/pages/articles/how-to-render-css-dynamically-in-rails.html.mdrb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ irb> Mime::SET.collect(&:to_s)
124124

125125
In Rails `mime_types.rb`, we can see the registered MIME types with each of their recognized extensions. Here‘s the original `mime_types.rb`, added to Rails on Dec 2, 2006 ([source](https://github.com/rails/rails/blob/5410f2cb74737bd6d96c226230c2b9c2bfe1d80b/actionpack/lib/action_controller/mime_types.rb 'Source code on Github')).
126126

127-
```ruby:{"show_header": false}
127+
```ruby:{"header": false}
128128
Mime::Type.register "*/*", :all
129129
Mime::Type.register "text/plain", :text
130130
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
@@ -138,7 +138,7 @@ Mime::Type.register "application/atom+xml", :atom
138138

139139
Support for CSS was added a short time later on Feb 17, 2007 ([commit](https://github.com/rails/rails/commit/392c7f7314d196c54912a65981d79002d032f896 'Commit on Github')).
140140

141-
```diff:{"show_header": false}
141+
```diff:{"header": false}
142142
+ Mime::Type.register "text/css", :css
143143
```
144144

@@ -158,7 +158,7 @@ Below you‘ll see an `iframe` with `src` set to request the **Blue Chill** colo
158158

159159
Here’s the code for the `iframe`:
160160

161-
```erb:{"show_header": false}
161+
```erb:{"header": false}
162162
<%= tag.iframe src: color_scheme_path(@color_scheme_blue_chill) %>
163163
```
164164

@@ -170,7 +170,7 @@ And here you‘ll see an `iframe` with `src` set to request the **Blue Chill** c
170170

171171
Here’s the code for the `iframe`:
172172

173-
```erb:{"show_header": false}
173+
```erb:{"header": false}
174174
<%= tag.iframe src: color_scheme_path(@color_scheme_blue_chill, format: :css) %>
175175
```
176176

app/content/pages/articles/introducing-joy-of-rails.html.mdrb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Let’s say I wanted to describe how to build a basic counter with Rails and Hot
9393

9494
I can also link to and embed the code that makes this work, like the <%= link_to_app_file("app/controllers/examples/counters_controller.rb", "counters controller") %> that saves the count in your session and renders the HTML for this counter dynamically:
9595

96-
<%= render_code_block_app_file("app/controllers/examples/counters_controller.rb", language: "ruby", revision: "504f449e34232fc5299da78056344fffe04460de") %>
96+
<%= render CodeBlock::AppFile.new("app/controllers/examples/counters_controller.rb", language: "ruby", revision: "504f449e34232fc5299da78056344fffe04460de") %>
9797

9898
It would be harder to accomplish this with a WordPress blog or static website.
9999

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
class SnippetsController < ApplicationController
2+
before_action :feature_enabled!
3+
4+
# GET /snippets
5+
def index
6+
@snippets = Snippet.all
7+
end
8+
9+
# GET /snippets/1
10+
def show
11+
@snippet = Snippet.find(params[:id])
12+
end
13+
14+
# GET /snippets/new
15+
def new
16+
@snippet = Snippet.new(snippet_params)
17+
end
18+
19+
# GET /snippets/1/edit
20+
def edit
21+
@snippet = Snippet.find(params[:id])
22+
@snippet.assign_attributes(snippet_params)
23+
end
24+
25+
# POST /snippets
26+
def create
27+
@snippet = Snippet.new(snippet_params)
28+
29+
if @snippet.save
30+
redirect_to @snippet, notice: "Snippet was successfully created."
31+
else
32+
render :new, status: :unprocessable_entity
33+
end
34+
end
35+
36+
# PATCH/PUT /snippets/1
37+
def update
38+
@snippet = Snippet.find(params[:id])
39+
if @snippet.update(snippet_params)
40+
redirect_to @snippet, notice: "Snippet was successfully updated.", status: :see_other
41+
else
42+
render :edit, status: :unprocessable_entity
43+
end
44+
end
45+
46+
# DELETE /snippets/1
47+
def destroy
48+
@snippet = Snippet.find(params[:id])
49+
@snippet.destroy!
50+
redirect_to snippets_url, notice: "Snippet was successfully destroyed.", status: :see_other
51+
end
52+
53+
private
54+
55+
# Only allow a list of trusted parameters through.
56+
def snippet_params
57+
params.fetch(:snippet, {}).permit(:filename, :source, :url, :language)
58+
end
59+
60+
def feature_enabled!
61+
return if user_signed_in? &&
62+
Flipper.enabled?(:snippets, current_user)
63+
64+
raise ActionController::RoutingError.new("Not Found")
65+
end
66+
end

app/helpers/articles_helper.rb

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/helpers/snippets_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module SnippetsHelper
2+
end

app/javascript/controllers/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import TableOfContents from './table-of-contents';
1717

1818
import FrameForm from './forms/frame';
1919
import SyntaxHighlightPreview from './syntax-highlight/preview';
20+
import SnippetPreview from './snippets/preview';
21+
import SnippetEditor from './snippets/editor';
2022

2123
application.register('analytics', AnalyticsCustomEvent);
2224
application.register('code-example', CodeExample);
@@ -29,4 +31,6 @@ application.register('pwa-web-push-demo', PwaWebPushDemo);
2931
application.register('table-of-contents', TableOfContents);
3032

3133
application.register('frame-form', FrameForm);
34+
application.register('snippet-preview', SnippetPreview);
35+
application.register('snippet-editor', SnippetEditor);
3236
application.register('syntax-highlight-preview', SyntaxHighlightPreview);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
import debug from '../../utils/debug';
4+
5+
const console = debug('app:javascript:controllers:snippets:editor');
6+
7+
export default class extends Controller {
8+
static targets = ['previewButton', 'source', 'textarea'];
9+
10+
declare previewButtonTarget: HTMLButtonElement;
11+
declare sourceTarget: HTMLElement;
12+
declare textareaTarget: HTMLTextAreaElement;
13+
14+
connect() {
15+
console.log('Stimulus controller connected');
16+
this.showCode();
17+
this.syncTextarea();
18+
// this.textareaTarget.addEventListener('input', this.autoGrow.bind(this));
19+
// this.textareaTarget.addEventListener(
20+
// 'blur',
21+
// this.handleTextareaBlur.bind(this),
22+
// );
23+
}
24+
25+
disconnect() {
26+
// this.textareaTarget.removeEventListener('input', this.autoGrow.bind(this));
27+
// this.textareaTarget.removeEventListener(
28+
// 'blur',
29+
// this.handleTextareaBlur.bind(this),
30+
// );
31+
}
32+
33+
preview() {
34+
this.previewButtonTarget.click();
35+
}
36+
37+
showTextarea() {
38+
this.sourceTarget.style.display = 'none';
39+
this.textareaTarget.style.display = 'block';
40+
this.textareaTarget.focus();
41+
this.autoGrow();
42+
}
43+
44+
syncTextarea() {
45+
this.textareaTarget.value = this.sourceTarget.textContent || '';
46+
this.autoGrow();
47+
}
48+
49+
showCode() {
50+
this.sourceTarget.style.display = 'block';
51+
this.textareaTarget.style.display = 'none';
52+
}
53+
54+
autoGrow() {
55+
this.textareaTarget.style.height = 'auto';
56+
this.textareaTarget.style.height = this.textareaTarget.scrollHeight + 'px';
57+
}
58+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
import debug from '../../utils/debug';
4+
5+
const console = debug('app:javascript:controllers:snippets:preview');
6+
7+
export default class extends Controller {
8+
// Make timeout type play nice with TypeScript
9+
// based on https://donatstudios.com/TypeScriptTimeoutTrouble
10+
private idleTimeout?: ReturnType<typeof setTimeout>;
11+
12+
static targets = ['source', 'previewButton'];
13+
14+
declare readonly hasSourceTarget: boolean;
15+
declare readonly sourceTarget: HTMLInputElement;
16+
declare readonly sourceTargets: HTMLInputElement[];
17+
18+
declare readonly hasPreviewButtonTarget: boolean;
19+
declare readonly previewButtonTarget: HTMLInputElement;
20+
declare readonly previewButtonTargets: HTMLInputElement[];
21+
22+
connect(): void {
23+
console.log('Connect!');
24+
}
25+
26+
disconnect(): void {
27+
this.clearIdleTimeout();
28+
}
29+
30+
clearIdleTimeout(): void {
31+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
32+
}
33+
34+
preview = (): void => {
35+
console.log('Start preview timer!');
36+
this.clearIdleTimeout();
37+
this.idleTimeout = setTimeout(this.clickPreviewButton, 500);
38+
};
39+
40+
clickPreviewButton = (): void => {
41+
console.log('Click preview button!');
42+
this.previewButtonTarget.click();
43+
};
44+
}

app/javascript/css/components/code.scss

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
}
77

88
@media screen and (min-width: $screen-md) {
9-
.code-wrapper {
10-
margin-inline-start: calc(var(--space-m) * -1);
11-
margin-inline-end: calc(var(--space-m) * -1);
9+
.article-content {
10+
& .code-wrapper {
11+
margin-inline-start: calc(var(--space-m) * -1);
12+
margin-inline-end: calc(var(--space-m) * -1);
13+
}
1214
}
1315
}
1416

@@ -17,17 +19,28 @@
1719
flex-direction: row;
1820
justify-content: space-between;
1921
overflow-x: auto;
22+
position: relative;
2023

21-
pre {
24+
& pre {
2225
line-height: 1.7777778;
2326
margin-top: 0;
2427
margin-bottom: 0;
2528
border-radius: 0.5rem;
29+
background-color: inherit;
30+
}
31+
32+
& pre,
33+
& textarea {
2634
padding-top: var(--space-m);
2735
padding-inline-end: var(--space-m);
2836
padding-bottom: var(--space-m);
2937
padding-inline-start: var(--space-m);
30-
background-color: inherit;
38+
}
39+
40+
textarea {
41+
width: 100%;
42+
font-family: var(--monospace);
43+
border: none;
3144
}
3245
}
3346

@@ -154,3 +167,13 @@
154167
}
155168
}
156169
}
170+
171+
.snippet {
172+
.code-body {
173+
& code {
174+
word-wrap: break-word;
175+
white-space: pre-wrap;
176+
word-break: normal;
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)