(rips shirt) HAUUUGH JARVASCRIPT

This commit is contained in:
codl 2017-08-31 18:59:09 +02:00
parent 984fce51d2
commit 04654a637c
No known key found for this signature in database
GPG Key ID: 6CD7C8891ED1233A
15 changed files with 612 additions and 48 deletions

View File

@ -1,3 +1,5 @@
import Banner from '../components/Banner.html';
(function(){
if(!('fetch' in window)){
return;
@ -9,6 +11,12 @@
let form = document.forms.settings;
let backoff_level = 0;
let banner_el = document.querySelector('.main-banner');
banner_el.innerHTML = '';
let banner = new Banner({
target: banner_el,
});
function hide_status(){
status_display.classList.remove('error', 'success', 'saving');
status_display.classList.add('hidden');
@ -107,7 +115,7 @@
// silently send_settings in case the user changed settings while the page was loading
send_settings(get_all_inputs());
let viewer_update_interval = 1000;
let viewer_update_interval = 500;
function fetch_viewer(){
viewer_update_interval *= 2;
@ -121,22 +129,37 @@
let last_viewer = {};
function update_viewer(viewer){
if(JSON.stringify(last_viewer) == JSON.stringify(viewer)){
if(last_viewer == JSON.stringify(viewer)){
return;
}
last_viewer = JSON.stringify(viewer);
document.querySelector('#post-count').textContent = viewer.post_count;
document.querySelector('#eligible-estimate').textContent = viewer.eligible_for_delete_estimate;
document.querySelector('#display-name').textContent = viewer.display_name || viewer.screen_name;
document.querySelector('#display-name').title = '@' + viewer.screen_name;
document.querySelector('#avatar').src = viewer.avatar_url;
viewer_update_interval = 1000;
last_viewer = viewer;
viewer_update_interval = 500;
viewer.next_delete = new Date(viewer.next_delete);
viewer.last_delete = new Date(viewer.last_delete);
banner.set(viewer);
}
update_viewer(JSON.parse(document.querySelector('script[data-viewer]').textContent))
function set_viewer_timeout(){
setTimeout(() => fetch_viewer().then(update_viewer).then(set_viewer_timeout, set_viewer_timeout),
viewer_update_interval);
}
set_viewer_timeout();
banner.on('toggle', enabled => {
send_settings({policy_enabled: enabled});
// TODO show error or spinner if it takes over a second
})
})();

View File

@ -32,6 +32,10 @@ section > * {
padding-right: 5rem;
}
section > .container {
padding: 0;
}
section > ul {
padding-left: 7rem;
}
@ -74,7 +78,8 @@ input, select, button {
margin-bottom: 1rem;
}
body > section > .banner {
body > section > .banner,
body > section > .container > .banner {
padding-left: 4.3rem;
}

149
components/Banner.html Normal file
View File

@ -0,0 +1,149 @@
<div class='banner {{policy_enabled?'enabled':'disabled'}}'>
<div class='btn-group right'>
<button class='primary' on:click='toggle(policy_enabled)'>{{policy_enabled?'Disable':'Enable'}}</button>
</div>
<div>
Forget is currently {{policy_enabled?'enabled':'disabled'}} on your account.
</div>
<div class='timers'>
{{#if last_delete }}
<span class='last-delete {{(!last_delete)?'hidden':''}}'>
Last delete {{rel_past(now - last_delete)}}.
</span>
{{/if }}
<span class='next-delete {{(!policy_enabled || !next_delete || !eligible_for_delete_estimate)?'hidden':''}}'>
Next delete {{rel_future(next_delete - now)}}.
</span>
</div>
</div>
<style>
.timers {
font-size: 0.8em;
}
.timers > * {
transition-property: opacity, transform;
transition-duration: 0.4s;
}
.timers > .hidden {
opacity: 0;
transform: translateY(-100%);
pointer-events: none;
}
.btn-group {
margin-right: 5rem;
}
.right {
float: right;
}
button.primary {
border: none;
padding: 0.5rem 1rem;
}
.enabled button.primary {
background-color: transparent;
border: 1px solid currentColor;
}
.disabled button.primary {
background-color: #37d;
color: white;
font-weight: bold;
}
.banner {
transition: background-color 0.6s;
}
</style>
<script>
function absmod(n, x){
n = n % x;
if(n < 0){
n += x
}
return n
}
function s(n){
// utility for plurals
if(n > 1){
return 's';
}
return '';
}
function rel(millis){
let secs = Math.floor(millis/1000)
if(secs < 120){
return `${secs} seconds`;
}
let mins = Math.floor(secs/60);
if(mins < 60){
return `${mins} minute${s(mins)}`;
}
let hours = Math.floor(mins/60);
mins = mins % 60;
if(hours < 6){
return `${hours}h ${mins}m`;
}
if(hours < 48){
return `${hours} hour${s(hours)}`;
}
let days = Math.floor(hours/24);
return `${days} days`;
}
export default {
data(){
return {
policy_enabled: false,
next_delete: null,
last_delete: null,
now: +(new Date()),
}
},
helpers: {
rel_future(millis){
if(millis < 2000){
let secs = Math.floor(millis/1000)
let ndots = absmod(-secs, 3);
let out = 'anytime now';
for(; ndots > 0; ndots--){
out += '.';
}
return out;
}
return `in ${rel(millis)}`;
},
rel_past(millis){
if(millis < 2000){
return 'just now';
}
return `${rel(millis)} ago`;
},
},
methods: {
toggle(policy_enabled){
policy_enabled = !policy_enabled;
this.set({policy_enabled});
this.fire('toggle', policy_enabled);
}
},
oncreate () {
this.interval = setInterval( () => {
this.set({ now: +(new Date()) });
}, 1000 );
},
ondestroy () {
clearInterval( this.interval );
},
};
</script>

29
dodo.py
View File

@ -1,4 +1,6 @@
from doit import create_after
from glob import glob
from itertools import chain
def reltouch(source_filename, dest_filename):
@ -73,7 +75,7 @@ def task_service_icon():
def task_copy():
"copy assets verbatim"
assets = ('icon.png', 'logotype.png', 'settings.js')
assets = ('icon.png', 'logotype.png')
def do_the_thing(src, dst):
from shutil import copy
@ -111,17 +113,38 @@ def task_minify_css():
)
def task_rollup():
"""rollup javascript bundle"""
filenames = ['settings.js']
for filename in filenames:
src = 'assets/{}'.format(filename)
dst = 'static/{}'.format(filename)
yield dict(
name=filename,
file_dep=list(chain(
# fuck it
glob('assets/*.js'),
glob('components/*.html'))),
targets=[dst],
clean=True,
actions=[
['node_modules/.bin/rollup', '-c',
'-i', src, '-o', dst, '-f', 'iife'],
],
)
@create_after('logotype')
@create_after('service_icon')
@create_after('copy')
@create_after('minify_css')
@create_after('rollup')
def task_compress():
"""
make gzip and brotli compressed versions of each
static file for the server to lazily serve
"""
from glob import glob
from itertools import chain
files = chain(
glob('static/*.css'),

View File

@ -1,4 +1,4 @@
from datetime import timedelta, datetime
from datetime import timedelta, datetime, timezone
from .timescales import SCALES
@ -77,4 +77,4 @@ def relative(interval):
def relnow(time):
return relative(time - datetime.now())
return relative(time - datetime.now(timezone.utc))

15
lib/json.py Normal file
View File

@ -0,0 +1,15 @@
from json import dumps
def account(acc):
return dumps(dict(
post_count=acc.post_count(),
eligible_for_delete_estimate=acc.estimate_eligible_for_delete(),
display_name=acc.display_name,
screen_name=acc.screen_name,
avatar_url=acc.avatar_url,
id=acc.id,
service=acc.service,
policy_enabled=acc.policy_enabled,
next_delete=acc.next_delete.isoformat(),
last_delete=acc.last_delete.isoformat(),
))

View File

@ -7,4 +7,5 @@ attrs = (
'policy_keep_younger_significand',
'policy_keep_media',
'policy_keep_direct',
'policy_enabled',
)

View File

@ -0,0 +1,30 @@
"""change timestamps to timestamptzs
Revision ID: 90b5b84abc6a
Revises: 6fd1f5b43824
Create Date: 2017-08-31 16:46:16.785021
"""
from alembic import op
from sqlalchemy.types import DateTime
# revision identifiers, used by Alembic.
revision = '90b5b84abc6a'
down_revision = '6fd1f5b43824'
branch_labels = None
depends_on = None
def upgrade():
for table in ('accounts', 'oauth_tokens', 'posts', 'sessions',
'twitter_archives', 'mastodon_apps'):
for column in ('created_at', 'updated_at'):
op.alter_column(table, column, type_=DateTime(timezone=True))
def downgrade():
for table in ('account', 'oauth_tokens', 'posts', 'sessions',
'twitter_archives', 'mastodon_apps'):
for column in ('created_at', 'updated_at'):
op.alter_column(table, column, type_=DateTime(timezone=False))

View File

@ -0,0 +1,26 @@
"""change more timestamps to timestamptzs
Revision ID: f95af1a8d89f
Revises: 90b5b84abc6a
Create Date: 2017-08-31 17:00:20.538070
"""
from alembic import op
from sqlalchemy.types import DateTime
# revision identifiers, used by Alembic.
revision = 'f95af1a8d89f'
down_revision = '90b5b84abc6a'
branch_labels = None
depends_on = None
def upgrade():
for column in ('last_fetch', 'last_refresh', 'last_delete', 'next_delete'):
op.alter_column('accounts', column, type_=DateTime(timezone=True))
def downgrade():
for column in ('last_fetch', 'last_refresh', 'last_delete', 'next_delete'):
op.alter_column('accounts', column, type_=DateTime(timezone=False))

View File

@ -1,4 +1,4 @@
from datetime import timedelta, datetime
from datetime import timedelta, datetime, timezone
from app import db
import secrets
@ -6,10 +6,12 @@ from lib.interval import decompose_interval
class TimestampMixin(object):
created_at = db.Column(db.DateTime, server_default=db.func.now(),
nullable=False)
updated_at = db.Column(db.DateTime, server_default=db.func.now(),
onupdate=db.func.now(), nullable=False)
created_at = db.Column(
db.DateTime(timezone=True), server_default=db.func.now(),
nullable=False)
updated_at = db.Column(
db.DateTime(timezone=True), server_default=db.func.now(),
onupdate=db.func.now(), nullable=False)
def touch(self):
self.updated_at = db.func.now()
@ -91,10 +93,14 @@ class Account(TimestampMixin, RemoteIDMixin):
avatar_url = db.Column(db.String)
reported_post_count = db.Column(db.Integer)
last_fetch = db.Column(db.DateTime, server_default='epoch', index=True)
last_refresh = db.Column(db.DateTime, server_default='epoch', index=True)
last_delete = db.Column(db.DateTime, index=True)
next_delete = db.Column(db.DateTime, server_default='epoch', index=True)
last_fetch = db.Column(db.DateTime(timezone=True),
server_default='epoch', index=True)
last_refresh = db.Column(db.DateTime(timezone=True),
server_default='epoch', index=True)
last_delete = db.Column(db.DateTime(timezone=True),
index=True)
next_delete = db.Column(db.DateTime(timezone=True),
server_default='epoch', index=True)
def touch_fetch(self):
self.last_fetch = db.func.now()
@ -103,7 +109,8 @@ class Account(TimestampMixin, RemoteIDMixin):
self.last_delete = db.func.now()
# if it's been more than 1 delete cycle ago that we've deleted a post,
# reset next_delete to be 1 cycle away
if(datetime.now() - self.next_delete > self.policy_delete_every):
if (datetime.now(timezone.utc) - self.next_delete
> self.policy_delete_every):
self.next_delete = db.func.now() + self.policy_delete_every
else:
self.next_delete += self.policy_delete_every
@ -116,9 +123,9 @@ class Account(TimestampMixin, RemoteIDMixin):
if not (value == timedelta(0) or value >= timedelta(minutes=1)):
value = timedelta(minutes=1)
if key == 'policy_delete_every' and \
datetime.now() + value < self.next_delete:
datetime.now(timezone.utc) + value < self.next_delete:
# make sure that next delete is not in the far future
self.next_delete = datetime.now() + value
self.next_delete = datetime.now(timezone.utc) + value
return value
# pylint: disable=R0201

7
package.json Normal file
View File

@ -0,0 +1,7 @@
{
"dependencies": {},
"devDependencies": {
"rollup": "^0.49.2",
"rollup-plugin-svelte": "^3.1.0"
}
}

12
rollup.config.js Normal file
View File

@ -0,0 +1,12 @@
import svelte from 'rollup-plugin-svelte';
export default {
output: {
format: 'iife',
},
plugins: [
svelte({
include: 'components/**/*.html',
}),
]
}

View File

@ -1,6 +1,6 @@
from flask import render_template, url_for, redirect, request, g, Response,\
jsonify
from datetime import datetime, timedelta
jsonify, make_response
from datetime import datetime, timedelta, timezone
import lib.twitter
import lib.mastodon
from lib.auth import require_auth, require_auth_api, csrf
@ -15,6 +15,7 @@ import version
import lib.version
import lib.brotli
import lib.settings
import lib.json
@app.before_request
@ -65,7 +66,9 @@ def index():
'logged_in.html',
scales=lib.interval.SCALES,
tweet_archive_failed='tweet_archive_failed' in request.args,
settings_error='settings_error' in request.args)
settings_error='settings_error' in request.args,
viewer_json=lib.json.account(get_viewer()),
)
else:
instances = (
MastodonInstance.query
@ -200,7 +203,8 @@ def enable():
else "" }
Go ahead?
""")
if g.viewer.account.next_delete < datetime.now() - timedelta(days=365):
if (g.viewer.account.next_delete <
datetime.now(timezone.utc) - timedelta(days=365)):
return render_template(
'warn.html',
message="""
@ -212,7 +216,8 @@ def enable():
if not g.viewer.account.policy_enabled:
g.viewer.account.next_delete = (
datetime.now() + g.viewer.account.policy_delete_every)
datetime.now(timezone.utc)
+ g.viewer.account.policy_delete_every)
g.viewer.account.policy_enabled = True
db.session.commit()
@ -243,20 +248,13 @@ def api_settings_put():
db.session.commit()
return jsonify(status='success', updated=updated)
@app.route('/api/viewer')
@require_auth_api
def api_viewer():
viewer = get_viewer()
return jsonify(
post_count=viewer.post_count(),
eligible_for_delete_estimate=viewer.estimate_eligible_for_delete(),
display_name=viewer.display_name,
screen_name=viewer.screen_name,
avatar_url=viewer.avatar_url,
id=viewer.id,
service=viewer.service,
)
resp = make_response(lib.json.account(viewer))
resp.headers.set('content-type', 'application/json')
return resp
@app.route('/api/viewer/timers')

View File

@ -11,17 +11,19 @@
<span id='display-name' title='@{{g.viewer.account.screen_name}}'>{{g.viewer.account.display_name or g.viewer.account.screen_name}}</span>!
<a href="{{url_for('logout')}}">Log out</a>
</p>
{% set enabled_disabled = "enabled" if g.viewer.account.policy_enabled else "disabled" %}
<div class="banner {{enabled_disabled}}">Forget is currently {{ enabled_disabled }} on your account.
{% if g.viewer.account.policy_enabled -%}
<form action='{{url_for("disable")}}' method='post' enctype='multipart/form-data'>
<input type='submit' value='Disable'>
{% else -%}
<form action='{{url_for("enable")}}' method='post' enctype='multipart/form-data'>
<input type='submit' value='Enable'>
{% endif %}
<input type='hidden' name='csrf-token' value='{{g.viewer.csrf_token}}'>
</form>
<div class='main-banner container'>
{% set enabled_disabled = "enabled" if g.viewer.account.policy_enabled else "disabled" %}
<div class="banner {{enabled_disabled}}">Forget is currently {{ enabled_disabled }} on your account.
{% if g.viewer.account.policy_enabled -%}
<form action='{{url_for("disable")}}' method='post' enctype='multipart/form-data'>
<input type='submit' value='Disable'>
{% else -%}
<form action='{{url_for("enable")}}' method='post' enctype='multipart/form-data'>
<input type='submit' value='Enable'>
{% endif %}
<input type='hidden' name='csrf-token' value='{{g.viewer.csrf_token}}'>
</form>
</div>
</div>
{% set post_count = g.viewer.account.post_count() %}
<p>Currently keeping track of <span id="post-count">{{ post_count }}</span> of your posts, roughly <span id="eligible-estimate">{{ g.viewer.account.estimate_eligible_for_delete() }}</span> of which currently match your expiration rules.</p>
@ -115,4 +117,8 @@
</section>
{% endif %}
<script type='text/json' data-viewer>
{{ viewer_json|safe }}
</script>
{% endblock %}

262
yarn.lock Normal file
View File

@ -0,0 +1,262 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
arr-diff@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
dependencies:
arr-flatten "^1.0.1"
arr-flatten@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
array-unique@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
braces@^1.8.2:
version "1.8.5"
resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
dependencies:
expand-range "^1.8.1"
preserve "^0.2.0"
repeat-element "^1.1.2"
estree-walker@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa"
expand-brackets@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
dependencies:
is-posix-bracket "^0.1.0"
expand-range@^1.8.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
dependencies:
fill-range "^2.1.0"
extglob@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
dependencies:
is-extglob "^1.0.0"
filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
fill-range@^2.1.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
dependencies:
is-number "^2.1.0"
isobject "^2.0.0"
randomatic "^1.1.3"
repeat-element "^1.1.2"
repeat-string "^1.5.2"
for-in@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
for-own@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
dependencies:
for-in "^1.0.1"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
dependencies:
glob-parent "^2.0.0"
is-glob "^2.0.0"
glob-parent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
dependencies:
is-glob "^2.0.0"
is-buffer@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
is-dotfile@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
is-equal-shallow@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
dependencies:
is-primitive "^2.0.0"
is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
dependencies:
is-extglob "^1.0.0"
is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
dependencies:
kind-of "^3.0.2"
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
dependencies:
kind-of "^3.0.2"
is-posix-bracket@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
is-primitive@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
isarray@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
isobject@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
dependencies:
isarray "1.0.0"
kind-of@^3.0.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
dependencies:
is-buffer "^1.1.5"
kind-of@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
dependencies:
is-buffer "^1.1.5"
micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
dependencies:
arr-diff "^2.0.0"
array-unique "^0.2.1"
braces "^1.8.2"
expand-brackets "^0.1.4"
extglob "^0.3.1"
filename-regex "^2.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.1"
kind-of "^3.0.2"
normalize-path "^2.0.1"
object.omit "^2.0.0"
parse-glob "^3.0.4"
regex-cache "^0.4.2"
normalize-path@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
dependencies:
remove-trailing-separator "^1.0.1"
object.omit@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
dependencies:
for-own "^0.1.4"
is-extendable "^0.1.1"
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
dependencies:
glob-base "^0.3.0"
is-dotfile "^1.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.0"
preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
randomatic@^1.1.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
dependencies:
is-number "^3.0.0"
kind-of "^4.0.0"
regex-cache@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
dependencies:
is-equal-shallow "^0.1.3"
is-primitive "^2.0.0"
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
repeat-element@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
repeat-string@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
require-relative@^0.8.7:
version "0.8.7"
resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de"
rollup-plugin-svelte@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-svelte/-/rollup-plugin-svelte-3.1.0.tgz#12405b2ac51016c1a35cc1be6840ac0ad6df8163"
dependencies:
require-relative "^0.8.7"
rollup-pluginutils "^2.0.1"
sourcemap-codec "^1.3.1"
svelte "^1.25.1"
rollup-pluginutils@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz#7ec95b3573f6543a46a6461bd9a7c544525d0fc0"
dependencies:
estree-walker "^0.3.0"
micromatch "^2.3.11"
rollup@^0.49.2:
version "0.49.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.49.2.tgz#a18f07595cde3b11875c9fece45b25ad3b220d1a"
sourcemap-codec@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.3.1.tgz#9ad6f9bdbd691931016e30939dbc868673323146"
dependencies:
vlq "^0.2.1"
svelte@^1.25.1:
version "1.34.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-1.34.0.tgz#8c2ad2f78b2896e801d4fb50e1043fd4837232be"
vlq@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.2.tgz#e316d5257b40b86bb43cb8d5fea5d7f54d6b0ca1"