Add StimulusJS and Refactor Internal article_script (#5539) deploy]
* Add StimulusJS 1.1.1 Because: - Internal will likely need more interactivity going forward - Vanilla JS and jQuery are great, but aren't quite as organized as we might like - Stimulus fits our model of what internal should be (imo) - Adopting Preact or something with templating would require rewriting a bunch of erb (that is currently working just fine) Stimulus is a JavaScript framework that directly compliments turbolinks and the approach vanilla Rails takes to JavaScript. I've set it up to specifically be used in the internal views where we are currently using jQuery and vanilla JS. I think we should chip away at the vanilla JS currently in place with this, which appears to be both scalable and pragmatic exactly the sort of tool we want for building our internal tools. * Replace crufty article JS with Stimulus I'm swapping out some of the stuff going on in article_script.erb with some StimulusJS, it seems like a good test case for Stimulus. This lets us organize the code a little more intentionally and hopefully fixes a small performance complaint that Michael had. * Rename uncommunicative methods * Add unit tests for Stimulus classes * Move Stimulus code to javascript/internal
This commit is contained in:
parent
b980481d0d
commit
3e99fb527f
14 changed files with 270 additions and 70 deletions
23
app/javascript/internal/__mocks__/mutationObserver.js
Normal file
23
app/javascript/internal/__mocks__/mutationObserver.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Sourced from https://github.com/stimulusjs/stimulus/issues/34
|
||||
// and https://shime.sh/testing-stimulus
|
||||
|
||||
// Setup MutationObserver shim since jsdom doesn't
|
||||
// support it out of the box.
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const shim = readFileSync(
|
||||
resolve(
|
||||
'node_modules',
|
||||
'mutationobserver-shim',
|
||||
'dist',
|
||||
'mutationobserver.min.js',
|
||||
),
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
|
||||
const script = window.document.createElement('script');
|
||||
script.textContent = shim;
|
||||
|
||||
window.document.body.appendChild(script);
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { Application } from 'stimulus';
|
||||
import ArticleController from '../../controllers/article_controller';
|
||||
import '../../__mocks__/mutationObserver';
|
||||
|
||||
describe('ArticleController', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `<div data-controller="article">
|
||||
<button data-action="article#increaseFeaturedNumber"></button>
|
||||
<button data-action="article#decreaseFeaturedNumber"></button>
|
||||
<button data-action="article#highlightElement"></button>
|
||||
<input data-target="article.featuredNumber"></input>
|
||||
</div>`;
|
||||
|
||||
const application = Application.start();
|
||||
application.register('article', ArticleController);
|
||||
});
|
||||
|
||||
// Unix timestamp, one hour ago
|
||||
const initialValue = Math.round((Date.now() - 3600000) / 1000);
|
||||
|
||||
describe('#increaseFeaturedNumber', () => {
|
||||
it('increases the featured number input', () => {
|
||||
const button = document.querySelectorAll('button')[0];
|
||||
const input = document.querySelector(
|
||||
"[data-target='article.featuredNumber']",
|
||||
);
|
||||
|
||||
input.value = initialValue;
|
||||
button.click();
|
||||
|
||||
expect(parseInt(input.value, 10)).toBeGreaterThan(initialValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#decreaseFeaturedNumber', () => {
|
||||
it('increases the featured number input', () => {
|
||||
const button = document.querySelectorAll('button')[1];
|
||||
const input = document.querySelector(
|
||||
"[data-target='article.featuredNumber']",
|
||||
);
|
||||
|
||||
input.value = initialValue;
|
||||
button.click();
|
||||
|
||||
expect(parseInt(input.value, 10)).toBeLessThan(initialValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#highlightElement', () => {
|
||||
it('adds a class to the controller element', () => {
|
||||
const button = document.querySelectorAll('button')[2];
|
||||
const element = document.querySelector("[data-controller='article']");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(
|
||||
element.classList.contains('highlighted-bg', 'highlighted-border'),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Application } from 'stimulus';
|
||||
import BufferController from '../../controllers/buffer_controller';
|
||||
import '../../__mocks__/mutationObserver';
|
||||
|
||||
describe('BufferController', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `<div data-controller="buffer">
|
||||
<h2 data-target="buffer.header"></h2>
|
||||
<button data-action="buffer#tagBufferUpdateConfirmed"></button>
|
||||
<button data-action="buffer#tagBufferUpdateDismissed"></button>
|
||||
<button data-action="buffer#highlightElement"></button>
|
||||
</div>`;
|
||||
|
||||
const application = Application.start();
|
||||
application.register('buffer', BufferController);
|
||||
});
|
||||
|
||||
describe('#tagBufferUpdateConfirmed', () => {
|
||||
it('adds a badge to the header', () => {
|
||||
const button = document.querySelectorAll('button')[0];
|
||||
const header = document.querySelector('h2');
|
||||
|
||||
button.click();
|
||||
|
||||
expect(header.firstChild.textContent).toMatch(/Confirm/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#tagBufferUpdateDismissed', () => {
|
||||
it('adds a badge to the header', () => {
|
||||
const button = document.querySelectorAll('button')[1];
|
||||
const header = document.querySelector('h2');
|
||||
|
||||
button.click();
|
||||
|
||||
expect(header.firstChild.textContent).toMatch(/Dismiss/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#highlightElement', () => {
|
||||
it('adds a class to the controller element', () => {
|
||||
const button = document.querySelectorAll('button')[2];
|
||||
const element = document.querySelector("[data-controller='buffer']");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(
|
||||
element.classList.contains('highlighted-bg', 'highlighted-border'),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
32
app/javascript/internal/controllers/article_controller.js
Normal file
32
app/javascript/internal/controllers/article_controller.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Controller } from 'stimulus';
|
||||
|
||||
export default class ArticleController extends Controller {
|
||||
static targets = ['featuredNumber'];
|
||||
|
||||
increaseFeaturedNumber() {
|
||||
// Increases the article's chances of being seen
|
||||
const seconds = new Date().getTime() / 1000;
|
||||
this.featuredNumberTarget.value = Math.round(seconds);
|
||||
}
|
||||
|
||||
decreaseFeaturedNumber() {
|
||||
// Decreases the article's chances of being seen
|
||||
const seconds = new Date().getTime() / 1080;
|
||||
this.featuredNumberTarget.value = Math.round(seconds);
|
||||
}
|
||||
|
||||
highlightElement() {
|
||||
this.element.classList.add('highlighted-bg', 'highlighted-border');
|
||||
setTimeout(() => {
|
||||
this.element.classList.remove('highlighted-bg');
|
||||
}, 350);
|
||||
}
|
||||
|
||||
get articleId() {
|
||||
return parseInt(this.data.get('id'), 10);
|
||||
}
|
||||
|
||||
set articleId(value) {
|
||||
this.data.set('id', value);
|
||||
}
|
||||
}
|
||||
30
app/javascript/internal/controllers/buffer_controller.js
Normal file
30
app/javascript/internal/controllers/buffer_controller.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { Controller } from 'stimulus';
|
||||
|
||||
export default class BufferController extends Controller {
|
||||
static targets = ['header'];
|
||||
|
||||
tagBufferUpdateConfirmed() {
|
||||
this.headerTarget.innerHTML +=
|
||||
'<span class="ml-2 badge badge-success">Confirm</span>';
|
||||
}
|
||||
|
||||
tagBufferUpdateDismissed() {
|
||||
this.headerTarget.innerHTML +=
|
||||
'<span class="ml-2 badge badge-danger">Dismiss</span>';
|
||||
}
|
||||
|
||||
highlightElement() {
|
||||
this.element.classList.add('highlighted-bg', 'highlighted-border');
|
||||
setTimeout(() => {
|
||||
this.element.classList.remove('highlighted-bg');
|
||||
}, 350);
|
||||
}
|
||||
|
||||
get bufferUpdateId() {
|
||||
return parseInt(this.data.get('id'), 10);
|
||||
}
|
||||
|
||||
set bufferUpdateId(value) {
|
||||
this.data.set('id', value);
|
||||
}
|
||||
}
|
||||
6
app/javascript/packs/internal.js
Normal file
6
app/javascript/packs/internal.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { Application } from 'stimulus';
|
||||
import { definitionsFromContext } from 'stimulus/webpack-helpers';
|
||||
|
||||
const application = Application.start();
|
||||
const context = require.context('internal/controllers', true, /.js$/);
|
||||
application.load(definitionsFromContext(context));
|
||||
|
|
@ -1,50 +1,5 @@
|
|||
<script>
|
||||
function timeNow(objectID) {
|
||||
var seconds = new Date().getTime() / 1000;
|
||||
document.getElementById("featured_number_" + objectID).value = Math.round(seconds);
|
||||
}
|
||||
|
||||
function sink(objectID) {
|
||||
var seconds = new Date().getTime() / 1080;
|
||||
document.getElementById("featured_number_" + objectID).value = Math.round(seconds);
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
function submitForms() {
|
||||
var form = $(this);
|
||||
var valuesToSubmit = $(this).serialize();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: $(this).attr('action'), //sumbits it to the given url of the form
|
||||
data: valuesToSubmit,
|
||||
}).success(function (json) {
|
||||
form.parents(".card").addClass("highlighted-bg")
|
||||
form.parents(".card").addClass("highlighted-border")
|
||||
form.parents(".single-internal-listing").addClass("highlighted-bg")
|
||||
form.parents(".single-internal-listing").addClass("highlighted-border")
|
||||
setTimeout(function () {
|
||||
form.parents(".card").removeClass("highlighted-bg")
|
||||
form.parents(".single-internal-listing").removeClass("highlighted-bg")
|
||||
}, 350)
|
||||
});
|
||||
return false; // prevents normal behaviour
|
||||
}
|
||||
|
||||
$('.card').on("submit", "form", submitForms)
|
||||
$('.buffer-area').on("submit", "form", submitForms)
|
||||
|
||||
function tagSuggestedTweet(event) {
|
||||
var updateId = $(this).data("id");
|
||||
var updateElement = $(`#suggested-tweet-${updateId}`)
|
||||
if (event.target.classList.contains('buffer-confirm')) {
|
||||
updateElement.find('.card-header h2').append('<span class="ml-2 badge badge-success">Confirm</span>')
|
||||
} else if (event.target.classList.contains('buffer-dismiss')) {
|
||||
updateElement.find('.card-header h2').append('<span class="ml-2 badge badge-danger">Dismiss</span>')
|
||||
}
|
||||
}
|
||||
|
||||
$('.buffer-form').on("submit", tagSuggestedTweet);
|
||||
|
||||
imageUploadButt = document.getElementById('image-upload-button')
|
||||
imageUpload = document.getElementById('image-upload')
|
||||
imageUploadSubmit = document.getElementById('image-upload-submit')
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="card my-3">
|
||||
<div class="card my-3" data-controller="article" data-article-id="<%= article.id %>">
|
||||
<div class="card-header">
|
||||
<a href="<%= article.path %>" target="_blank"><%= article.title %></a>
|
||||
<a class="ml-2" href="<%= article.path %>/edit" target="_blank"><span class="badge badge-success">EDIT</span></a>
|
||||
|
|
@ -68,11 +68,10 @@
|
|||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<button class="btn btn-success" data-action="article#increaseFeaturedNumber">☝️ Boost!</button>
|
||||
<button class="btn btn-danger" data-action="article#decreaseFeaturedNumber">👇 SPAM!</button>
|
||||
|
||||
<button class="btn btn-success" onclick="timeNow(<%= article.id %>);return false;">☝️ Boost!</button>
|
||||
<button class="btn btn-danger" onclick="sink(<%= article.id %>);return false;">👇 SPAM!</button>
|
||||
|
||||
<form action="/internal/articles/<%= article.id %>" accept-charset="UTF-8" method="post">
|
||||
<%= form_with url: internal_article_path(article.id), html: { data: { action: "submit->article#highlightElement" } } do |f| %>
|
||||
<div class="form-group">
|
||||
<input name="utf8" type="hidden" value="✓">
|
||||
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
|
||||
|
|
@ -80,7 +79,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label for="featured_number_<%= article.id %>">Featured Number:</label>
|
||||
<input id="featured_number_<%= article.id %>" class="form-control" name="article[featured_number]" value="<%= article.featured_number %>">
|
||||
<input id="featured_number_<%= article.id %>" class="form-control" name="article[featured_number]" value="<%= article.featured_number %>" data-target="article.featuredNumber">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="author_id_<%= article.id %>">Author ID:</label>
|
||||
|
|
@ -131,7 +130,7 @@
|
|||
<% end %>
|
||||
|
||||
<button class="btn btn-primary float-right">Submit</button>
|
||||
</form>
|
||||
<% end %>
|
||||
|
||||
<% if article.last_buffered %>
|
||||
<div class="mt-5">
|
||||
|
|
@ -179,7 +178,7 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<%= form_tag "/internal/buffer_updates" do %>
|
||||
<%= form_with url: "/internal/buffer_updates", html: { data: { action: "submit->article#highlightElement" } } do %>
|
||||
<input type="hidden" name="social_channel" value="main_twitter" />
|
||||
<input type="hidden" name="article_id" value="<%= article.id %>" />
|
||||
<h5>Twitter MAIN</h5>
|
||||
|
|
@ -192,7 +191,7 @@
|
|||
<% end %>
|
||||
|
||||
<% if (article.decorate.cached_tag_list_array & Tag.bufferized_tags).any? %>
|
||||
<%= form_tag "/internal/buffer_updates" do %>
|
||||
<%= form_with url: "/internal/buffer_updates", html: { data: { action: "submit->article#highlightElement" } } do %>
|
||||
<input type="hidden" name="social_channel" value="satellite_twitter" />
|
||||
<input type="hidden" name="article_id" value="<%= article.id %>" />
|
||||
<h5>Twitter Satellite</h5>
|
||||
|
|
@ -206,7 +205,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= form_tag "/internal/buffer_updates" do %>
|
||||
<%= form_with url: "/internal/buffer_updates", html: { data: { action: "submit->article#highlightElement" } } do %>
|
||||
<input type="hidden" name="social_channel" value="facebook" />
|
||||
<input type="hidden" name="article_id" value="<%= article.id %>" />
|
||||
<h5>Facebook & LinkedIn</h5>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="row">
|
||||
<div class="col-12 py-2 sticky-top bg-white d-flex justify-content-center editor-image-upload">
|
||||
<div class="col-12 py-2 sticky-top bg-white d-flex justify-content-center editor-image-upload" data-controller="image-upload">
|
||||
<input type="file" id="image-upload" name="file" accept="image/*" style="display:none">
|
||||
<input type="submit" id='image-upload-submit' value="Upload" style="display:none">
|
||||
<button class="btn btn-primary mr-2" id="image-upload-button">Upload Image</button>
|
||||
|
|
@ -47,9 +47,9 @@
|
|||
<summary style="font-size: 1.3em; cursor: pointer;">Suggested tweets (<%= @pending_buffer_updates.size %>)</summary>
|
||||
<% @pending_buffer_updates.each do |buffer_update| %>
|
||||
<% next unless buffer_update.article %>
|
||||
<div class="card my-3" id="suggested-tweet-<%= buffer_update.id %>">
|
||||
<div class="card my-3" id="suggested-tweet-<%= buffer_update.id %>" data-controller="buffer">
|
||||
<div class="card-header">
|
||||
<h2 class="my-0"><%= buffer_update.article.title %></h2>
|
||||
<h2 class="my-0" data-target="buffer.header"><%= buffer_update.article.title %></h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h4>Score: <%= buffer_update.article.score %></h4>
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
<code><b><%= Tag.find_by(id: buffer_update.tag_id)&.name || buffer_update.social_service_name %>:</b></code>
|
||||
|
||||
<form action="/internal/buffer_updates/<%= buffer_update.id %>" accept-charset="UTF-8" method="post" class="buffer-form buffer-confirm" data-id="<%= buffer_update.id %>">
|
||||
<%= form_with url: "/internal/buffer_updates/#{buffer_update.id}", class: "buffer-form buffer-confirm", html: { data: { action: "submit->buffer#highlightElement" } } do |f| %>
|
||||
<div class="form-group">
|
||||
<input name="utf8" type="hidden" value="✓">
|
||||
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
|
||||
|
|
@ -68,18 +68,18 @@
|
|||
<input type="hidden" name="status" value="confirmed" />
|
||||
<textarea name="body_text" class="form-control"><%= buffer_update.body_text %></textarea>
|
||||
</div>
|
||||
<button value="confirmed" name="status" class="btn btn-success">Confirm</button>
|
||||
</form>
|
||||
<button value="confirmed" name="status" class="btn btn-success" data-action="buffer#tagBufferUpdateConfirmed">Confirm</button>
|
||||
<% end %>
|
||||
|
||||
<form action="/internal/buffer_updates/<%= buffer_update.id %>" accept-charset="UTF-8" method="post" class="buffer-form buffer-dismiss" data-id="<%= buffer_update.id %>">
|
||||
<%= form_with url: "/internal/buffer_updates/#{buffer_update.id}", class: "buffer-form buffer-dismiss", html: { data: { action: "submit->buffer#highlightElement" } } do |f| %>
|
||||
<div class="form-group">
|
||||
<input name="utf8" type="hidden" value="✓">
|
||||
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
|
||||
<input type="hidden" name="_method" value="patch" />
|
||||
<input type="hidden" name="status" value="dismissed" />
|
||||
</div>
|
||||
<button value="dismissed" name="status" class="btn btn-danger">Dismiss</button>
|
||||
</form>
|
||||
<button value="dismissed" name="status" class="btn btn-danger" data-action="buffer#tagBufferUpdateDismissed">Dismiss</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -105,4 +105,4 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%= render "article_script" %>
|
||||
<%= render "image_upload_script" %>
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
<%= render "individual_article", article: @article %>
|
||||
<%= render "article_script" %>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<div class="grid-item">Last Bumped</div>
|
||||
</div>
|
||||
<% @classified_listings.each do |listing| %>
|
||||
<div class="single-internal-listing">
|
||||
<div class="single-internal-listing" data-controller="buffer">
|
||||
<div class="wrapper">
|
||||
<div class="grid-item">
|
||||
<a href="/internal/listings/<%= listing.id %>/edit" target="_blank">
|
||||
|
|
@ -60,12 +60,12 @@
|
|||
<p class="listing-body"><%= listing.processed_html&.html_safe %></p>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<%= form_tag "/internal/buffer_updates" do %>
|
||||
<%= form_with url: "/internal/buffer_updates", html: { data: { action: "submit->buffer#highlightElement" } } do %>
|
||||
<input type="hidden" name="social_channel" value="listings_twitter" />
|
||||
<input type="hidden" name="listing_id" value="<%= listing.id %>" />
|
||||
<textarea cols="37" rows="8" wrap="hard" name="tweet" maxlength="255">📋 New DEV Listing!

Category: <%= listing.category %>

<%= listing.title %>

<% if listing.user.twitter_username? %>Posted by @<%= listing.user.twitter_username %><% end %></textarea>
|
||||
<br>
|
||||
<button class="btn btn-primary">🐦 Tweet 🐦</button>
|
||||
<button class="btn btn-primary" data-action=>🐦 Tweet 🐦</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -75,4 +75,3 @@
|
|||
<% end %>
|
||||
<%= paginate @classified_listings %>
|
||||
|
||||
<%= render "internal/articles/article_script" %>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
<%= favicon_link_tag SiteConfig.favicon_url %>
|
||||
<title><%= controller_name.titleize %></title>
|
||||
|
||||
<!-- StimulusJS -->
|
||||
<%= javascript_pack_tag "manifest", defer: true %>
|
||||
<%= javascript_pack_tag "vendor", defer: true %>
|
||||
<%= javascript_pack_tag "internal", defer: true %>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Bootstrap and FontAwesome -->
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@
|
|||
"jest-fetch-mock": "^3.0.1",
|
||||
"jsdom": "^15.2.1",
|
||||
"lint-staged": "^10.0.0",
|
||||
"mutationobserver-shim": "^0.3.3",
|
||||
"preact-render-spy": "1.3.0",
|
||||
"preact-render-to-json": "^3.6.6",
|
||||
"prettier": "^1.19.1",
|
||||
|
|
@ -105,6 +106,7 @@
|
|||
"preact-textarea-autosize": "^4.0.7",
|
||||
"prop-types": "^15.7.2",
|
||||
"pusher-js": "^5.0.3",
|
||||
"stimulus": "^1.1.1",
|
||||
"twilio-video": "^2.0.0",
|
||||
"web-share-wrapper": "^0.2.1"
|
||||
},
|
||||
|
|
|
|||
37
yarn.lock
37
yarn.lock
|
|
@ -290,6 +290,30 @@
|
|||
dependencies:
|
||||
any-observable "^0.3.0"
|
||||
|
||||
"@stimulus/core@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@stimulus/core/-/core-1.1.1.tgz#42b0cfe5b73ca492f41de64b77a03980bae92c82"
|
||||
integrity sha512-PVJv7IpuQx0MVPCBblXc6O2zbCmU8dlxXNH4bC9KK6LsvGaE+PCXXrXQfXUwAsse1/CmRu/iQG7Ov58himjiGg==
|
||||
dependencies:
|
||||
"@stimulus/mutation-observers" "^1.1.1"
|
||||
|
||||
"@stimulus/multimap@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@stimulus/multimap/-/multimap-1.1.1.tgz#b95e3fd607345ab36e5d5b55486ee1a12d56b331"
|
||||
integrity sha512-26R1fI3a8uUj0WlMmta4qcfIQGlagegdP4PTz6lz852q/dXlG6r+uPS/bx+H8GtfyS+OOXVr3SkZ0Zg0iRqRfQ==
|
||||
|
||||
"@stimulus/mutation-observers@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@stimulus/mutation-observers/-/mutation-observers-1.1.1.tgz#0f6c6f081308427fed2a26360dda0c173b79cfc0"
|
||||
integrity sha512-/zCnnw1KJlWO2mrx0yxYaRFZWMGnDMdOgSnI4hxDLxdWVuL2HMROU8FpHWVBLjKY3T9A+lGkcrmPGDHF3pfS9w==
|
||||
dependencies:
|
||||
"@stimulus/multimap" "^1.1.1"
|
||||
|
||||
"@stimulus/webpack-helpers@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@stimulus/webpack-helpers/-/webpack-helpers-1.1.1.tgz#eff60cd4e58b921d1a2764dc5215f5141510f2c2"
|
||||
integrity sha512-XOkqSw53N9072FLHvpLM25PIwy+ndkSSbnTtjKuyzsv8K5yfkFB2rv68jU1pzqYa9FZLcvZWP4yazC0V38dx9A==
|
||||
|
||||
"@storybook/addon-actions@3.4.12":
|
||||
version "3.4.12"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.4.12.tgz#ff6cbaf563c3cb5d648d6a35f66cfa50ced49bf4"
|
||||
|
|
@ -8297,6 +8321,11 @@ multicast-dns@^6.0.1:
|
|||
dns-packet "^1.3.1"
|
||||
thunky "^1.0.2"
|
||||
|
||||
mutationobserver-shim@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#65869630bc89d7bf8c9cd9cb82188cd955aacd2b"
|
||||
integrity sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==
|
||||
|
||||
mute-stream@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||
|
|
@ -12071,6 +12100,14 @@ stickyfill@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/stickyfill/-/stickyfill-1.1.1.tgz#39413fee9d025c74a7e59ceecb23784cc0f17f02"
|
||||
integrity sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI=
|
||||
|
||||
stimulus@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/stimulus/-/stimulus-1.1.1.tgz#53c2fded6849e7b85eed3ed8dd76e33abd74bec5"
|
||||
integrity sha512-R0mBqKp48YnRDZOxZ8hiOH4Ilph3Yj78CIFTBkCwyHs4iGCpe7xlEdQ7cjIxb+7qVCSxFKgxO+mAQbsNgt/5XQ==
|
||||
dependencies:
|
||||
"@stimulus/core" "^1.1.1"
|
||||
"@stimulus/webpack-helpers" "^1.1.1"
|
||||
|
||||
store2@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/store2/-/store2-2.7.1.tgz#22070b7dc04748a792fc6912a58ab99d3a21d788"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue