forked from e621ng/e621ng
Merge branch 'master' into ruby-3
This commit is contained in:
commit
57f711bb4d
6
Gemfile
6
Gemfile
@ -42,9 +42,6 @@ gem 'net-smtp'
|
||||
gem 'net-pop'
|
||||
gem 'net-imap'
|
||||
|
||||
# needed for looser jpeg header compat
|
||||
gem 'ruby-imagespec', :require => "image_spec", :git => "https://github.com/r888888888/ruby-imagespec.git", :branch => "exif-fixes"
|
||||
|
||||
group :production, :staging do
|
||||
gem 'unicorn', :platforms => :ruby
|
||||
end
|
||||
@ -55,7 +52,6 @@ group :production do
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
gem 'awesome_print'
|
||||
gem 'pry-byebug'
|
||||
gem 'listen'
|
||||
gem 'puma'
|
||||
@ -67,9 +63,7 @@ group :test do
|
||||
gem "factory_bot"
|
||||
gem "mocha", :require => "mocha/minitest"
|
||||
gem "ffaker"
|
||||
gem "simplecov", :require => false
|
||||
gem "timecop"
|
||||
gem "webmock"
|
||||
gem "minitest-ci"
|
||||
gem "mock_redis"
|
||||
end
|
||||
|
23
Gemfile.lock
23
Gemfile.lock
@ -1,10 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/r888888888/ruby-imagespec.git
|
||||
revision: 2dab9811f4abb4fbaeea66feb42e388ba545b2d8
|
||||
branch: exif-fixes
|
||||
specs:
|
||||
ruby-imagespec (0.3.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/zwagoth/dtext_rb.git
|
||||
revision: efc37c7795c53425935245cde57cef2121b4f402
|
||||
@ -86,7 +79,6 @@ GEM
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
awesome_print (1.9.2)
|
||||
bcrypt (3.1.16)
|
||||
bootsnap (1.9.3)
|
||||
msgpack (~> 1.0)
|
||||
@ -108,7 +100,6 @@ GEM
|
||||
dalli (3.1.5)
|
||||
diff-lcs (1.5.0)
|
||||
digest (3.1.0)
|
||||
docile (1.4.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
@ -200,8 +191,6 @@ GEM
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.15.0)
|
||||
minitest-ci (3.4.0)
|
||||
minitest (>= 5.0.6)
|
||||
mocha (1.13.0)
|
||||
mock_redis (0.29.0)
|
||||
ruby2_keywords
|
||||
@ -243,7 +232,7 @@ GEM
|
||||
puma (5.6.4)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rack (2.2.3.1)
|
||||
rack-proxy (0.7.0)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
@ -323,12 +312,6 @@ GEM
|
||||
simple_form (5.1.0)
|
||||
actionpack (>= 5.2)
|
||||
activemodel (>= 5.2)
|
||||
simplecov (0.21.2)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.3)
|
||||
sprockets (4.0.3)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
@ -377,7 +360,6 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.10.0)
|
||||
addressable
|
||||
awesome_print
|
||||
bcrypt
|
||||
bootsnap
|
||||
cityhash
|
||||
@ -396,7 +378,6 @@ DEPENDENCIES
|
||||
mailgun-ruby
|
||||
marcel
|
||||
memoist
|
||||
minitest-ci
|
||||
mocha
|
||||
mock_redis
|
||||
net-imap
|
||||
@ -413,7 +394,6 @@ DEPENDENCIES
|
||||
resolv
|
||||
responders
|
||||
retriable
|
||||
ruby-imagespec!
|
||||
ruby-vips
|
||||
sanitize
|
||||
shoulda-context
|
||||
@ -421,7 +401,6 @@ DEPENDENCIES
|
||||
sidekiq
|
||||
sidekiq-unique-jobs
|
||||
simple_form
|
||||
simplecov
|
||||
streamio-ffmpeg
|
||||
timecop
|
||||
unicorn
|
||||
|
@ -5,15 +5,13 @@ module Admin
|
||||
def index
|
||||
end
|
||||
|
||||
def enable_uploads
|
||||
DangerZone.enable_uploads
|
||||
StaffAuditLog.log(:uploads_enable, CurrentUser.user)
|
||||
redirect_to admin_danger_zone_index_path
|
||||
end
|
||||
|
||||
def disable_uploads
|
||||
DangerZone.disable_uploads
|
||||
StaffAuditLog.log(:uploads_disable, CurrentUser.user)
|
||||
def uploading_limits
|
||||
new_level = params[:uploading_limits][:min_level].to_i
|
||||
raise ArgumentError, "#{new_level} is not valid" unless User.level_hash.values.include? new_level
|
||||
if new_level != DangerZone.min_upload_level
|
||||
DangerZone.min_upload_level = new_level
|
||||
StaffAuditLog.log(:min_upload_level, CurrentUser.user, { level: new_level })
|
||||
end
|
||||
redirect_to admin_danger_zone_index_path
|
||||
end
|
||||
end
|
||||
|
@ -6,6 +6,7 @@ class PostReplacementsController < ApplicationController
|
||||
|
||||
content_security_policy only: [:new] do |p|
|
||||
p.img_src :self, :data, :blob, "*"
|
||||
p.media_src :self, :data, :blob, "*"
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -3,7 +3,6 @@ class PostSetMaintainersController < ApplicationController
|
||||
respond_to :js, except: [:index]
|
||||
before_action :member_only
|
||||
|
||||
|
||||
def index
|
||||
@invites = PostSetMaintainer.where(user_id: CurrentUser.id).order(updated_at: :desc).includes(:post_set)
|
||||
end
|
||||
@ -94,9 +93,8 @@ class PostSetMaintainersController < ApplicationController
|
||||
end
|
||||
|
||||
def check_edit_access(set)
|
||||
unless set.is_owner?(CurrentUser) || CurrentUser.is_admin?
|
||||
unless set.can_edit_settings?(CurrentUser)
|
||||
raise User::PrivilegeError
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -41,24 +41,23 @@ class PostSetsController < ApplicationController
|
||||
|
||||
def edit
|
||||
@post_set = PostSet.find(params[:id])
|
||||
check_edit_access(@post_set)
|
||||
@can_edit = @post_set.is_owner?(CurrentUser) || CurrentUser.is_admin?
|
||||
check_post_edit_access(@post_set)
|
||||
respond_with(@post_set)
|
||||
end
|
||||
|
||||
def update
|
||||
@post_set = PostSet.find(params[:id])
|
||||
check_edit_access(@post_set)
|
||||
check_settings_edit_access(@post_set)
|
||||
@post_set.update(set_params)
|
||||
flash[:notice] = @post_set.valid? ? 'Set updated' : @post_set.errors.full_messages.join('; ')
|
||||
|
||||
if CurrentUser.is_admin? && !@post_set.is_owner?(CurrentUser.user)
|
||||
unless @post_set.is_owner?(CurrentUser.user)
|
||||
if @post_set.saved_change_to_is_public?
|
||||
ModAction.log(:set_mark_private, {set_id: @post_set.id, user_id: @post_set.creator_id})
|
||||
ModAction.log(:set_change_visibility, { set_id: @post_set.id, user_id: @post_set.creator_id, is_public: @post_set.is_public })
|
||||
end
|
||||
|
||||
if @post_set.saved_change_to_watched_attribute?
|
||||
Modaction.log(:set_update, {set_id: @post_set.id, user_id: @post_set.creator_id})
|
||||
if @post_set.saved_change_to_watched_attributes?
|
||||
ModAction.log(:set_update, { set_id: @post_set.id, user_id: @post_set.creator_id })
|
||||
end
|
||||
end
|
||||
|
||||
@ -71,13 +70,13 @@ class PostSetsController < ApplicationController
|
||||
|
||||
def post_list
|
||||
@post_set = PostSet.find(params[:id])
|
||||
check_edit_access(@post_set)
|
||||
check_post_edit_access(@post_set)
|
||||
respond_with(@post_set)
|
||||
end
|
||||
|
||||
def update_posts
|
||||
@post_set = PostSet.find(params[:id])
|
||||
check_edit_access(@post_set)
|
||||
check_post_edit_access(@post_set)
|
||||
@post_set.update(update_posts_params)
|
||||
flash[:notice] = @post_set.valid? ? 'Set posts updated.' : @post_set.errors.full_messages.join('; ')
|
||||
|
||||
@ -86,9 +85,7 @@ class PostSetsController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@post_set = PostSet.find(params[:id])
|
||||
unless @post_set.is_owner?(CurrentUser.user) || CurrentUser.is_admin?
|
||||
raise User::PrivilegeError
|
||||
end
|
||||
check_settings_edit_access(@post_set)
|
||||
if CurrentUser.is_admin?
|
||||
ModAction.log(:set_delete, {set_id: @post_set.id, user_id: @post_set.creator_id})
|
||||
end
|
||||
@ -110,7 +107,7 @@ class PostSetsController < ApplicationController
|
||||
|
||||
def add_posts
|
||||
@post_set = PostSet.find(params[:id])
|
||||
check_edit_access(@post_set)
|
||||
check_post_edit_access(@post_set)
|
||||
@post_set.add(params[:post_ids].map(&:to_i))
|
||||
@post_set.save
|
||||
respond_with(@post_set)
|
||||
@ -118,7 +115,7 @@ class PostSetsController < ApplicationController
|
||||
|
||||
def remove_posts
|
||||
@post_set = PostSet.find(params[:id])
|
||||
check_edit_access(@post_set)
|
||||
check_post_edit_access(@post_set)
|
||||
@post_set.remove(params[:post_ids].map(&:to_i))
|
||||
@post_set.save
|
||||
respond_with(@post_set)
|
||||
@ -126,17 +123,20 @@ class PostSetsController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def check_edit_access(set)
|
||||
unless set.is_owner?(CurrentUser.user) || set.is_maintainer?(CurrentUser.user)
|
||||
def check_settings_edit_access(set)
|
||||
unless set.can_edit_settings?(CurrentUser.user)
|
||||
raise User::PrivilegeError
|
||||
end
|
||||
if !set.is_public && !set.is_owner?(CurrentUser.user)
|
||||
end
|
||||
|
||||
def check_post_edit_access(set)
|
||||
unless set.can_edit_posts?(CurrentUser.user)
|
||||
raise User::PrivilegeError
|
||||
end
|
||||
end
|
||||
|
||||
def check_view_access(set)
|
||||
unless set.is_public || set.is_owner?(CurrentUser.user) || CurrentUser.is_admin?
|
||||
unless set.can_view?(CurrentUser.user)
|
||||
raise User::PrivilegeError
|
||||
end
|
||||
end
|
||||
|
@ -13,7 +13,7 @@ class TagAliasRequestsController < ApplicationController
|
||||
elsif @tag_alias_request.forum_topic
|
||||
redirect_to forum_topic_path(@tag_alias_request.forum_topic)
|
||||
else
|
||||
redirect_to tag_alias_path(@tag_alias_request.tag_alias)
|
||||
redirect_to tag_alias_path(@tag_alias_request.tag_relationship)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -13,7 +13,7 @@ class TagImplicationRequestsController < ApplicationController
|
||||
elsif @tag_implication_request.forum_topic
|
||||
redirect_to forum_topic_path(@tag_implication_request.forum_topic)
|
||||
else
|
||||
redirect_to tag_implication_path(@tag_implication_request.tag_implication)
|
||||
redirect_to tag_implication_path(@tag_implication_request.tag_relationship)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
class UploadsController < ApplicationController
|
||||
before_action :member_only
|
||||
before_action :janitor_only, only: [:index, :show]
|
||||
before_action :ensure_uploads_enabled, only: [:new, :create]
|
||||
respond_to :html, :json
|
||||
content_security_policy only: [:new] do |p|
|
||||
p.img_src :self, :data, :blob, "*"
|
||||
@ -32,10 +33,6 @@ class UploadsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
if DangerZone.uploads_disabled?
|
||||
return access_denied("Uploads are disabled.")
|
||||
end
|
||||
|
||||
Post.transaction do
|
||||
@service = UploadService.new(upload_params)
|
||||
@upload = @service.start!
|
||||
@ -81,4 +78,10 @@ class UploadsController < ApplicationController
|
||||
|
||||
params.require(:upload).permit(permitted_params).merge(uploader_id: CurrentUser.id, uploader_ip_addr: CurrentUser.ip_addr)
|
||||
end
|
||||
|
||||
def ensure_uploads_enabled
|
||||
if DangerZone.uploads_disabled?(CurrentUser.user)
|
||||
access_denied "Uploads are disabled"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -100,8 +100,8 @@ class ModActionDecorator < ApplicationDecorator
|
||||
|
||||
### Set ###
|
||||
|
||||
when "set_mark_private"
|
||||
"Made set ##{vals['set_id']} by #{user} private"
|
||||
when "set_change_visibility"
|
||||
"Made set ##{vals['set_id']} by #{user} #{vals['is_public'] ? 'public' : 'private'}"
|
||||
when "set_update"
|
||||
"Edited set ##{vals['set_id']} by #{user}"
|
||||
when "set_delete"
|
||||
|
@ -40,6 +40,7 @@ export { default as PostModeMenu } from '../src/javascripts/post_mode_menu.js';
|
||||
export { default as PostReplacement } from '../src/javascripts/post_replacement.js';
|
||||
export { default as PostVersions } from '../src/javascripts/post_versions.js';
|
||||
export { default as RelatedTag } from '../src/javascripts/related_tag.js';
|
||||
export { default as Replacer } from '../src/javascripts/replacer.js';
|
||||
export { default as Shortcuts } from '../src/javascripts/shortcuts.js';
|
||||
export { default as Utility } from '../src/javascripts/utility.js';
|
||||
export { default as TagRelationships } from '../src/javascripts/tag_relationships.js';
|
||||
|
@ -19,25 +19,16 @@ RelatedTag.initialize_all = function() {
|
||||
}
|
||||
|
||||
import TagEditor from './tag_editor.vue';
|
||||
import Vue from 'vue';
|
||||
import { createApp } from 'vue';
|
||||
|
||||
RelatedTag.tag_editor_setup = false;
|
||||
RelatedTag.init_post_show_editor = function() {
|
||||
if (RelatedTag.tag_editor_setup)
|
||||
return;
|
||||
RelatedTag.tag_editor_setup = true;
|
||||
const app = new Vue({
|
||||
render: (h) => h(TagEditor)
|
||||
});
|
||||
|
||||
app.$mount('#tag-string-editor');
|
||||
setTimeout(function() {
|
||||
// Work around that browsers seem to take a few frames to acknowledge that the element is there before it can be focused.
|
||||
const el = app.$children[0].$refs['otherTags'];
|
||||
el.style.height = el.scrollHeight + "px";
|
||||
el.focus();
|
||||
el.scrollIntoView();
|
||||
}, 20);
|
||||
const app = createApp(TagEditor);
|
||||
app.mount('#tag-string-editor');
|
||||
}
|
||||
|
||||
RelatedTag.on_click_related_tags_button = function (event) {
|
||||
|
@ -1,142 +0,0 @@
|
||||
const Replacer = {};
|
||||
|
||||
const thumbURLs = [
|
||||
"/images/notfound-preview.png",
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
];
|
||||
const thumbs = {
|
||||
notfound: "/images/notfound-preview.png",
|
||||
none: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
|
||||
};
|
||||
|
||||
Replacer.old_domain = "";
|
||||
|
||||
Replacer.update_preview_file = function (file) {
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
Replacer.set_preview_url(objectUrl);
|
||||
};
|
||||
|
||||
Replacer.update_preview_url = function () {
|
||||
const url = $("#post_replacement_replacement_url").val();
|
||||
if (!url) {
|
||||
Replacer.upload_allow_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const sampleURL = Replacer.isSampleURL(url);
|
||||
if (sampleURL !== false) {
|
||||
$('#bad_upload_url_reason').text(sampleURL);
|
||||
$('#bad_upload_url').show();
|
||||
return;
|
||||
} else {
|
||||
$('#bad_upload_url').hide();
|
||||
}
|
||||
|
||||
const domain = $("<a>").prop("href", url).prop("hostname");
|
||||
if (domain && domain !== Replacer.old_domain) {
|
||||
$.getJSON("/upload_whitelists/is_allowed.json", {url: url}, function(data) {
|
||||
if(data.domain) {
|
||||
Replacer.upload_allow_set(data.is_allowed, data.domain, data.reason);
|
||||
if(!data.is_allowed)
|
||||
Replacer.set_preview_url(thumbs.none);
|
||||
}
|
||||
});
|
||||
} else if (!domain) {
|
||||
Replacer.upload_allow_clear();
|
||||
}
|
||||
Replacer.old_domain = domain;
|
||||
Replacer.set_preview_url(url);
|
||||
};
|
||||
|
||||
Replacer.update_preview_dims = function () {
|
||||
const img = $('#replacement_preview_img')[0];
|
||||
if (thumbURLs.filter(function (x) {
|
||||
return img.src.indexOf(x) !== -1;
|
||||
}).length !== 0)
|
||||
return;
|
||||
Replacer.set_preview_dims(img.naturalHeight, img.naturalWidth);
|
||||
};
|
||||
|
||||
Replacer.preview_error = function (e) {
|
||||
const img = e.target;
|
||||
Replacer.set_preview_dims(-1, -1);
|
||||
if (thumbURLs.filter(function (x) {
|
||||
return img.src.indexOf(x) !== -1;
|
||||
}).length !== 0)
|
||||
return;
|
||||
Replacer.set_preview_url(thumbs.notfound);
|
||||
};
|
||||
|
||||
Replacer.set_preview_dims = function (height, width) {
|
||||
if (height <= 0 && width <= 0) {
|
||||
$('#replacement_preview_dims').text('');
|
||||
} else {
|
||||
$('#replacement_preview_dims').text(`${width}x${height}`);
|
||||
}
|
||||
};
|
||||
|
||||
Replacer.set_preview_url = function (url) {
|
||||
$('#replacement_preview_img').attr('src', url);
|
||||
};
|
||||
|
||||
|
||||
Replacer.update_preview = function () {
|
||||
const $file = $("#post_replacement_replacement_file")[0];
|
||||
if ($file && $file.files[0])
|
||||
Replacer.update_preview_file($file.files[0]);
|
||||
else
|
||||
Replacer.update_preview_url();
|
||||
}
|
||||
|
||||
Replacer.update_preview_paste = function () {
|
||||
setTimeout(Replacer.update_preview, 150);
|
||||
}
|
||||
|
||||
Replacer.isSampleURL = function (url) {
|
||||
const patterns = [
|
||||
{reason: 'Thumbnail URL', test: /[at]\.facdn\.net/gi},
|
||||
{reason: 'Sample URL', test: /pximg\.net.*\/img-master\//gi},
|
||||
{reason: 'Sample URL', test: /d3gz42uwgl1r1y\.cloudfront\.net\/.*\/\d+x\d+\./gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/[\w\-_]+\.(jpg|png)(:large)?$/gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/[\w\-_]+\?format=(jpg|png)(?!&name=orig)/gi},
|
||||
{reason: 'Sample URL', test: /derpicdn\.net\/.*\/large\./gi},
|
||||
{reason: 'Sample URL', test: /metapix\.net\/files\/(preview|screen)\//gi},
|
||||
{reason: 'Sample URL', test: /sofurryfiles\.com\/std\/preview/gi}];
|
||||
for (let i = 0; i < patterns.length; ++i) {
|
||||
const pattern = patterns[i];
|
||||
if (pattern.test.test(url))
|
||||
return pattern.reason;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Replacer.upload_allow_clear = function() {
|
||||
$('#whitelist-warning').hide();
|
||||
}
|
||||
|
||||
Replacer.upload_allow_set = function(allowed, domain, reason) {
|
||||
const classes = ['whitelist-warning-disallowed', 'whitelist-warning-allowed'];
|
||||
$('#whitelist-warning').removeClass().addClass(classes[allowed ? 1 : 0]);
|
||||
$('#whitelist-warning-domain').text(domain);
|
||||
if(allowed)
|
||||
$('#whitelist-warning-not').hide();
|
||||
else
|
||||
$('#whitelist-warning-not').show();
|
||||
$('#whitelist-warning').show();
|
||||
}
|
||||
|
||||
Replacer.init_uploader = function () {
|
||||
$('#replacement_preview_img').on('load', Replacer.update_preview_dims);
|
||||
$('#replacement_preview_img').on('error', Replacer.preview_error);
|
||||
$('#post_replacement_replacement_url').on('keyup', Replacer.update_preview);
|
||||
$('#post_replacement_replacement_file').on('change', Replacer.update_preview);
|
||||
$('#post_replacement_replacement_url').on('paste', Replacer.update_preview_paste);
|
||||
};
|
||||
|
||||
$(function () {
|
||||
if ($("#c-post-replacements > #a-new").length) {
|
||||
Replacer.init_uploader();
|
||||
}
|
||||
});
|
||||
|
||||
export default Replacer;
|
28
app/javascript/src/javascripts/replacement_uploader.vue
Normal file
28
app/javascript/src/javascripts/replacement_uploader.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<file-input @previewChanged="previewData = $event"
|
||||
:fileInputName="'post_replacement[replacement_file]'"
|
||||
:urlInputName="'post_replacement[replacement_url]'"></file-input>
|
||||
<Teleport to="#replacement-preview">
|
||||
<file-preview :data="previewData"></file-preview>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import filePreview from "./uploader/file_preview.vue";
|
||||
import fileInput from "./uploader/file_input.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
"file-preview": filePreview,
|
||||
"file-input": fileInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
previewData: {
|
||||
url: "",
|
||||
isVideo: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
9
app/javascript/src/javascripts/replacer.js
Normal file
9
app/javascript/src/javascripts/replacer.js
Normal file
@ -0,0 +1,9 @@
|
||||
import Replacer from "./replacement_uploader.vue";
|
||||
import { createApp } from "vue";
|
||||
|
||||
export default {
|
||||
init() {
|
||||
const app = createApp(Replacer);
|
||||
app.mount("#replacement-uploader");
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div v-show="!preview.show">
|
||||
<textarea class="tag-textarea" id="post_tag_string" v-model="tags" rows="5" data-autocomplete="tag-edit"
|
||||
ref="otherTags" name="post[tag_string]" spellcheck="false" @keyup="updateTagCount"></textarea>
|
||||
ref="otherTags" name="post[tag_string]" :spellcheck="false" @keyup="updateTagCount"></textarea>
|
||||
</div>
|
||||
<div v-show="preview.show">
|
||||
<tag-preview :tags="preview.tags" :loading="preview.loading" @close="previewFinalTags"></tag-preview>
|
||||
@ -27,9 +27,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import relatedTags from './uploader_related.vue';
|
||||
import tagPreview from './uploader_tag_preview.vue';
|
||||
import { nextTick } from 'vue';
|
||||
import relatedTags from './uploader/related.vue';
|
||||
import tagPreview from './uploader/tag_preview.vue';
|
||||
import Post from './posts';
|
||||
import Autocomplete from "./autocomplete.js.erb";
|
||||
import Utility from "./utility.js";
|
||||
@ -57,6 +57,13 @@
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
// Work around that browsers seem to take a few frames to acknowledge that the element is there before it can be focused.
|
||||
const el = this.$refs.otherTags;
|
||||
el.style.height = el.scrollHeight + "px";
|
||||
el.focus();
|
||||
el.scrollIntoView();
|
||||
}, 20);
|
||||
if(Utility.meta("enable-auto-complete") !== "true")
|
||||
return;
|
||||
Autocomplete.initialize_tag_autocomplete();
|
||||
@ -105,7 +112,7 @@
|
||||
}
|
||||
this.tags = groups.join('\n') + ' ';
|
||||
}
|
||||
Vue.nextTick(function() {
|
||||
nextTick(function() {
|
||||
Post.update_tag_count({target: $("#post_tag_string")});
|
||||
})
|
||||
},
|
||||
@ -152,9 +159,7 @@
|
||||
return sortedRelated;
|
||||
};
|
||||
const getSelectedTags = function () {
|
||||
const field = self.$refs['otherTags'];
|
||||
console.log(field.hasOwnProperty('selectionStart'));
|
||||
console.log(field.selectionStart);
|
||||
const field = self.$refs.otherTags;
|
||||
if (typeof field['selectionStart'] === 'undefined')
|
||||
return null;
|
||||
const length = field.selectionEnd - field.selectionStart;
|
||||
|
@ -1,12 +1,9 @@
|
||||
import Uploader from './uploader.vue';
|
||||
import Vue from 'vue';
|
||||
import Uploader from './uploader/uploader.vue';
|
||||
import { createApp } from 'vue';
|
||||
|
||||
export default {
|
||||
init() {
|
||||
const app = new Vue({
|
||||
render: (h) => h(Uploader)
|
||||
});
|
||||
|
||||
app.$mount('#uploader');
|
||||
const app = createApp(Uploader);
|
||||
app.mount('#uploader');
|
||||
}
|
||||
}
|
||||
|
168
app/javascript/src/javascripts/uploader/file_input.vue
Normal file
168
app/javascript/src/javascripts/uploader/file_input.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<span>
|
||||
<div v-if="!disableFileUpload">
|
||||
<div class="box-section sect_red" v-if="fileTooLarge">
|
||||
The file you are trying to upload is too large. Maximum allowed is {{this.maxFileSize / (1024*1024) }} MiB.<br>
|
||||
Check out <a href="/help/supported_filetypes">the Supported Formats</a> for more information.
|
||||
</div>
|
||||
<label>File:
|
||||
<input type="file" :name="fileInputName" ref="post_file" @change="updatePreviewFile"
|
||||
accept="image/png,image/apng,image/jpeg,image/gif,video/webm,.png,.apng,.jpg,.jpeg,.gif,.webm"
|
||||
:disabled="disableFileUpload"/>
|
||||
</label>
|
||||
<button @click.prevent="clearFileUpload" v-show="disableURLUpload">Clear</button>
|
||||
</div>
|
||||
<div v-if="!disableURLUpload">
|
||||
<div class="box-section sect_red" v-if="badDirectURL">
|
||||
The direct URL entered has the following problem: {{ directURLProblem }}<br>
|
||||
You should review <a href="/wiki_pages/howto:sites_and_sources">the sourcing guide</a>.
|
||||
</div>
|
||||
<label>{{!disableFileUpload ? "(or) " : "" }}URL:
|
||||
<input type="text" :name="urlInputName" size="50" v-model="uploadURL" :disabled="disableURLUpload"/>
|
||||
</label>
|
||||
<div id="whitelist-warning" v-show="whitelist.visible"
|
||||
:class="{'whitelist-warning-allowed': whitelist.allowed, 'whitelist-warning-disallowed': !whitelist.allowed}">
|
||||
<span v-if="whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are permitted.</span>
|
||||
<span v-if="!whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are not permitted.
|
||||
<span v-if="whitelist.reason">Reason given: {{whitelist.reason}}</span>
|
||||
(<a href="/upload_whitelists">View whitelisted domains</a>)</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
whitelist: {
|
||||
visible: false,
|
||||
allowed: false,
|
||||
reason: "",
|
||||
domain: "",
|
||||
oldDomain: "",
|
||||
},
|
||||
uploadURL: new URLSearchParams(window.location.search).get("upload_url") || "",
|
||||
fileTooLarge: false,
|
||||
maxFileSize: window.uploaderSettings.maxFileSize,
|
||||
disableFileUpload: false,
|
||||
disableURLUpload: false,
|
||||
}
|
||||
},
|
||||
props: ["fileInputName", "urlInputName"],
|
||||
computed: {
|
||||
directURLProblem: function () {
|
||||
return this.directURLCheck(this.uploadURL);
|
||||
},
|
||||
badDirectURL: function () {
|
||||
return !!this.directURLProblem;
|
||||
},
|
||||
invalidUploadValue: function() {
|
||||
return this.badDirectURL || this.fileTooLarge;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
uploadURL: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.fileTooLarge = false;
|
||||
this.uploadValueChanged(this.uploadURL);
|
||||
this.updatePreviewURL();
|
||||
}
|
||||
},
|
||||
invalidUploadValue() {
|
||||
this.$emit("invalidUploadValueChanged", this.invalidUploadValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
whitelistWarning(allowed, domain, reason) {
|
||||
this.whitelist.allowed = allowed;
|
||||
this.whitelist.domain = domain;
|
||||
this.whitelist.reason = reason;
|
||||
this.whitelist.visible = true;
|
||||
},
|
||||
clearWhitelistWarning() {
|
||||
this.whitelist.visible = false;
|
||||
this.whitelist.domain = "";
|
||||
},
|
||||
directURLCheck(url) {
|
||||
const patterns = [
|
||||
{ reason: "Thumbnail URL", test: /[at]\.(facdn|furaffinity)\.net/gi },
|
||||
{ reason: "Sample URL", test: /pximg\.net.*\/img-master\//gi },
|
||||
{ reason: "Sample URL", test: /d3gz42uwgl1r1y\.cloudfront\.net\/.*\/\d+x\d+\./gi },
|
||||
{ reason: "Sample URL", test: /pbs\.twimg\.com\/media\/[\w\-_]+\.(jpg|png)(:large)?$/gi },
|
||||
{ reason: "Sample URL", test: /pbs\.twimg\.com\/media\/[\w\-_]+\?format=(jpg|png)(?!&name=orig)/gi },
|
||||
{ reason: "Sample URL", test: /derpicdn\.net\/.*\/large\./gi },
|
||||
{ reason: "Sample URL", test: /metapix\.net\/files\/(preview|screen)\//gi },
|
||||
{ reason: "Sample URL", test: /sofurryfiles\.com\/std\/preview/gi }
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.test.test(url)) {
|
||||
return pattern.reason;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
clearFileUpload() {
|
||||
if (!this.$refs["post_file"]?.files?.[0]) {
|
||||
return;
|
||||
}
|
||||
this.$refs["post_file"].value = null;
|
||||
this.disableURLUpload = false;
|
||||
this.disableFileUpload = false;
|
||||
this.fileTooLarge = false;
|
||||
this.setEmptyThumb();
|
||||
this.uploadValueChanged("");
|
||||
|
||||
},
|
||||
updatePreviewURL() {
|
||||
if (this.uploadURL.length === 0 || this.$refs["post_file"]?.files?.[0]) {
|
||||
this.disableFileUpload = false;
|
||||
this.whitelist.oldDomain = "";
|
||||
this.clearWhitelistWarning();
|
||||
return;
|
||||
}
|
||||
this.disableFileUpload = true;
|
||||
const domain = $("<a>").prop("href", this.uploadURL).prop("hostname");
|
||||
|
||||
if (domain && domain !== this.whitelist.oldDomain) {
|
||||
$.getJSON("/upload_whitelists/is_allowed.json", {url: this.uploadURL}, data => {
|
||||
if (data.domain) {
|
||||
this.whitelistWarning(data.is_allowed, data.domain, data.reason);
|
||||
if (!data.is_allowed) {
|
||||
this.setEmptyThumb();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (!domain) {
|
||||
this.clearWhitelistWarning();
|
||||
this.setEmptyThumb();
|
||||
}
|
||||
this.whitelist.oldDomain = domain;
|
||||
if(/^(https?\:\/\/|www).*?$/.test(this.uploadURL)) {
|
||||
const isVideo = /^(https?\:\/\/|www).*?\.(webm)$/.test(this.uploadURL);
|
||||
this.previewChanged(this.uploadURL, isVideo);
|
||||
} else {
|
||||
this.setEmptyThumb();
|
||||
}
|
||||
},
|
||||
updatePreviewFile() {
|
||||
const file = this.$refs["post_file"].files[0];
|
||||
this.fileTooLarge = file.size > this.maxFileSize;
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
this.disableURLUpload = true;
|
||||
this.uploadValueChanged(file);
|
||||
this.previewChanged(objectUrl, file.type === "video/webm");
|
||||
},
|
||||
uploadValueChanged(value) {
|
||||
this.$emit("uploadValueChanged", value);
|
||||
},
|
||||
setEmptyThumb() {
|
||||
this.previewChanged("", false);
|
||||
},
|
||||
previewChanged(url, isVideo) {
|
||||
this.$emit("previewChanged", { url: url, isVideo: isVideo });
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
74
app/javascript/src/javascripts/uploader/file_preview.vue
Normal file
74
app/javascript/src/javascripts/uploader/file_preview.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="upload_preview_container" :class="classes">
|
||||
<div class="box-section sect_red" v-show="overDims">
|
||||
One of the image dimensions is above the maximum allowed of 15,000px and will fail to upload.
|
||||
</div>
|
||||
<div v-if="!failed">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<video v-if="data.isVideo" class="upload_preview_img" controls :src="finalPreviewUrl"
|
||||
v-on:loadeddata="updateDimensions($event)" v-on:error="previewFailed()">
|
||||
</video>
|
||||
<img v-else class="upload_preview_img" :src="finalPreviewUrl"
|
||||
referrerpolicy="no-referrer"
|
||||
v-on:load="updateDimensions($event)" v-on:error="previewFailed()"/>
|
||||
</div>
|
||||
<div v-else class="preview-fail box-section sect_yellow">
|
||||
<p>The preview for this file failed to load. Please, double check that the URL you provided is correct.</p>
|
||||
Note that some sites intentionally prevent images they host from being displayed on other sites. The file can still be uploaded despite that.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const thumbNone = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
|
||||
export default {
|
||||
props: {
|
||||
classes: String,
|
||||
data: {
|
||||
validator: function(obj) {
|
||||
return typeof obj.isVideo === "boolean" && typeof obj.url === "string";
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
heigth: 0,
|
||||
width: 0,
|
||||
overDims: false,
|
||||
failed: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
previewDimensions() {
|
||||
if (this.width > 1 && this.height > 1)
|
||||
return this.width + "×" + this.height;
|
||||
return "";
|
||||
},
|
||||
finalPreviewUrl() {
|
||||
return this.data.url === "" ? thumbNone : this.data.url;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
data: function() {
|
||||
this.resetFilePreview();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateDimensions(e) {
|
||||
const target = e.target;
|
||||
this.height = target.naturalHeight || target.videoHeight;
|
||||
this.width = target.naturalWidth || target.videoWidth;
|
||||
this.overDims = (this.height > 15000 || this.width > 15000);
|
||||
},
|
||||
resetFilePreview() {
|
||||
this.overDims = false;
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.failed = false;
|
||||
},
|
||||
previewFailed() {
|
||||
this.failed = true;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
@ -8,10 +8,10 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['value', 'index', 'last'],
|
||||
props: ['modelValue', 'index', 'last'],
|
||||
data() {
|
||||
return {
|
||||
backendValue: this.value
|
||||
backendValue: this.modelValue
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -21,7 +21,7 @@
|
||||
},
|
||||
set: function (v) {
|
||||
this.backendValue = v;
|
||||
this.$emit('input', v);
|
||||
this.$emit('update:modelValue', v);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -34,7 +34,7 @@
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
modelValue(v) {
|
||||
this.backendValue = v;
|
||||
}
|
||||
}
|
11
app/javascript/src/javascripts/uploader/tag_link.vue
Normal file
11
app/javascript/src/javascripts/uploader/tag_link.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<a :class="'tag-type-' + tagType" :href="'/wiki_pages/show_or_new?title=' + name" target="_blank">
|
||||
{{ name }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["tagType", "name"],
|
||||
}
|
||||
</script>
|
45
app/javascript/src/javascripts/uploader/tag_preview.vue
Normal file
45
app/javascript/src/javascripts/uploader/tag_preview.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-show="loading">Fetching tags...</div>
|
||||
<div class="related-tags flex-wrap">
|
||||
<div class="related-items" v-for="sTags, i in splitTags" :key="i">
|
||||
<tag-preview-tag v-for="tag, $idx in sTags" :key="$idx" :tag="tag"></tag-preview-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" @click.prevent="close">Close Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tagPreviewTag from './tag_preview_tag.vue';
|
||||
|
||||
export default {
|
||||
props: ['tags', 'loading'],
|
||||
components: {
|
||||
'tag-preview-tag': tagPreviewTag
|
||||
},
|
||||
methods: {
|
||||
close: function () {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
splitTags: function () {
|
||||
var newTags = this.tags.concat([]);
|
||||
newTags.sort(function (a, b) {
|
||||
return a.a === b.a ? 0 : (a.a < b.a ? -1 : 1);
|
||||
});
|
||||
var chunkArray = function (arr, size) {
|
||||
var chunks = [];
|
||||
for (var i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
return chunkArray(newTags, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
24
app/javascript/src/javascripts/uploader/tag_preview_tag.vue
Normal file
24
app/javascript/src/javascripts/uploader/tag_preview_tag.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<span v-if="tag.type === 'alias'" class="tag-preview tag-preview-alias">
|
||||
<del><tag-link :name="tag.a" :tagType="tag.tagTypeA"></tag-link></del>
|
||||
→ <tag-link :name="tag.b" :tagType="tag.tagTypeB"></tag-link>
|
||||
</span>
|
||||
<span v-else-if="tag.type === 'implication'" class="tag-preview tag-preview-implication">
|
||||
<tag-link :name="tag.a" :tagType="tag.tagTypeA"></tag-link>
|
||||
⇐ <tag-link :name="tag.b" :tagType="tag.tagTypeB"></tag-link>
|
||||
</span>
|
||||
<span v-else class="tag-preview">
|
||||
<tag-link :name="tag.a" :tagType="tag.tagTypeA"></tag-link>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tagLink from "./tag_link.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
"tag-link": tagLink,
|
||||
},
|
||||
props: ["tag"],
|
||||
}
|
||||
</script>
|
@ -1,47 +1,18 @@
|
||||
<template>
|
||||
<div class="flex-grid-outer">
|
||||
<div class="col box-section" style="flex: 2 0 0;">
|
||||
<div class="box-section sect_red" v-show="filePreview.overDims">
|
||||
One of the image dimensions is above the maximum allowed of 15,000px and will fail to upload.
|
||||
</div>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_file">File</label>
|
||||
<div class="hint"><a href="/help/supported_filetypes">Supported Formats</a></div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<div v-if="!disableFileUpload">
|
||||
<div class="box-section sect_red" v-if="fileTooLarge">
|
||||
The file you are trying to upload is too large. Maximum allowed is {{this.maxFileSize / (1024*1024) }} MiB.<br>
|
||||
Check out <a href="/help/supported_filetypes">the Supported Formats</a> for more information.
|
||||
</div>
|
||||
<label>File:
|
||||
<input type="file" ref="post_file" @change="updateFilePreview"
|
||||
accept="image/png,image/apng,image/jpeg,image/gif,video/webm,.png,.apng,.jpg,.jpeg,.gif,.webm"
|
||||
:disabled="disableFileUpload"/>
|
||||
</label>
|
||||
<button @click="clearFile" v-show="disableURLUpload">Clear</button>
|
||||
</div>
|
||||
<div v-if="!disableURLUpload">
|
||||
<div class="box-section sect_red" v-if="badDirectURL">
|
||||
The direct URL entered has the following problem: {{ directURLProblem }}<br>
|
||||
You should review <a href="/wiki_pages/howto:sites_and_sources">the sourcing guide</a>.
|
||||
</div>
|
||||
<label>{{!disableFileUpload ? '(or) ' : '' }}URL:
|
||||
<input type="text" size="50" v-model="uploadURL"
|
||||
:disabled="disableURLUpload"/>
|
||||
</label>
|
||||
<div id="whitelist-warning" v-show="whitelist.visible"
|
||||
:class="{'whitelist-warning-allowed': whitelist.allowed, 'whitelist-warning-disallowed': !whitelist.allowed}">
|
||||
<span v-if="whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are permitted.</span>
|
||||
<span v-if="!whitelist.allowed">Uploads from <b>{{whitelist.domain}}</b> are not permitted.
|
||||
<span v-if="whitelist.reason">Reason given: {{whitelist.reason}}</span>
|
||||
(<a href='/upload_whitelists'>View whitelisted domains</a>)</span>
|
||||
</div>
|
||||
</div>
|
||||
<file-input @uploadValueChanged="uploadValue = $event"
|
||||
@previewChanged="previewData = $event"
|
||||
@invalidUploadValueChanged="invalidUploadValue = $event"></file-input>
|
||||
</div>
|
||||
</div>
|
||||
<file-preview classes="box-section in-editor below-upload" :preview="filePreview"></file-preview>
|
||||
<file-preview classes="box-section in-editor below-upload" :data="previewData"></file-preview>
|
||||
<div class="flex-grid border-bottom">
|
||||
<div class="col">
|
||||
<label class="section-label" for="post_sources">Sources</label>
|
||||
@ -184,7 +155,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col2">
|
||||
<file-preview classes="box-section in-editor" :preview="filePreview"></file-preview>
|
||||
<file-preview classes="box-section in-editor" :data="previewData"></file-preview>
|
||||
<div class="box-section sect_red" v-show="showErrors && notEnoughTags">
|
||||
You must provide at least <b>{{4 - tagCount}}</b> more tags. Tags in other sections count
|
||||
towards this total.
|
||||
@ -278,25 +249,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="preview-sidebar" class="col box-section" style="margin-left: 10px; padding: 10px;">
|
||||
<file-preview classes="in-sidebar" :preview="filePreview" @load="updateFilePreviewDims" @error="filePreviewError"></file-preview>
|
||||
<file-preview classes="in-sidebar" :data="previewData"></file-preview>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import source from './uploader_source.vue';
|
||||
import checkbox from './uploader_checkbox.vue';
|
||||
import relatedTags from './uploader_related.vue';
|
||||
import tagPreview from './uploader_tag_preview.vue';
|
||||
import filePreview from './uploader_file_preview.vue';
|
||||
|
||||
const thumbURLs = [
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
];
|
||||
const thumbs = {
|
||||
none: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
|
||||
};
|
||||
import source from './source.vue';
|
||||
import checkbox from './checkbox.vue';
|
||||
import relatedTags from './related.vue';
|
||||
import tagPreview from './tag_preview.vue';
|
||||
import filePreview from './file_preview.vue';
|
||||
import fileInput from './file_input.vue';
|
||||
|
||||
const sex_checks = [
|
||||
{name: 'Male'},
|
||||
@ -330,133 +294,15 @@
|
||||
{name: 'Human'},
|
||||
{name: 'Taur'}];
|
||||
|
||||
function updateFilePreviewDims(e) {
|
||||
const target = e.target;
|
||||
if (thumbURLs.filter(function (x) {
|
||||
return target.src.indexOf(x) !== -1;
|
||||
}).length !== 0)
|
||||
return;
|
||||
this.filePreview.height = target.naturalHeight || target.videoHeight;
|
||||
this.filePreview.width = target.naturalWidth || target.videoWidth;
|
||||
this.filePreview.overDims = (this.filePreview.height > 15000 || this.filePreview.width > 15000);
|
||||
}
|
||||
|
||||
function filePreviewError() {
|
||||
this.filePreview.failed = true;
|
||||
}
|
||||
|
||||
function updatePreviewFile() {
|
||||
const file = this.$refs['post_file'].files[0];
|
||||
this.fileTooLarge = file.size > this.maxFileSize;
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
if (file.type.match('video/webm'))
|
||||
this.setPreviewVideo(objectUrl);
|
||||
else
|
||||
this.setPreviewImage(objectUrl);
|
||||
|
||||
this.disableURLUpload = true;
|
||||
}
|
||||
|
||||
function updatePreviewURL() {
|
||||
const self = this;
|
||||
if (this.uploadURL.length === 0 || (this.$refs['post_file'] && this.$refs['post_file'].files.length > 0)) {
|
||||
this.disableFileUpload = false;
|
||||
this.oldDomain = '';
|
||||
self.clearWhitelistWarning();
|
||||
return;
|
||||
}
|
||||
this.disableFileUpload = true;
|
||||
const domain = $("<a>").prop("href", this.uploadURL).prop("hostname");
|
||||
|
||||
if (domain && domain !== this.oldDomain) {
|
||||
$.getJSON("/upload_whitelists/is_allowed.json", {url: this.uploadURL}, function (data) {
|
||||
if (data.domain) {
|
||||
self.whitelistWarning(data.is_allowed, data.domain, data.reason);
|
||||
if (!data.is_allowed)
|
||||
self.setPreviewImage(thumbs.none);
|
||||
}
|
||||
});
|
||||
} else if (!domain) {
|
||||
self.clearWhitelistWarning();
|
||||
}
|
||||
this.oldDomain = domain;
|
||||
|
||||
if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(webm)$/))
|
||||
this.setPreviewVideo(this.uploadURL);
|
||||
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?$/))
|
||||
this.setPreviewImage(this.uploadURL);
|
||||
else
|
||||
this.setPreviewImage(thumbs.none);
|
||||
}
|
||||
|
||||
function updateFilePreview() {
|
||||
this.resetFilePreview();
|
||||
if (this.$refs['post_file'] && this.$refs['post_file'].files[0])
|
||||
updatePreviewFile.call(this);
|
||||
else
|
||||
updatePreviewURL.call(this);
|
||||
}
|
||||
|
||||
function setPreviewImage(url) {
|
||||
this.filePreview.isVideo = false;
|
||||
this.filePreview.url = url;
|
||||
}
|
||||
|
||||
function setPreviewVideo(url) {
|
||||
this.filePreview.isVideo = true;
|
||||
this.filePreview.url = url;
|
||||
}
|
||||
|
||||
function resetFilePreview() {
|
||||
// This might not be an objectURL, but revoking in those cases doesn't hurt
|
||||
URL.revokeObjectURL(this.filePreview.url);
|
||||
this.filePreview.isVideo = false;
|
||||
this.filePreview.url = thumbs.none;
|
||||
this.filePreview.overDims = false;
|
||||
this.filePreview.width = 0;
|
||||
this.filePreview.height = 0;
|
||||
this.filePreview.failed = false;
|
||||
this.fileTooLarge = false;
|
||||
}
|
||||
|
||||
function directURLCheck(url) {
|
||||
var patterns = [
|
||||
{reason: 'Thumbnail URL', test: /[at]\.facdn\.net/gi},
|
||||
{reason: 'Sample URL', test: /pximg\.net.*\/img-master\//gi},
|
||||
{reason: 'Sample URL', test: /d3gz42uwgl1r1y\.cloudfront\.net\/.*\/\d+x\d+\./gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/[\w\-_]+\.(jpg|png)(:large)?$/gi},
|
||||
{reason: 'Sample URL', test: /pbs\.twimg\.com\/media\/[\w\-_]+\?format=(jpg|png)(?!&name=orig)/gi},
|
||||
{reason: 'Sample URL', test: /derpicdn\.net\/.*\/large\./gi},
|
||||
{reason: 'Sample URL', test: /metapix\.net\/files\/(preview|screen)\//gi},
|
||||
{reason: 'Sample URL', test: /sofurryfiles\.com\/std\/preview/gi}];
|
||||
for (var i = 0; i < patterns.length; ++i) {
|
||||
var pattern = patterns[i];
|
||||
if (pattern.test.test(url))
|
||||
return pattern.reason;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function clearFileUpload() {
|
||||
if (!(this.$refs['post_file'] && this.$refs['post_file'].files[0]))
|
||||
return;
|
||||
this.$refs['post_file'].value = null;
|
||||
this.disableURLUpload = this.disableFileUpload = false;
|
||||
this.resetFilePreview();
|
||||
this.updateFilePreview();
|
||||
}
|
||||
|
||||
function tagSorter(a, b) {
|
||||
return a[0] > b[0] ? 1 : -1;
|
||||
}
|
||||
|
||||
function unloadWarning() {
|
||||
if (this.allowNavigate)
|
||||
if (this.allowNavigate || this.uploadValue === "") {
|
||||
return;
|
||||
const post_file = this.$refs['post_file'];
|
||||
if ((post_file && post_file.files && post_file.files.length) || this.uploadURL) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default {
|
||||
@ -465,7 +311,8 @@
|
||||
'image-checkbox': checkbox,
|
||||
'related-tags': relatedTags,
|
||||
'tag-preview': tagPreview,
|
||||
'file-preview': filePreview
|
||||
'file-preview': filePreview,
|
||||
'file-input': fileInput,
|
||||
},
|
||||
data() {
|
||||
const allChecks = {};
|
||||
@ -485,27 +332,15 @@
|
||||
return {
|
||||
safe: window.uploaderSettings.safeSite,
|
||||
showErrors: false,
|
||||
whitelist: {
|
||||
visible: false,
|
||||
allowed: false,
|
||||
domain: ''
|
||||
},
|
||||
allowNavigate: false,
|
||||
submitting: false,
|
||||
disableFileUpload: false,
|
||||
disableURLUpload: false,
|
||||
|
||||
filePreview: {
|
||||
heigth: 0,
|
||||
width: 0,
|
||||
overDims: false,
|
||||
url: thumbs.none,
|
||||
previewData: {
|
||||
url: '',
|
||||
isVideo: false,
|
||||
failed: false,
|
||||
},
|
||||
|
||||
uploadURL: '',
|
||||
oldDomain: '',
|
||||
uploadValue: '',
|
||||
invalidUploadValue: false,
|
||||
|
||||
noSource: false,
|
||||
sources: [''],
|
||||
@ -550,9 +385,6 @@
|
||||
duplicateId: 0,
|
||||
|
||||
descrLimit: window.uploaderSettings.descrLimit,
|
||||
|
||||
maxFileSize: window.uploaderSettings.maxFileSize,
|
||||
fileTooLarge: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -580,9 +412,6 @@
|
||||
self.pushTag(trimTag, true);
|
||||
}
|
||||
};
|
||||
fillField('uploadURL', 'upload_url');
|
||||
if(params.has('upload_url'))
|
||||
this.updateFilePreview();
|
||||
fillField('parentID', 'parent');
|
||||
fillField('description', 'description');
|
||||
fillTags();
|
||||
@ -596,29 +425,7 @@
|
||||
if(this.allowUploadAsPending)
|
||||
fillFieldBool("uploadAsPending", "upload_as_pending")
|
||||
},
|
||||
watch: {
|
||||
uploadURL: function() {
|
||||
this.updateFilePreview();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateFilePreview,
|
||||
updateFilePreviewDims,
|
||||
setPreviewImage,
|
||||
setPreviewVideo,
|
||||
resetFilePreview,
|
||||
filePreviewError,
|
||||
clearFile: clearFileUpload,
|
||||
whitelistWarning(allowed, domain, reason) {
|
||||
this.whitelist.allowed = allowed;
|
||||
this.whitelist.domain = domain;
|
||||
this.whitelist.reason = reason;
|
||||
this.whitelist.visible = true;
|
||||
},
|
||||
clearWhitelistWarning() {
|
||||
this.whitelist.visible = false;
|
||||
this.whitelist.domain = '';
|
||||
},
|
||||
removeSource(i) {
|
||||
this.sources.splice(i, 1);
|
||||
},
|
||||
@ -627,7 +434,7 @@
|
||||
this.sources.push('');
|
||||
},
|
||||
setCheck(tag, value) {
|
||||
Vue.set(this.checkboxes.selected, tag, value);
|
||||
this.checkboxes.selected[tag] = value;
|
||||
},
|
||||
submit() {
|
||||
this.showErrors = true;
|
||||
@ -637,11 +444,10 @@
|
||||
const self = this;
|
||||
this.submitting = true;
|
||||
const data = new FormData();
|
||||
const post_file = this.$refs['post_file'];
|
||||
if (post_file && post_file.files && post_file.files.length) {
|
||||
data.append('upload[file]', this.$refs['post_file'].files[0]);
|
||||
if (typeof this.uploadValue === "string") {
|
||||
data.append('upload[direct_url]', this.uploadValue);
|
||||
} else {
|
||||
data.append('upload[direct_url]', this.uploadURL);
|
||||
data.append('upload[file]', this.uploadValue);
|
||||
}
|
||||
data.append('upload[tag_string]', this.tags);
|
||||
data.append('upload[rating]', this.rating);
|
||||
@ -792,12 +598,6 @@
|
||||
tagsArray() {
|
||||
return this.tags.toLowerCase().split(' ');
|
||||
},
|
||||
directURLProblem: function () {
|
||||
return directURLCheck(this.uploadURL);
|
||||
},
|
||||
badDirectURL: function () {
|
||||
return !!this.directURLProblem;
|
||||
},
|
||||
sourceWarning: function () {
|
||||
const validSourceCount = this.sources.filter(function (i) {
|
||||
return i.length > 0;
|
||||
@ -816,8 +616,8 @@
|
||||
return !this.rating;
|
||||
},
|
||||
preventUpload: function () {
|
||||
return this.sourceWarning || this.badDirectURL || this.notEnoughTags
|
||||
|| this.invalidRating || this.fileTooLarge;
|
||||
return this.sourceWarning || this.notEnoughTags
|
||||
|| this.invalidRating || this.invalidUploadValue;
|
||||
},
|
||||
duplicatePath: function () {
|
||||
return `/posts/${this.duplicateId}`;
|
@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<div class="upload_preview_container" :class="classes">
|
||||
<div v-if="!preview.failed">
|
||||
<div class="upload_preview_dims">{{ previewDimensions }}</div>
|
||||
<img class="upload_preview_img" :src="preview.url"
|
||||
referrerpolicy="no-referrer"
|
||||
v-if="!preview.isVideo"
|
||||
v-on:load="$emit('load', $event)" v-on:error="$emit('error')"/>
|
||||
<video class="upload_preview_img" controls :src="preview.url"
|
||||
v-on:loadeddata="$emit('load', $event)" v-on:error="$emit('error')"
|
||||
v-if="preview.isVideo"></video>
|
||||
</div>
|
||||
<div class="preview-fail box-section sect_yellow" v-if="preview.failed">
|
||||
<p>The preview for this file failed to load. Please, double check that the URL you provided is correct.</p>
|
||||
Note that some sites intentionally prevent images they host from being displayed on other sites. The file can still be uploaded despite that.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
classes: String,
|
||||
preview: Object
|
||||
},
|
||||
computed: {
|
||||
previewDimensions() {
|
||||
if (this.preview.width && this.preview.height)
|
||||
return this.preview.width + '×' + this.preview.height;
|
||||
return '';
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-show="loading">Fetching tags...</div>
|
||||
<div class="related-tags flex-wrap">
|
||||
<div class="related-items" v-for="sTags, i in splitTags" :key="i">
|
||||
<tag-preview v-for="tag, $idx in sTags" :key="$idx" :tag="tag"></tag-preview>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" @click.prevent="close">Close Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
const tagPreviewTag = Vue.extend({
|
||||
functional: true,
|
||||
props: ['tag'],
|
||||
render: function (h, ctx) {
|
||||
function create_tag_link(name, tagType) {
|
||||
return h('a', {
|
||||
staticClass: 'tag-type-' + tagType,
|
||||
attrs: { href: "/wiki_pages/show_or_new?title=" + name, target: "_blank" }
|
||||
}, name);
|
||||
}
|
||||
var tag = ctx.props.tag;
|
||||
switch (tag.type) {
|
||||
default:
|
||||
case 'tag':
|
||||
return h('span', {staticClass: 'tag-preview'}, [create_tag_link(tag.a, tag.tagTypeA)]);
|
||||
case 'alias':
|
||||
return h('span', {staticClass: 'tag-preview tag-preview-alias'}, [
|
||||
h('del', undefined, [
|
||||
create_tag_link(tag.a, tag.tagTypeA)
|
||||
]), ' → ', create_tag_link(tag.b, tag.tagTypeB)
|
||||
]);
|
||||
case 'implication':
|
||||
return h('span', {staticClass: 'tag-preview tag-preview-implication'}, [
|
||||
create_tag_link(tag.a, tag.tagTypeA), ' ⇐ ', create_tag_link(tag.b, tag.tagTypeB)
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
props: ['tags', 'loading'],
|
||||
components: {
|
||||
'tag-preview': tagPreviewTag
|
||||
},
|
||||
methods: {
|
||||
close: function () {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
splitTags: function () {
|
||||
var newTags = this.tags.concat([]);
|
||||
newTags.sort(function (a, b) {
|
||||
return a.a === b.a ? 0 : (a.a < b.a ? -1 : 1);
|
||||
});
|
||||
var chunkArray = function (arr, size) {
|
||||
var chunks = [];
|
||||
for (var i = 0; i < arr.length; i += size) {
|
||||
chunks.push(arr.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
return chunkArray(newTags, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -65,6 +65,7 @@
|
||||
@import "specific/sessions.scss";
|
||||
@import "specific/site_map.scss";
|
||||
@import "specific/staff_notes.scss";
|
||||
@import "specific/stats.scss";
|
||||
@import "specific/tags.scss";
|
||||
@import "specific/takedowns.scss";
|
||||
@import "specific/terms_of_service.scss";
|
||||
|
@ -23,4 +23,8 @@ div#c-post-replacements {
|
||||
color: themed("color-text-muted");
|
||||
}
|
||||
}
|
||||
|
||||
.upload_preview_img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,9 @@ div.related-tags {
|
||||
}
|
||||
|
||||
.tag-active {
|
||||
background: rgb(0, 111, 250);
|
||||
@include themable {
|
||||
background: themed("color-active-tag");
|
||||
}
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
65
app/javascript/src/styles/specific/stats.scss
Normal file
65
app/javascript/src/styles/specific/stats.scss
Normal file
@ -0,0 +1,65 @@
|
||||
.stats-column {
|
||||
width:400px;
|
||||
display:inline-block;
|
||||
vertical-align:top;
|
||||
}
|
||||
|
||||
.stats-chart {
|
||||
display:inline-block;
|
||||
width:300px;
|
||||
height:300px;
|
||||
}
|
||||
|
||||
.stats-chart-container {
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
.stats-pct {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
#stats-column-1 table {
|
||||
margin-bottom:60px;
|
||||
}
|
||||
|
||||
#stats-column-2 table {
|
||||
margin-bottom:19px;
|
||||
}
|
||||
|
||||
#stats-column-3 table {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.stats-column table:last-child {
|
||||
margin-bottom:10px !important;
|
||||
}
|
||||
|
||||
.stats-column table {
|
||||
margin-bottom:2em;
|
||||
padding:3px;
|
||||
border-spacing:0;
|
||||
}
|
||||
|
||||
.stats-rounded {
|
||||
@include themable {
|
||||
background-color: themed("color-section");
|
||||
box-shadow:2px 2px 5px darken( themed("color-section"), 15%);
|
||||
}
|
||||
border-radius:8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stats-rounded tr:nth-child(even) {
|
||||
@include themable {
|
||||
background-color: lighten( themed("color-section"), 3%);
|
||||
}
|
||||
}
|
||||
.stats-rounded tr:nth-child(even) td {
|
||||
@include themable {
|
||||
border-top:1px solid lighten( themed("color-section"), 10%);
|
||||
border-bottom:1px solid lighten( themed("color-section"), 10%);
|
||||
}
|
||||
}
|
||||
.stats-rounded tr:last-child td{ border-bottom:none !important; }
|
||||
.stats-rounded td { padding:2px 10px; vertical-align:top; }
|
||||
.stats-rounded th { font-weight:bold; text-align:left; vertical-align:top; padding:0.2em 0.5em; white-space:nowrap; }
|
@ -33,6 +33,8 @@ $theme_bloodlust: (
|
||||
"color-score-positive": #3e9e49,
|
||||
"color-score-negative": #e45f5f,
|
||||
|
||||
"color-active-tag": #222222,
|
||||
|
||||
// Detail Colors
|
||||
"color-detail-quote": #67717b,
|
||||
"color-detail-code": #ffe380,
|
||||
|
@ -41,6 +41,8 @@ $theme_hexagon: (
|
||||
"color-score-positive": #3e9e49,
|
||||
"color-score-negative": #e45f5f,
|
||||
|
||||
"color-active-tag": #006ffa,
|
||||
|
||||
// Detail Colors
|
||||
"color-detail-quote": #b4c7d9,
|
||||
"color-detail-code": #ffe380,
|
||||
|
@ -21,6 +21,8 @@ $theme_hotdog: (
|
||||
"color-score-positive": #3e9e49,
|
||||
"color-score-negative": #e45f5f,
|
||||
|
||||
"color-active-tag": #ff0000,
|
||||
|
||||
// Detail Colors
|
||||
"color-detail-quote": #67717b,
|
||||
"color-detail-code": #ffe380,
|
||||
|
@ -22,6 +22,8 @@ $theme_pony: (
|
||||
"color-score-positive": #3e9e49,
|
||||
"color-score-negative": #e45f5f,
|
||||
|
||||
"color-active-tag": #6f36da,
|
||||
|
||||
// Detail Colors
|
||||
"color-detail-quote": #67717b,
|
||||
"color-detail-code": #ffe380,
|
||||
|
@ -22,6 +22,8 @@ $theme_serpent: (
|
||||
"color-score-positive": #3e9e49,
|
||||
"color-score-negative": #e45f5f,
|
||||
|
||||
"color-active-tag": #44a544,
|
||||
|
||||
// Detail Colors
|
||||
"color-detail-quote": #67717b,
|
||||
"color-detail-code": #ffe380,
|
||||
|
@ -3,106 +3,36 @@ module DanbooruImageResizer
|
||||
|
||||
# Taken from ArgyllCMS 2.0.0 (see also: https://ninedegreesbelow.com/photography/srgb-profile-comparison.html)
|
||||
SRGB_PROFILE = "#{Rails.root}/config/sRGB.icm"
|
||||
# http://jcupitt.github.io/libvips/API/current/libvips-resample.html#vips-thumbnail
|
||||
# https://www.libvips.org/API/current/libvips-resample.html#vips-thumbnail
|
||||
THUMBNAIL_OPTIONS = { size: :down, linear: false, no_rotate: true, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
|
||||
THUMBNAIL_OPTIONS_NO_ICC = { size: :down, linear: false, no_rotate: true, export_profile: SRGB_PROFILE }
|
||||
# http://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave
|
||||
# https://www.libvips.org/API/current/VipsForeignSave.html#vips-jpegsave
|
||||
JPEG_OPTIONS = { background: 0, strip: true, interlace: true, optimize_coding: true }
|
||||
CROP_OPTIONS = { linear: false, no_rotate: true, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE, crop: :attention }
|
||||
CROP_OPTIONS_NO_ICC = { linear: false, no_rotate: true, export_profile: SRGB_PROFILE, crop: :attention }
|
||||
|
||||
# XXX libvips-8.4 on Debian doesn't support the `Vips::Image.thumbnail` method.
|
||||
# On 8.4 we have to shell out to vipsthumbnail instead. Remove when Debian supports 8.5.
|
||||
def resize(file, width, height, quality = 90)
|
||||
if Vips.at_least_libvips?(8, 5)
|
||||
resize_ruby(file, width, height, quality)
|
||||
else
|
||||
resize_shell(file, width, height, quality)
|
||||
end
|
||||
end
|
||||
|
||||
def crop(file, width, height, quality = 90)
|
||||
if Vips.at_least_libvips?(8, 5)
|
||||
crop_ruby(file, width, height, quality)
|
||||
else
|
||||
crop_shell(file, width, height, quality)
|
||||
end
|
||||
end
|
||||
|
||||
# https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking
|
||||
# http://jcupitt.github.io/libvips/API/current/Using-vipsthumbnail.md.html
|
||||
def resize_ruby(file, width, height, resize_quality)
|
||||
def resize(file, width, height, resize_quality = 90)
|
||||
output_file = Tempfile.new
|
||||
begin
|
||||
resized_image = Vips::Image.thumbnail(file.path, width, height: height, **THUMBNAIL_OPTIONS)
|
||||
rescue Vips::Error => e
|
||||
raise e unless e.message =~ /icc_transform/i
|
||||
resized_image = Vips::Image.thumbnail(file.path, width, height: height, **THUMBNAIL_OPTIONS_NO_ICC)
|
||||
end
|
||||
resized_image = thumbnail(file, width, height, THUMBNAIL_OPTIONS)
|
||||
resized_image.jpegsave(output_file.path, Q: resize_quality, **JPEG_OPTIONS)
|
||||
|
||||
output_file
|
||||
end
|
||||
|
||||
def crop_ruby(file, width, height, resize_quality)
|
||||
def crop(file, width, height, resize_quality = 90)
|
||||
return nil unless Danbooru.config.enable_image_cropping?
|
||||
|
||||
output_file = Tempfile.new
|
||||
begin
|
||||
resized_image = Vips::Image.thumbnail(file.path, width, height: height, **CROP_OPTIONS)
|
||||
rescue Vips::Error => e
|
||||
raise e unless e.message =~ /icc_transform/i
|
||||
resized_image = Vips::Image.thumbnail(file.path, width, height: height, **CROP_OPTIONS_NO_ICC)
|
||||
end
|
||||
resized_image = thumbnail(file, width, height, CROP_OPTIONS)
|
||||
resized_image.jpegsave(output_file.path, Q: resize_quality, **JPEG_OPTIONS)
|
||||
|
||||
output_file
|
||||
end
|
||||
|
||||
def resize_shell(file, width, height, quality)
|
||||
output_file = Tempfile.new(["resize", ".jpg"])
|
||||
|
||||
# --size=WxH will upscale if the image is smaller than the target size.
|
||||
# Fix the target size so that it's not bigger than the image.
|
||||
image = Vips::Image.new_from_file(file.path)
|
||||
target_width = [image.width, width].min
|
||||
target_height = [image.height, height].min
|
||||
|
||||
arguments = [
|
||||
file.path,
|
||||
"--eprofile=#{SRGB_PROFILE}",
|
||||
"--iprofile=#{SRGB_PROFILE}",
|
||||
"--size=#{target_width}x#{target_height}",
|
||||
"--format=#{output_file.path}[Q=#{quality},background=0,strip,interlace,optimize_coding]"
|
||||
]
|
||||
|
||||
_, status = Open3.capture2e("vipsthumbnail", *arguments)
|
||||
raise RuntimeError, "vipsthumbnail failed (exit status: #{status})" if status != 0
|
||||
|
||||
output_file
|
||||
end
|
||||
|
||||
def crop_shell(file, width, height, quality)
|
||||
return nil unless Danbooru.config.enable_image_cropping?
|
||||
|
||||
output_file = Tempfile.new(["crop", ".jpg"])
|
||||
|
||||
# --size=WxH will upscale if the image is smaller than the target size.
|
||||
# Fix the target size so that it's not bigger than the image.
|
||||
# image = Vips::Image.new_from_file(file.path)
|
||||
|
||||
arguments = [
|
||||
file.path,
|
||||
"--eprofile=#{SRGB_PROFILE}",
|
||||
"--iprofile=#{SRGB_PROFILE}",
|
||||
"--smartcrop=attention",
|
||||
"--size=#{width}x#{height}",
|
||||
"--format=#{output_file.path}[Q=#{quality},background=0,strip,interlace,optimize_coding]"
|
||||
]
|
||||
|
||||
_, status = Open3.capture2e("vipsthumbnail", *arguments)
|
||||
raise RuntimeError, "vipsthumbnail failed (exit status: #{status})" if status != 0
|
||||
|
||||
output_file
|
||||
# https://github.com/libvips/libvips/wiki/HOWTO----Image-shrinking
|
||||
# https://www.libvips.org/API/current/Using-vipsthumbnail.md.html
|
||||
def thumbnail(file, width, height, options)
|
||||
Vips::Image.thumbnail(file.path, width, height: height, **options)
|
||||
rescue Vips::Error => e
|
||||
raise e unless e.message =~ /icc_transform/i
|
||||
Vips::Image.thumbnail(file.path, width, height: height, **options.except(:import_profile))
|
||||
end
|
||||
end
|
||||
|
@ -1,14 +1,14 @@
|
||||
module DangerZone
|
||||
def self.uploads_disabled?
|
||||
redis_client.get("disable_uploads") == "y"
|
||||
def self.uploads_disabled?(user)
|
||||
user.level < min_upload_level
|
||||
end
|
||||
|
||||
def self.disable_uploads
|
||||
redis_client.set("disable_uploads", "y")
|
||||
def self.min_upload_level
|
||||
(redis_client.get("min_upload_level") || User::Levels::MEMBER).to_i
|
||||
end
|
||||
|
||||
def self.enable_uploads
|
||||
redis_client.set("disable_uploads", "n")
|
||||
def self.min_upload_level=(min_upload_level)
|
||||
redis_client.set("min_upload_level", min_upload_level)
|
||||
end
|
||||
|
||||
def self.redis_client
|
||||
|
@ -69,8 +69,8 @@ module FileMethods
|
||||
[video.width, video.height]
|
||||
|
||||
elsif is_image?
|
||||
image_size = ImageSpec.new(file_path)
|
||||
[image_size.width, image_size.height]
|
||||
image = Vips::Image.new_from_file(file_path)
|
||||
[image.width, image.height]
|
||||
|
||||
else
|
||||
[0, 0]
|
||||
|
@ -1,11 +1,4 @@
|
||||
class TagAliasRequest
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :antecedent_name, :consequent_name, :reason, :tag_alias, :forum_topic, :skip_forum
|
||||
|
||||
validate :validate_tag_alias
|
||||
validate :validate_forum_topic
|
||||
|
||||
class TagAliasRequest < TagRelationshipRequest
|
||||
def self.topic_title(antecedent_name, consequent_name)
|
||||
"Tag alias: #{antecedent_name} -> #{consequent_name}"
|
||||
end
|
||||
@ -18,69 +11,7 @@ class TagAliasRequest
|
||||
"create alias [[#{antecedent_name}]] -> [[#{consequent_name}]]"
|
||||
end
|
||||
|
||||
def initialize(attributes)
|
||||
@antecedent_name = attributes[:antecedent_name].strip.tr(" ", "_")
|
||||
@consequent_name = attributes[:consequent_name].strip.tr(" ", "_")
|
||||
@reason = attributes[:reason]
|
||||
self.skip_forum = attributes[:skip_forum]
|
||||
end
|
||||
|
||||
def create
|
||||
return false if invalid?
|
||||
|
||||
TagAlias.transaction do
|
||||
@tag_alias = build_tag_alias
|
||||
@tag_alias.save
|
||||
|
||||
unless skip_forum
|
||||
@forum_topic = build_forum_topic(@tag_alias.id)
|
||||
@forum_topic.save
|
||||
|
||||
@tag_alias.forum_topic_id = @forum_topic.id
|
||||
@tag_alias.forum_post_id = @forum_topic.posts.first.id
|
||||
@tag_alias.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_tag_alias
|
||||
x = TagAlias.new(
|
||||
:antecedent_name => antecedent_name,
|
||||
:consequent_name => consequent_name
|
||||
)
|
||||
x.status = "pending"
|
||||
x
|
||||
end
|
||||
|
||||
def build_forum_topic(tag_alias_id)
|
||||
ForumTopic.new(
|
||||
:title => TagAliasRequest.topic_title(antecedent_name, consequent_name),
|
||||
:original_post_attributes => {
|
||||
:body => TagAliasRequest.command_string(antecedent_name, consequent_name, tag_alias_id) + "\n\nReason: #{reason}"
|
||||
},
|
||||
:category_id => Danbooru.config.alias_implication_forum_category
|
||||
)
|
||||
end
|
||||
|
||||
def validate_tag_alias
|
||||
ta = @tag_alias || build_tag_alias
|
||||
|
||||
if ta.invalid?
|
||||
self.errors.add(:base, ta.errors.full_messages.join("; "))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def validate_forum_topic
|
||||
return if skip_forum
|
||||
ft = @forum_topic || build_forum_topic(nil)
|
||||
if ft.invalid?
|
||||
self.errors.add(:base, ft.errors.full_messages.join("; "))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def skip_forum=(v)
|
||||
@skip_forum = v.to_s.truthy?
|
||||
def tag_relationship_class
|
||||
TagAlias
|
||||
end
|
||||
end
|
||||
|
@ -1,11 +1,4 @@
|
||||
class TagImplicationRequest
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :antecedent_name, :consequent_name, :reason, :tag_implication, :forum_topic, :skip_forum
|
||||
|
||||
validate :validate_tag_implication
|
||||
validate :validate_forum_topic
|
||||
|
||||
class TagImplicationRequest < TagRelationshipRequest
|
||||
def self.topic_title(antecedent_name, consequent_name)
|
||||
"Tag implication: #{antecedent_name} -> #{consequent_name}"
|
||||
end
|
||||
@ -18,70 +11,7 @@ class TagImplicationRequest
|
||||
"create implication [[#{antecedent_name}]] -> [[#{consequent_name}]]"
|
||||
end
|
||||
|
||||
def initialize(attributes)
|
||||
@antecedent_name = attributes[:antecedent_name].strip.tr(" ", "_")
|
||||
@consequent_name = attributes[:consequent_name].strip.tr(" ", "_")
|
||||
@reason = attributes[:reason]
|
||||
self.skip_forum = attributes[:skip_forum]
|
||||
end
|
||||
|
||||
def create
|
||||
return false if invalid?
|
||||
|
||||
TagImplication.transaction do
|
||||
@tag_implication = build_tag_implication
|
||||
@tag_implication.save
|
||||
|
||||
|
||||
unless skip_forum
|
||||
@forum_topic = build_forum_topic(@tag_implication.id)
|
||||
@forum_topic.save
|
||||
|
||||
@tag_implication.forum_topic_id = @forum_topic.id
|
||||
@tag_implication.forum_post_id = @forum_topic.posts.first.id
|
||||
@tag_implication.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_tag_implication
|
||||
x = TagImplication.new(
|
||||
:antecedent_name => antecedent_name,
|
||||
:consequent_name => consequent_name
|
||||
)
|
||||
x.status = "pending"
|
||||
x
|
||||
end
|
||||
|
||||
def build_forum_topic(tag_implication_id)
|
||||
ForumTopic.new(
|
||||
:title => TagImplicationRequest.topic_title(antecedent_name, consequent_name),
|
||||
:original_post_attributes => {
|
||||
:body => TagImplicationRequest.command_string(antecedent_name, consequent_name, tag_implication_id) + "\n\nReason: #{reason}"
|
||||
},
|
||||
:category_id => Danbooru.config.alias_implication_forum_category
|
||||
)
|
||||
end
|
||||
|
||||
def validate_tag_implication
|
||||
ti = @tag_implication || build_tag_implication
|
||||
|
||||
if ti.invalid?
|
||||
self.errors.add(:base, ti.errors.full_messages.join("; "))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def validate_forum_topic
|
||||
return if skip_forum
|
||||
ft = @forum_topic || build_forum_topic(nil)
|
||||
if ft.invalid?
|
||||
self.errors.add(:base, ft.errors.full_messages.join("; "))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def skip_forum=(v)
|
||||
@skip_forum = v.to_s.truthy?
|
||||
def tag_relationship_class
|
||||
TagImplication
|
||||
end
|
||||
end
|
||||
|
73
app/logical/tag_relationship_request.rb
Normal file
73
app/logical/tag_relationship_request.rb
Normal file
@ -0,0 +1,73 @@
|
||||
class TagRelationshipRequest
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :antecedent_name, :consequent_name, :tag_relationship, :reason, :forum_topic, :skip_forum
|
||||
|
||||
validate :validate_tag_relationship
|
||||
validate :validate_forum_topic
|
||||
validates :reason, length: { minimum: 5 }, unless: :skip_forum
|
||||
|
||||
def initialize(attributes)
|
||||
@antecedent_name = attributes[:antecedent_name].strip.tr(" ", "_")
|
||||
@consequent_name = attributes[:consequent_name].strip.tr(" ", "_")
|
||||
@reason = attributes[:reason]
|
||||
self.skip_forum = attributes[:skip_forum]
|
||||
end
|
||||
|
||||
def create
|
||||
return false if invalid?
|
||||
|
||||
tag_relationship_class.transaction do
|
||||
@tag_relationship = build_tag_relationship
|
||||
@tag_relationship.save
|
||||
|
||||
unless skip_forum
|
||||
@forum_topic = build_forum_topic(@tag_relationship.id)
|
||||
@forum_topic.save
|
||||
|
||||
@tag_relationship.forum_topic_id = @forum_topic.id
|
||||
@tag_relationship.forum_post_id = @forum_topic.posts.first.id
|
||||
@tag_relationship.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_tag_relationship
|
||||
x = tag_relationship_class.new(
|
||||
antecedent_name: antecedent_name,
|
||||
consequent_name: consequent_name
|
||||
)
|
||||
x.status = "pending"
|
||||
x
|
||||
end
|
||||
|
||||
def build_forum_topic(tag_relationship_id)
|
||||
ForumTopic.new(
|
||||
title: self.class.topic_title(antecedent_name, consequent_name),
|
||||
original_post_attributes: {
|
||||
body: self.class.command_string(antecedent_name, consequent_name, tag_relationship_id) + "\n\nReason: #{reason}"
|
||||
},
|
||||
category_id: Danbooru.config.alias_implication_forum_category
|
||||
)
|
||||
end
|
||||
|
||||
def validate_tag_relationship
|
||||
tag_relationship = @tag_relationship || build_tag_relationship
|
||||
|
||||
if tag_relationship.invalid?
|
||||
errors.add(:base, tag_relationship.errors.full_messages.join("; "))
|
||||
end
|
||||
end
|
||||
|
||||
def validate_forum_topic
|
||||
return if skip_forum
|
||||
ft = @forum_topic || build_forum_topic(nil)
|
||||
if ft.invalid?
|
||||
errors.add(:base, ft.errors.full_messages.join("; "))
|
||||
end
|
||||
end
|
||||
|
||||
def skip_forum=(v)
|
||||
@skip_forum = v.to_s.truthy?
|
||||
end
|
||||
end
|
@ -82,6 +82,7 @@ class UploadService
|
||||
post.image_width = upload.image_width
|
||||
post.image_height = upload.image_height
|
||||
post.file_size = upload.file_size
|
||||
post.duration = upload.video_duration(upload.file.path)
|
||||
post.source = "#{replacement.source}\n" + post.source
|
||||
post.tag_string = upload.tag_string
|
||||
# Reset ownership information on post.
|
||||
|
@ -14,7 +14,8 @@ class BulkUpdateRequest < ApplicationRecord
|
||||
validate :forum_topic_id_not_invalid
|
||||
validate :validate_script, on: :create
|
||||
validate :check_validate_script, on: :update
|
||||
before_validation :initialize_attributes, :on => :create
|
||||
validates :reason, length: { minimum: 5 }, on: :create, unless: :skip_forum
|
||||
before_validation :initialize_attributes, on: :create
|
||||
before_validation :normalize_text
|
||||
after_create :create_forum_topic
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
class EmailBlacklist < ApplicationRecord
|
||||
UNVERIFY_COUNT_TRESHOLD = 50
|
||||
|
||||
belongs_to_creator
|
||||
|
||||
validates :domain, uniqueness: { case_sensitive: false, message: 'already exists' }
|
||||
after_save :invalidate_cache
|
||||
after_create :invalidate_cache
|
||||
after_create :unverify_accounts
|
||||
after_destroy :invalidate_cache
|
||||
|
||||
def self.is_banned?(email)
|
||||
@ -56,4 +59,12 @@ class EmailBlacklist < ApplicationRecord
|
||||
def invalidate_cache
|
||||
Cache.delete('banned_emails')
|
||||
end
|
||||
|
||||
def unverify_accounts
|
||||
# Only unverify exact domain matches
|
||||
matching_users = User.search(email_matches: "*@#{domain}")
|
||||
return if matching_users.count > UNVERIFY_COUNT_TRESHOLD
|
||||
|
||||
matching_users.each(&:mark_unverified!)
|
||||
end
|
||||
end
|
||||
|
@ -46,7 +46,7 @@ class ModAction < ApplicationRecord
|
||||
:report_reason_update,
|
||||
:set_update,
|
||||
:set_delete,
|
||||
:set_mark_private,
|
||||
:set_change_visibility,
|
||||
:tag_alias_create,
|
||||
:tag_alias_update,
|
||||
:tag_implication_create,
|
||||
|
@ -329,10 +329,12 @@ class Post < ApplicationRecord
|
||||
|
||||
def approve!(approver = CurrentUser.user, force: false)
|
||||
raise ApprovalError.new("Post already approved.") if self.approver != nil && !force
|
||||
PostEvent.add(id, CurrentUser.user, :approved)
|
||||
|
||||
approv = approvals.create(user: approver)
|
||||
flags.each(&:resolve!)
|
||||
if flags.unresolved.any?
|
||||
unflag!
|
||||
end
|
||||
PostEvent.add(id, CurrentUser.user, :approved)
|
||||
update(approver: approver, is_flagged: false, is_pending: false, is_deleted: false)
|
||||
approv
|
||||
end
|
||||
@ -762,19 +764,19 @@ class Post < ApplicationRecord
|
||||
|
||||
when /^set:(\d+)$/i
|
||||
set = PostSet.find_by_id($1.to_i)
|
||||
set.add!(self) if set && set.can_edit?(CurrentUser.user)
|
||||
set.add!(self) if set&.can_edit_posts?(CurrentUser.user)
|
||||
|
||||
when /^-set:(\d+)$/i
|
||||
set = PostSet.find_by_id($1.to_i)
|
||||
set.remove!(self) if set && set.can_edit?(CurrentUser.user)
|
||||
set.remove!(self) if set&.can_edit_posts?(CurrentUser.user)
|
||||
|
||||
when /^set:(.+)$/i
|
||||
set = PostSet.find_by_shortname($1)
|
||||
set.add!(self) if set && set.can_edit?(CurrentUser.user)
|
||||
set.add!(self) if set&.can_edit_posts?(CurrentUser.user)
|
||||
|
||||
when /^-set:(.+)$/i
|
||||
set = PostSet.find_by_shortname($1)
|
||||
set.remove!(self) if set && set.can_edit?(CurrentUser.user)
|
||||
set.remove!(self) if set&.can_edit_posts?(CurrentUser.user)
|
||||
|
||||
when /^child:none$/i
|
||||
children.each do |post|
|
||||
|
@ -66,7 +66,7 @@ class PostSet < ApplicationRecord
|
||||
end
|
||||
|
||||
def saved_change_to_watched_attributes?
|
||||
saved_change_to_name? || saved_change_to_shortname? || saved_change_to_description?
|
||||
saved_change_to_name? || saved_change_to_shortname? || saved_change_to_description? || saved_change_to_transfer_on_delete?
|
||||
end
|
||||
|
||||
module ValidationMethods
|
||||
@ -146,11 +146,15 @@ class PostSet < ApplicationRecord
|
||||
|
||||
module AccessMethods
|
||||
def can_view?(user)
|
||||
is_public || creator_id == user.id || user.is_admin?
|
||||
is_public || is_owner?(user) || user.is_admin?
|
||||
end
|
||||
|
||||
def can_edit?(user)
|
||||
is_owner?(user) || is_maintainer?(user)
|
||||
def can_edit_settings?(user)
|
||||
is_owner?(user) || user.is_admin?
|
||||
end
|
||||
|
||||
def can_edit_posts?(user)
|
||||
can_edit_settings?(user) || (is_maintainer?(user) && is_public)
|
||||
end
|
||||
|
||||
def is_maintainer?(user)
|
||||
@ -168,7 +172,7 @@ class PostSet < ApplicationRecord
|
||||
|
||||
def is_owner?(user)
|
||||
return false if user.is_blocked?
|
||||
creator_id == user.id || user.is_admin?
|
||||
creator_id == user.id
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -203,7 +203,7 @@ class TagAlias < TagRelationship
|
||||
end
|
||||
rescue Exception => e
|
||||
Rails.logger.error("[TA] #{e.message}\n#{e.backtrace}")
|
||||
if tries < 5
|
||||
if tries < 5 && !Rails.env.test?
|
||||
tries += 1
|
||||
sleep 2 ** tries
|
||||
retry
|
||||
|
@ -124,7 +124,7 @@ class TagImplication < TagRelationship
|
||||
forum_updater.update(approval_message(approver), "APPROVED") if update_topic
|
||||
end
|
||||
rescue Exception => e
|
||||
if tries < 5
|
||||
if tries < 5 && !Rails.env.test?
|
||||
tries += 1
|
||||
sleep 2 ** tries
|
||||
retry
|
||||
|
@ -824,7 +824,7 @@ class User < ApplicationRecord
|
||||
# q = q.attribute_matches(:note_update_count, params[:note_update_count])
|
||||
# q = q.attribute_matches(:favorite_count, params[:favorite_count])
|
||||
|
||||
if params[:email_matches].present? && CurrentUser.is_admin?
|
||||
if params[:email_matches].present?
|
||||
q = q.where_ilike(:email, params[:email_matches])
|
||||
end
|
||||
|
||||
|
@ -2,12 +2,10 @@
|
||||
<div id="a-index">
|
||||
<h1>Danger Zone</h1>
|
||||
<h2>Uploads</h2>
|
||||
<% if DangerZone.uploads_disabled? %>
|
||||
Uploads are currently <b>disabled</b>.
|
||||
<%= link_to "Re-enable Uploads", enable_uploads_admin_danger_zone_index_path, method: :put, data: { confirm: "Are you sure?" } %>
|
||||
<% else %>
|
||||
Uploads are currently <b>enabled</b>.
|
||||
<%= link_to "Prevent Uploads", disable_uploads_admin_danger_zone_index_path, method: :put, data: { confirm: "Are you sure?" } %>
|
||||
Uploads are currently permitted for <b><%= User.level_string(DangerZone.min_upload_level) %></b> and up.
|
||||
<%= simple_form_for(:uploading_limits, url: uploading_limits_admin_danger_zone_index_path, method: :put) do |f| %>
|
||||
<%= f.input :min_level, collection: User.level_hash.select {|k,v| v >= User::Levels::MEMBER }.to_a, selected: DangerZone.min_upload_level %>
|
||||
<%= f.button :submit, value: "Submit" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,31 +3,26 @@
|
||||
<%= format_text(WikiPage.titled(Danbooru.config.replacement_notice_wiki_page).first.try(&:body)) %>
|
||||
</div>
|
||||
|
||||
<%= simple_form_for(post_replacement, url: post_replacements_path(post_id: post_replacement.post_id), method: :post) do |f| %>
|
||||
<%= simple_form_for(post_replacement, url: post_replacements_path(post_id: post_replacement.post_id), html: { multipart: true }, method: :post) do |f| %>
|
||||
<div>
|
||||
<% if post.visible? %>
|
||||
<%= PostPresenter.preview(post, tags: "status:any", no_blacklist: true) %>
|
||||
<% end %>
|
||||
<div><%= "#{post.image_width}x#{post.image_height} (#{post.file_size.to_s(:human_size, precision: 5)})" %></div>
|
||||
</div>
|
||||
|
||||
<%= f.input :replacement_file, label: "File", as: :file %>
|
||||
<%= f.input :replacement_url, label: "Replacement URL", hint: "The source URL to download the replacement from.", as: :string, input_html: {size: 40} %>
|
||||
<div class="box-section sect_red" id="bad_upload_url" style="display: none;">
|
||||
The direct URL entered has the following problem: <span id="bad_upload_url_reason"></span><br>
|
||||
You should review <a href="/wiki/show/howto:sites_and_sources">the sourcing guide</a>.
|
||||
</div>
|
||||
<div id="whitelist-warning" style="display: none;"
|
||||
:class="{'whitelist-warning-allowed': whitelist.allowed, 'whitelist-warning-disallowed': !whitelist.allowed}">
|
||||
<span>Uploads from <b id="whitelist-warning-domain"></b> are <span id="whitelist-warning-not">not </span>permitted.</span>
|
||||
</div>
|
||||
<br>
|
||||
<div id="replacement-uploader"></div>
|
||||
<br>
|
||||
<%= f.input :source, label: "Additional Source", hint: "(Optional) The submission page the replacement file came from.", input_html: {size: 40} %>
|
||||
<%= f.input :reason, label: "Reason", hint: "Tell us why this file should replace the original.", as: :string, input_html: {size: 40} %>
|
||||
<%= f.submit "Submit" %>
|
||||
<% end %>
|
||||
|
||||
<div id="replacement-upload-preview">
|
||||
<div id="replacement_preview_dims"></div>
|
||||
<img id="replacement_preview_img" src="" style="max-width: 100%;"/>
|
||||
</div>
|
||||
<div id="replacement-preview"></div>
|
||||
</div>
|
||||
|
||||
<%= javascript_tag nonce: true do -%>
|
||||
var uploaderSettings = {
|
||||
maxFileSize: <%= Danbooru.config.max_file_size %>,
|
||||
};
|
||||
Danbooru.Replacer.init();
|
||||
<% end -%>
|
||||
|
@ -12,15 +12,15 @@
|
||||
<%= subnav_link_to "Posts", posts_path(tags: "set:#{@post_set.shortname}") %>
|
||||
<%= subnav_link_to "Maintainers", maintainers_post_set_path(@post_set) %>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser.user) || CurrentUser.is_admin? %>
|
||||
<% if @post_set.can_edit_settings?(CurrentUser.user) %>
|
||||
<%= subnav_link_to "Edit", edit_post_set_path(@post_set) %>
|
||||
<% end %>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser.user) || @post_set.is_maintainer?(CurrentUser.user) %>
|
||||
<% if @post_set.can_edit_posts?(CurrentUser.user) %>
|
||||
<%= subnav_link_to "Edit Posts", post_list_post_set_path(@post_set) %>
|
||||
<% end %>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser.user) || CurrentUser.is_admin? %>
|
||||
<% if @post_set.can_edit_settings?(CurrentUser.user) %>
|
||||
<%= subnav_link_to "Delete", post_set_path(@post_set), method: 'delete', data: {confirm: 'Are you sure you want to delete this set?'} %>
|
||||
<% end %>
|
||||
<%= subnav_link_to "Report", new_ticket_path(type: 'set', disp_id: @post_set.id) %>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div id="c-edit">
|
||||
<h2>Editing <span class="set-name"><%= @post_set.name %></span></h2>
|
||||
|
||||
<% if @can_edit %>
|
||||
<% if @post_set.can_edit_settings?(CurrentUser) %>
|
||||
<div class='section'>
|
||||
<%= simple_form_for(@post_set) do |f| %>
|
||||
<%= f.input :name, as: :string %>
|
||||
@ -21,13 +21,11 @@
|
||||
<%= f.button :submit, "Update" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser) %>
|
||||
<%= link_to "» Click here to edit maintainers.".html_safe, maintainers_post_set_path(@post_set) %><br/>
|
||||
<% end %>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser) || @post_set.is_maintainer?(CurrentUser) %>
|
||||
<% if @post_set.can_edit_posts?(CurrentUser) %>
|
||||
<%= link_to "» Click here to add/remove posts.".html_safe, post_list_post_set_path(@post_set) %><br/>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<p>Maintainers are users who are assigned to a set and have the ability to add or remove posts from it. They cannot
|
||||
edit any other details of the set.</p>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser) && @post_set.is_public %>
|
||||
<% if @post_set.can_edit_settings?(CurrentUser) && @post_set.is_public %>
|
||||
<h3 style="margin-top:15px;">Add new Maintainer</h3>
|
||||
<div class='section' style="width:380px;">
|
||||
<%= form_tag(post_set_maintainers_path) do %>
|
||||
@ -33,10 +33,10 @@
|
||||
<td><%= m.status.capitalize %></td>
|
||||
<td><%= compact_time m.updated_at %></td>
|
||||
<td>
|
||||
<% if @post_set.is_owner?(CurrentUser) && m.status == "approved" %>
|
||||
<% if @post_set.can_edit_settings?(CurrentUser) && m.status == "approved" %>
|
||||
<%= link_to "Remove", post_set_maintainer_path(m), method: :delete, data: {confirm: "Are you sure you want to remove this maintainer?"} %>
|
||||
<% end %>
|
||||
<% if @post_set.is_owner?(CurrentUser) && m.status == "pending" %>
|
||||
<% if @post_set.can_edit_settings?(CurrentUser) && m.status == "pending" %>
|
||||
<%= link_to "Remove", post_set_maintainer_path(m), method: :delete, data: {confirm: "Are you sure you want to remove this pending invite?"} %>
|
||||
<% end %>
|
||||
</td>
|
||||
|
@ -37,7 +37,7 @@
|
||||
<div class="set-empty section">
|
||||
<p>This set has no posts in it.</p>
|
||||
|
||||
<% if @post_set.is_owner?(CurrentUser.user) || @post_set.is_maintainer?(CurrentUser.user) %>
|
||||
<% if @post_set.can_edit_posts?(CurrentUser.user) %>
|
||||
To start adding posts to this set:
|
||||
<ul>
|
||||
<li>On a post's page, click <strong>Add to Set</strong> under <strong>Options</strong> in the sidebar,
|
||||
@ -49,7 +49,7 @@
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
<% elsif @post_set.is_owner?(CurrentUser.user) || @post_set.is_maintainer?(CurrentUser.user) %>
|
||||
<% elsif @post_set.can_edit_posts?(CurrentUser.user) %>
|
||||
<div class="section">
|
||||
To add posts to this set:
|
||||
<ul>
|
||||
|
@ -24,13 +24,11 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="tag-string-editor">
|
||||
<%= f.text_area :tag_string, :size => "60x5", :spellcheck => false, :"data-autocomplete" => "tag-edit", :"data-shortcut" => "e", :value => post.presenter.split_tag_list_text + " " %>
|
||||
</div>
|
||||
<div id="tag-string-editor"></div>
|
||||
|
||||
<div>
|
||||
<%= f.label :locked_tags, "Locked Tags" %>
|
||||
<%= f.text_area :locked_tags, :size => "60x2", :spellcheck => false, :"data-autocomplete" => "tag-edit", :"data-shortcut" => "e", :value => (post.locked_tags || ""), disabled: !CurrentUser.is_admin? %>
|
||||
<%= f.text_area :locked_tags, :size => "60x2", :spellcheck => false, :"data-autocomplete" => "tag-edit", :value => (post.locked_tags || ""), disabled: !CurrentUser.is_admin? %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,69 +1,3 @@
|
||||
<style>
|
||||
.stats-column {
|
||||
width:400px;
|
||||
display:inline-block;
|
||||
vertical-align:top;
|
||||
}
|
||||
|
||||
.stats-chart {
|
||||
display:inline-block;
|
||||
width:300px;
|
||||
height:300px;
|
||||
}
|
||||
|
||||
.stats-chart-container {
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
.stats-pct {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
#stats-column-1 table {
|
||||
margin-bottom:60px;
|
||||
}
|
||||
|
||||
#stats-column-2 table {
|
||||
margin-bottom:19px;
|
||||
}
|
||||
|
||||
#stats-column-3 table {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.stats-column table:last-child {
|
||||
margin-bottom:10px !important;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
margin-bottom:2em;
|
||||
padding:3px;
|
||||
border-spacing:0;
|
||||
}
|
||||
td { padding:1px 4px; vertical-align:top; }
|
||||
th { font-weight:bold; text-align:left; vertical-align:top; padding:0.2em 0.5em; white-space:nowrap; }
|
||||
|
||||
tr.selected { background-color:#288233 !important; /* green */ }
|
||||
|
||||
|
||||
.rounded {
|
||||
background-color:#203f6c;
|
||||
border-radius:8px;
|
||||
box-shadow:2px 2px 5px #07162D;
|
||||
}
|
||||
|
||||
.rounded th { border-bottom:2px solid #6388c3; padding: 2px 10px; }
|
||||
.rounded tr:nth-child(even) { background-color:#204274; }
|
||||
.rounded tr:nth-child(even) td { border-top:1px solid #255193; border-bottom:1px solid #255193; }
|
||||
.rounded tr:last-child td{ border-bottom:0; }
|
||||
.rounded td { padding:2px 10px; }
|
||||
|
||||
.rounded-even { background-color:#204274; }
|
||||
.rounded-even td { border-top:1px solid #255193; border-bottom:1px solid #255193; }
|
||||
.rounded-odd { background-color:transparent; }
|
||||
.rounded-odd td { border-top:none; border-bottom:none; }
|
||||
</style>
|
||||
<%= javascript_tag nonce: true do -%>
|
||||
function pct(current,total) {
|
||||
return Math.round((current/total)*100) + "%"
|
||||
@ -185,7 +119,7 @@
|
||||
<p>Refreshed once a day.</p>
|
||||
<div class='stats-column' id='stats-column-1'>
|
||||
<h2>Site</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Started</td>
|
||||
<td style='width:105px;'><%= @stats['started'] %></td>
|
||||
@ -213,7 +147,7 @@
|
||||
</table>
|
||||
|
||||
<h2>Posts</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Total posts</td>
|
||||
<td style='width:50px;'><%= del(@stats['total_posts']) %></td>
|
||||
@ -283,7 +217,7 @@
|
||||
|
||||
<div class='stats-column' id='stats-column-2'>
|
||||
<h2>Image files</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Total existing posts</td>
|
||||
<td style='width:50px;'><%= del(@stats['active_posts']+@stats['deleted_posts']) %></td>
|
||||
@ -320,7 +254,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Users</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>User count</td>
|
||||
<td style='width:50px;'><%= del(@stats['total_users']) %></td>
|
||||
@ -367,7 +301,7 @@
|
||||
|
||||
<div class='stats-column' id='stats-column-3'>
|
||||
<h2>Comments</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Comment count</td>
|
||||
<td style='width:50px;'><%= del(@stats['total_comments']) %></td>
|
||||
@ -388,7 +322,7 @@
|
||||
</table>
|
||||
|
||||
<h2>Forum Posts</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Thread count</td>
|
||||
<td style='width:100px;'><%= del(@stats['total_forum_threads']) %></td>
|
||||
@ -404,7 +338,7 @@
|
||||
</table>
|
||||
|
||||
<h2>Blips</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Blip count</td>
|
||||
<td style='width:50px;'><%= del(@stats['total_blips']) %></td>
|
||||
@ -425,7 +359,7 @@
|
||||
</table>
|
||||
|
||||
<h2>Tags</h2>
|
||||
<table class='rounded'>
|
||||
<table class='stats-rounded'>
|
||||
<tr>
|
||||
<td style='width:250px;'>Tag count</td>
|
||||
<td style='width:50px;'><%= del(@stats['total_tags']) %></td>
|
||||
|
@ -24,8 +24,7 @@ Rails.application.routes.draw do
|
||||
resources :staff_notes, only: [:index]
|
||||
resources :danger_zone, only: [:index] do
|
||||
collection do
|
||||
put :enable_uploads
|
||||
put :disable_uploads
|
||||
put :uploading_limits
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -176,7 +175,7 @@ Rails.application.routes.draw do
|
||||
get :is_allowed
|
||||
end
|
||||
end
|
||||
resources :email_blacklists
|
||||
resources :email_blacklists, only: [:new, :create, :destroy, :index]
|
||||
resource :iqdb_queries, :only => [:show] do
|
||||
collection do
|
||||
post :show
|
||||
|
@ -1,17 +1,29 @@
|
||||
// config/webpack/rules/vue.js
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
whitespace: "preserve",
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [new VueLoaderPlugin()],
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new DefinePlugin({
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: false,
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.vue']
|
||||
extensions: ['.vue'],
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@rails/webpacker": "^6.0.0-rc.5",
|
||||
"@vue/compiler-sfc": "^3.1.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"core-js": "3.8",
|
||||
"css-loader": "^6.3.0",
|
||||
@ -16,9 +17,8 @@
|
||||
"sass-loader": "^12.1.0",
|
||||
"script-loader": "^0.7.2",
|
||||
"style-loader": "^3.3.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vue": "^3.1.0",
|
||||
"vue-loader": "^16.0.0",
|
||||
"webpack": "^5.51.1",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"zingtouch": "^1.0.6"
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
@ -1,21 +1,49 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Downbooru</title>
|
||||
<style type="text/css">
|
||||
<title>e621 - Maintenance</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 10em 2em;
|
||||
font-family: 'Palatino Linotype', serif;
|
||||
color: white;
|
||||
background-color: #012e56;
|
||||
font-family: verdana,sans-serif;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
h1, p {
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img, .mascot {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
/* Crop out some of the empty space to the left so it looks better on mobile */
|
||||
background-image: url("https://static1.e621.net/data/mascot_bg/esix2.jpg");
|
||||
background-position: -200px 0px;
|
||||
width: 400px;
|
||||
height: 615px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #b4c7d9;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #e9f2fa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>The site is down for maintenance.</h1>
|
||||
<p><a class="twitter-timeline" data-width="500" data-tweet-limit="3" data-dnt="true" href="https://twitter.com/danboorubot">Check Twitter for updates.</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p>
|
||||
<img src="http://static1.e621.net/data/mascot_bg/android-chrome-192x192.png">
|
||||
<p>
|
||||
We are performing maintenance on the site and its servers! Keep an eye on the <a href="https://twitter.com/e621dotnet">e621 Twitter</a> for more details.
|
||||
</p>
|
||||
<p>Thanks for your patience!</p>
|
||||
<div class="mascot"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
11944
public/vendor/vue.js
vendored
11944
public/vendor/vue.js
vendored
File diff suppressed because it is too large
Load Diff
@ -2,5 +2,6 @@ FactoryBot.define do
|
||||
factory(:bulk_update_request) do |f|
|
||||
title { "xxx" }
|
||||
script { "create alias aaa -> bbb" }
|
||||
reason { "xxxxx" }
|
||||
end
|
||||
end
|
||||
|
27
test/functional/admin/danger_zone_controller_test.rb
Normal file
27
test/functional/admin/danger_zone_controller_test.rb
Normal file
@ -0,0 +1,27 @@
|
||||
require 'test_helper'
|
||||
|
||||
class Admin::DangerZoneControllerTest < ActionDispatch::IntegrationTest
|
||||
context "The danger zone controller" do
|
||||
setup do
|
||||
@admin = create(:admin_user)
|
||||
end
|
||||
|
||||
teardown do
|
||||
DangerZone.min_upload_level = User::Levels::MEMBER
|
||||
end
|
||||
|
||||
context "index action" do
|
||||
should "render" do
|
||||
get_auth admin_danger_zone_index_path, @admin
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
context "uploading limits action" do
|
||||
should "work" do
|
||||
put_auth uploading_limits_admin_danger_zone_index_path, @admin, params: { uploading_limits: { min_level: User::Levels::PRIVILEGED } }
|
||||
assert_equal DangerZone.min_upload_level, User::Levels::PRIVILEGED
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -17,7 +17,7 @@ class BulkUpdateRequestsControllerTest < ActionDispatch::IntegrationTest
|
||||
context "#create" do
|
||||
should "succeed" do
|
||||
assert_difference("BulkUpdateRequest.count", 1) do
|
||||
post_auth bulk_update_requests_path, @user, params: {bulk_update_request: {script: "create alias aaa -> bbb", title: "xxx"}}
|
||||
post_auth bulk_update_requests_path, @user, params: { bulk_update_request: { script: "create alias aaa -> bbb", title: "xxx", reason: "xxxxx" } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -13,6 +13,7 @@ class IqdbQueriesControllerTest < ActionDispatch::IntegrationTest
|
||||
context "show action" do
|
||||
context "with a url parameter" do
|
||||
setup do
|
||||
FactoryBot.create(:upload_whitelist, pattern: "*google.com")
|
||||
@url = "https://google.com"
|
||||
@params = { url: @url }
|
||||
@mocked_response = [{
|
||||
@ -41,7 +42,7 @@ class IqdbQueriesControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
should "redirect to iqdbs" do
|
||||
IqdbProxy.expects(:query).with(@posts[0].preview_file_url).returns(@mocked_response)
|
||||
IqdbProxy.expects(:query_path).with(@posts[0].preview_file_path).returns(@mocked_response)
|
||||
get_auth iqdb_queries_path, @user, params: @params
|
||||
assert_select("#post_#{@posts[0].id}")
|
||||
end
|
||||
@ -49,14 +50,14 @@ class IqdbQueriesControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
context "with matches" do
|
||||
setup do
|
||||
json = @posts.map {|x| {"post_id" => x.id, "score" => 1}}.to_json
|
||||
json = @posts.map { |x| { "post_id" => x.id, "score" => 1 } }.to_json
|
||||
@params = { matches: json }
|
||||
end
|
||||
|
||||
should "render with matches" do
|
||||
get_auth iqdb_queries_path, @user, params: @params
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,7 +4,7 @@ class RelatedTagsControllerTest < ActionDispatch::IntegrationTest
|
||||
context "The related tags controller" do
|
||||
context "show action" do
|
||||
should "work" do
|
||||
get related_tag_path, params: { query: "touhou" }
|
||||
get_auth related_tag_path, create(:user), params: { query: "touhou" }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
@ -16,7 +16,7 @@ class TagAliasRequestsControllerTest < ActionDispatch::IntegrationTest
|
||||
context "create action" do
|
||||
should "render" do
|
||||
assert_difference("ForumTopic.count", 1) do
|
||||
post_auth tag_alias_request_path, @user, params: {:tag_alias_request => {:antecedent_name => "aaa", :consequent_name => "bbb", :reason => "ccc"}}
|
||||
post_auth tag_alias_request_path, @user, params: { tag_alias_request: { antecedent_name: "aaa", consequent_name: "bbb", reason: "ccccc" } }
|
||||
end
|
||||
assert_redirected_to(forum_topic_path(ForumTopic.last))
|
||||
end
|
||||
|
@ -18,7 +18,7 @@ class TagImplicationRequestsControllerTest < ActionDispatch::IntegrationTest
|
||||
context "create action" do
|
||||
should "create forum post" do
|
||||
assert_difference("ForumTopic.count", 1) do
|
||||
post_auth tag_implication_request_path, @user, params: {:tag_implication_request => {:antecedent_name => "aaa", :consequent_name => "bbb", :reason => "ccc"}}
|
||||
post_auth tag_implication_request_path, @user, params: { tag_implication_request: { antecedent_name: "aaa", consequent_name: "bbb", reason: "ccccc" } }
|
||||
end
|
||||
assert_redirected_to(forum_topic_path(ForumTopic.last))
|
||||
end
|
||||
|
@ -12,7 +12,6 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
|
||||
context "The uploads controller" do
|
||||
setup do
|
||||
@user = create(:janitor_user)
|
||||
mock_iqdb_service!
|
||||
end
|
||||
|
||||
context "new action" do
|
||||
@ -65,6 +64,26 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when uploads are disabled" do
|
||||
setup do
|
||||
DangerZone.min_upload_level = User::Levels::PRIVILEGED
|
||||
end
|
||||
|
||||
teardown do
|
||||
DangerZone.min_upload_level = User::Levels::MEMBER
|
||||
end
|
||||
|
||||
should "prevent uploads" do
|
||||
get_auth new_upload_path, create(:user)
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
should "allow uploads for users of the same or higher level" do
|
||||
get_auth new_upload_path, create(:privileged_user, created_at: 2.weeks.ago)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "index action" do
|
||||
@ -75,7 +94,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
should "render" do
|
||||
get uploads_path
|
||||
get_auth uploads_path, @user
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
@ -90,7 +109,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
|
||||
status: @upload.status
|
||||
}
|
||||
|
||||
get_auth uploads_path, params: { search: search_params }
|
||||
get_auth uploads_path, @user, params: { search: search_params }
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
@ -90,25 +90,26 @@ class UploadServiceTest < ActiveSupport::TestCase
|
||||
|
||||
context ".generate_resizes" do
|
||||
context "for a video" do
|
||||
teardown do
|
||||
@file.close
|
||||
end
|
||||
|
||||
context "for a webm" do
|
||||
setup do
|
||||
@file = File.open("test/files/test-512x512.webm", "rb")
|
||||
@upload = mock()
|
||||
@upload.stubs(:is_video?).returns(true)
|
||||
@upload = UploadService.new(FactoryBot.attributes_for(:upload).merge(file: @file, uploader: @user, uploader_ip_addr: '127.0.0.1')).start!
|
||||
end
|
||||
|
||||
teardown do
|
||||
@file.close
|
||||
end
|
||||
|
||||
should "generate a video" do
|
||||
preview, crop, sample = subject.generate_resizes(@file, @upload)
|
||||
assert_operator(File.size(preview.path), :>, 0)
|
||||
assert_operator(File.size(crop.path), :>, 0)
|
||||
assert_equal(150, ImageSpec.new(preview.path).width)
|
||||
assert_equal(150, ImageSpec.new(preview.path).height)
|
||||
assert_equal(150, ImageSpec.new(crop.path).width)
|
||||
assert_equal(150, ImageSpec.new(crop.path).height)
|
||||
preview_image = Vips::Image.new_from_file(preview.path)
|
||||
crop_image = Vips::Image.new_from_file(crop.path)
|
||||
assert_equal(150, preview_image.width)
|
||||
assert_equal(150, preview_image.height)
|
||||
assert_equal(150, crop_image.width)
|
||||
assert_equal(150, crop_image.height)
|
||||
preview.close
|
||||
preview.unlink
|
||||
crop.close
|
||||
|
@ -1,13 +1,5 @@
|
||||
ENV["RAILS_ENV"] = "test"
|
||||
|
||||
if ENV["SIMPLECOV"]
|
||||
require 'simplecov'
|
||||
SimpleCov.start 'rails' do
|
||||
add_group "Libraries", ["app/logical", "lib"]
|
||||
add_group "Presenters", "app/presenters"
|
||||
end
|
||||
end
|
||||
|
||||
require File.expand_path('../../config/environment', __FILE__)
|
||||
require 'rails/test_help'
|
||||
require 'cache'
|
||||
@ -47,9 +39,7 @@ module TestHelpers
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ActiveSupport::TestCase
|
||||
include IqdbTestHelper
|
||||
include UploadTestHelper
|
||||
include TestHelpers
|
||||
|
||||
@ -64,7 +54,6 @@ class ActiveSupport::TestCase
|
||||
Danbooru.config.stubs(:storage_manager).returns(storage_manager)
|
||||
Danbooru.config.stubs(:backup_storage_manager).returns(StorageManager::Null.new)
|
||||
Danbooru.config.stubs(:enable_email_verification?).returns(false)
|
||||
|
||||
end
|
||||
|
||||
teardown do
|
||||
|
@ -1,30 +0,0 @@
|
||||
module IqdbTestHelper
|
||||
def mock_iqdb_service!
|
||||
mock_sqs_service = Class.new do
|
||||
def initialize
|
||||
@commands = []
|
||||
end
|
||||
|
||||
def commands
|
||||
@commands
|
||||
end
|
||||
|
||||
def send_message(msg)
|
||||
@commands << msg.split(/\n/).first
|
||||
end
|
||||
end
|
||||
|
||||
service = mock_sqs_service.new
|
||||
Post.stubs(:iqdb_sqs_service).returns(service)
|
||||
|
||||
Danbooru.config.stubs(:iqdbs_server).returns("http://localhost:3004")
|
||||
end
|
||||
|
||||
def mock_iqdb_matches!(post_or_source, matches)
|
||||
source = post_or_source.is_a?(Post) ? post_or_source.preview_file_url : post_or_source
|
||||
url = "http://localhost:3004/similar?key=hunter2&url=#{CGI.escape source}&ref"
|
||||
body = matches.map { |post| { post_id: post.id } }.to_json
|
||||
|
||||
stub_request(:get, url).to_return(body: body)
|
||||
end
|
||||
end
|
@ -146,7 +146,7 @@ class BanTest < ActiveSupport::TestCase
|
||||
|
||||
context "when only expired bans exist" do
|
||||
setup do
|
||||
@ban = FactoryBot.create(:ban, :user => @user, :banner => @admin, :duration => -1)
|
||||
@ban = FactoryBot.create(:ban, :user => @user, :banner => @admin, :duration => 1)
|
||||
end
|
||||
|
||||
should "not return expired bans" do
|
||||
|
@ -71,7 +71,13 @@ class BulkUpdateRequestTest < ActiveSupport::TestCase
|
||||
|
||||
should "create a forum topic" do
|
||||
assert_difference("ForumTopic.count", 1) do
|
||||
BulkUpdateRequest.create(:title => "abc", :reason => "zzz", :script => "create alias aaa -> bbb")
|
||||
create(:bulk_update_request)
|
||||
end
|
||||
end
|
||||
|
||||
should "not create a forum when skip_forum is true" do
|
||||
assert_no_difference("ForumTopic.count") do
|
||||
create(:bulk_update_request, skip_forum: true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -21,8 +21,32 @@ class EmailBlacklistTest < ActiveSupport::TestCase
|
||||
|
||||
should "detect email by mx" do
|
||||
block = EmailBlacklist.create(creator: @user, domain: 'google.com', reason: 'test')
|
||||
|
||||
EmailBlacklist.stubs(:get_mx_records).returns(['google.com'])
|
||||
assert(EmailBlacklist.is_banned?('spam@e621.net'))
|
||||
|
||||
EmailBlacklist.unstub(:get_mx_records)
|
||||
assert_equal(false, EmailBlacklist.is_banned?('what@me.xynzs'))
|
||||
end
|
||||
|
||||
should "keep accounts verified if there are too many matches" do
|
||||
(EmailBlacklist::UNVERIFY_COUNT_TRESHOLD + 1).times do |i|
|
||||
@domain_blocked_user = create(:user, email: "#{i}@domain.com")
|
||||
end
|
||||
EmailBlacklist.create(creator: @user, domain: "domain.com", reason: "test")
|
||||
@domain_blocked_user.reload
|
||||
assert @domain_blocked_user.is_verified?
|
||||
end
|
||||
|
||||
should "unverify accounts if there are few matches" do
|
||||
@domain_blocked_user = create(:user, email: "0@domain.com")
|
||||
@other_user1 = create(:user, email: "0@prefix.domain.com")
|
||||
@other_user2 = create(:user, email: "0@somethingelse.xynzs")
|
||||
EmailBlacklist.create(creator: @user, domain: "domain.com", reason: "test")
|
||||
@domain_blocked_user.reload
|
||||
@other_user1.reload
|
||||
@other_user2.reload
|
||||
assert_not @domain_blocked_user.is_verified?
|
||||
assert @other_user1.is_verified?
|
||||
assert @other_user2.is_verified?
|
||||
end
|
||||
end
|
||||
|
@ -77,7 +77,8 @@ class NoteTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "update the post's last_noted_at field" do
|
||||
@note.update(x: 500)
|
||||
assert_nil(@post.last_noted_at)
|
||||
@note = create(:note, post: @post)
|
||||
@post.reload
|
||||
assert_equal(@post.last_noted_at, @note.updated_at)
|
||||
end
|
||||
@ -112,10 +113,12 @@ class NoteTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "update the post's last_noted_at field" do
|
||||
assert_nil(@post.last_noted_at)
|
||||
@note.update(:x => 500)
|
||||
@post.reload
|
||||
assert_equal(@post.last_noted_at.to_i, @note.updated_at.to_i)
|
||||
assert_equal(@post.last_noted_at, @note.updated_at)
|
||||
Timecop.travel(1.day.from_now) do
|
||||
@note.update(x: 500)
|
||||
@post.reload
|
||||
assert_equal(@post.last_noted_at, @note.updated_at)
|
||||
end
|
||||
end
|
||||
|
||||
should "create a version" do
|
||||
|
@ -95,6 +95,20 @@ class PostEventTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
context "unflag on approve" do
|
||||
setup do
|
||||
as @janitor do
|
||||
create(:post_flag, post: @post)
|
||||
end
|
||||
end
|
||||
|
||||
should "create both post events" do
|
||||
assert_post_events_created(@janitor, [:flag_removed, :approved]) do
|
||||
@post.approve!(@janitor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "replacements" do
|
||||
setup do
|
||||
@replacement = FactoryBot.create(:png_replacement, creator: @user, creator_ip_addr: '127.0.0.1', post: @post)
|
||||
|
@ -146,7 +146,6 @@ class PostReplacementTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
should "update post with new image" do
|
||||
old_md5 = @post.md5
|
||||
@replacement.approve! penalize_current_uploader: true
|
||||
@ -249,6 +248,18 @@ class PostReplacementTest < ActiveSupport::TestCase
|
||||
@replacement.approve! penalize_current_uploader: false
|
||||
assert_equal ["Status must be pending or original to approve"], @replacement.errors.full_messages
|
||||
end
|
||||
|
||||
context "update the duration" do
|
||||
setup do
|
||||
@replacement = FactoryBot.create(:webm_replacement, creator: @user, creator_ip_addr: '127.0.0.1', post: @post)
|
||||
end
|
||||
|
||||
should "when the replacement is a video" do
|
||||
@replacement.approve! penalize_current_uploader: false
|
||||
@post.reload
|
||||
assert @post.duration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "Toggle:" do
|
||||
|
@ -76,8 +76,7 @@ class PostTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "remove the post from iqdb" do
|
||||
mock_iqdb_service!
|
||||
|
||||
@post.expects(:remove_iqdb_async).once
|
||||
@post.expunge!
|
||||
end
|
||||
|
||||
@ -2255,8 +2254,8 @@ class PostTest < ActiveSupport::TestCase
|
||||
|
||||
assert_equal("https://#{Danbooru.config.hostname}/data/preview/deadbeef.jpg", @post.preview_file_url)
|
||||
|
||||
assert_equal("https://#{Socket.gethostname}/data/deadbeef.gif", @post.large_file_url)
|
||||
assert_equal("https://#{Socket.gethostname}/data/deadbeef.gif", @post.file_url)
|
||||
assert_equal("https://#{Danbooru.config.hostname}/data/deadbeef.gif", @post.large_file_url)
|
||||
assert_equal("https://#{Danbooru.config.hostname}/data/deadbeef.gif", @post.file_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -44,7 +44,20 @@ class TagAliasRequestTest < ActiveSupport::TestCase
|
||||
should "save the forum post id" do
|
||||
tar = TagAliasRequest.new(:antecedent_name => "aaa", :consequent_name => "bbb", :reason => "reason")
|
||||
tar.create
|
||||
assert_equal(tar.forum_topic.posts.first.id, tar.tag_alias.forum_post.id)
|
||||
assert_equal(tar.forum_topic.posts.first.id, tar.tag_relationship.forum_post.id)
|
||||
end
|
||||
|
||||
should "fail validation if the reason is too short" do
|
||||
tar = TagAliasRequest.new(antecedent_name: "aaa", consequent_name: "bbb", reason: "")
|
||||
tar.create
|
||||
assert_match(/Reason is too short/, tar.errors.full_messages.join)
|
||||
end
|
||||
|
||||
should "not create a forum post if skip_forum is true" do
|
||||
assert_no_difference("ForumPost.count") do
|
||||
tar = TagAliasRequest.new(antecedent_name: "aaa", consequent_name: "bbb", skip_forum: true)
|
||||
tar.create
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -185,10 +185,10 @@ class TagAliasTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "update the topic when failed" do
|
||||
@alias.stubs(:sleep).returns(true)
|
||||
@alias.stubs(:update_posts).raises(Exception, "oh no")
|
||||
TagAlias.any_instance.stubs(:update_blacklists).raises(Exception, "oh no")
|
||||
@alias.approve!(approver: @admin)
|
||||
@topic.reload
|
||||
@alias.reload
|
||||
|
||||
assert_equal("[FAILED] Tag alias: aaa -> bbb", @topic.title)
|
||||
assert_match(/error: oh no/, @alias.status)
|
||||
|
@ -5,7 +5,6 @@ class UploadTest < ActiveSupport::TestCase
|
||||
|
||||
context "In all cases" do
|
||||
setup do
|
||||
mock_iqdb_service!
|
||||
user = FactoryBot.create(:contributor_user)
|
||||
CurrentUser.user = user
|
||||
CurrentUser.ip_addr = "127.0.0.1"
|
||||
@ -21,6 +20,7 @@ class UploadTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
CurrentUser.user = FactoryBot.create(:user, :created_at => 1.year.ago)
|
||||
User.any_instance.stubs(:upload_limit).returns(0)
|
||||
Danbooru.config.stubs(:disable_throttles?).returns(false)
|
||||
end
|
||||
|
||||
should "fail creation" do
|
||||
|
294
yarn.lock
294
yarn.lock
@ -412,6 +412,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.7.tgz#0c3ed4a2eb07b165dfa85b3cc45c727334c4edae"
|
||||
integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==
|
||||
|
||||
"@babel/parser@^7.16.4":
|
||||
version "7.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.0.tgz#10a8d4e656bc01128d299a787aa006ce1a91e112"
|
||||
integrity sha512-AqDccGC+m5O/iUStSJy3DGRIUFu7WbY/CppZYwrEUB4N0tZlnI8CSTsgL7v5fHVFmUbRv2sd+yy27o8Ydt4MGg==
|
||||
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.15.4":
|
||||
version "7.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz#dbdeabb1e80f622d9f0b583efb2999605e0a567e"
|
||||
@ -1177,21 +1182,95 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
|
||||
integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
|
||||
|
||||
"@vue/component-compiler-utils@^3.1.0":
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.2.tgz#2f7ed5feed82ff7f0284acc11d525ee7eff22460"
|
||||
integrity sha512-rAYMLmgMuqJFWAOb3Awjqqv5X3Q3hVr4jH/kgrFJpiU0j3a90tnNBplqbj+snzrgZhC9W128z+dtgMifOiMfJg==
|
||||
"@vue/compiler-core@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.35.tgz#f2fc01bd25d859a77b0d9ef5df28f657c1979070"
|
||||
integrity sha512-1Mtmh8ceVUoUsn/PME5oM+Dus648rCeV/fBaZ4ERLFbTHBJXj6QmDPrSn9mfEyPDXE0RYIwyJNn884NdWK+Yiw==
|
||||
dependencies:
|
||||
consolidate "^0.15.1"
|
||||
hash-sum "^1.0.2"
|
||||
lru-cache "^4.1.2"
|
||||
merge-source-map "^1.1.0"
|
||||
postcss "^7.0.36"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
source-map "~0.6.1"
|
||||
vue-template-es2015-compiler "^1.9.0"
|
||||
optionalDependencies:
|
||||
prettier "^1.18.2"
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/shared" "3.2.35"
|
||||
estree-walker "^2.0.2"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-dom@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.35.tgz#11bbcca0d49f9991d64dd8fbf8a0a4453caa571c"
|
||||
integrity sha512-I4bXB9MkRSTJ3gVXRQ4iaYJgABZGew+K/CCBoAh9fdLaeY7A7uUlS5nWGOlICSVfOH0/xk4QlcXeGZYCJkEleA==
|
||||
dependencies:
|
||||
"@vue/compiler-core" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
|
||||
"@vue/compiler-sfc@3.2.35", "@vue/compiler-sfc@^3.1.0":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.35.tgz#1de80f858b33548bc22d166126234435937ebe0c"
|
||||
integrity sha512-2wKQtnuHfwBFc7uV2Cmtms3Cc7u/u6kKJI3F+i0A+9xnuahK39cCMNJKHzI9x93Xai+uft64fDc5JSh8zDQBQA==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.35"
|
||||
"@vue/compiler-dom" "3.2.35"
|
||||
"@vue/compiler-ssr" "3.2.35"
|
||||
"@vue/reactivity-transform" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
postcss "^8.1.10"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-ssr@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.35.tgz#8d7726f95444c02f3301083e2c6c814780fb7b00"
|
||||
integrity sha512-dJyqB8fZbvVQEnWl5VGxkWHTqx0ERnZXXqInFzyOX8FpTEidmQbUSmDrXidea7bZTdeg6ly94kZFGPYXT29mgQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
|
||||
"@vue/reactivity-transform@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.35.tgz#29ba344b0ec9ec7ee75ca6c1a6f349595ca150d8"
|
||||
integrity sha512-VjdQU4nIrgsh1iPqAdYZufWgFqdH9fIl6ttO2PCFlLsrQl7b8BcuawM6moSBLF8damBzSNcqvbvQDBhsI3fyVQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.35.tgz#c66af289f3beda6aba63c264db9c6acd607d1c73"
|
||||
integrity sha512-6j9N9R1SwHVcJas4YqAzwdRS/cgmj3Z9aUert5Mv1jk5B9H9ivN/zot/fgMUbseWXigkkmX60OsfRbz49o8kCw==
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.35"
|
||||
|
||||
"@vue/runtime-core@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.35.tgz#a87bd5214ff31f9dc6542f5c498d4f3543c6ea8f"
|
||||
integrity sha512-P8AeGPRGyIiYdOdvLc/7KR8VSdbUGG8Jxdx6Xlj5okEjyV9IYxeHRIQIoye85K0lZXBH4zuh1syD1mX+oZ0KqQ==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
|
||||
"@vue/runtime-dom@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.35.tgz#004bf6109353e75cf22eb11baf247288c216ef7b"
|
||||
integrity sha512-M5xrVJ/b0KqssjPQMdpwLp3KwzG1Tn2w/IrOptVqGY5c9fEBluIbm18AeO4Fr3YxfeyaPWm1rY8POrEso0UE3w==
|
||||
dependencies:
|
||||
"@vue/runtime-core" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
csstype "^2.6.8"
|
||||
|
||||
"@vue/server-renderer@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.35.tgz#79fb9e2315ec6196b5f2f385ef4ae31cf5599a2f"
|
||||
integrity sha512-ZMF8V+bZ0EIjSB7yzPEmDlxRDOIXj04iqG4Rw/H5rIuBCf0b7rNTleiOldlX5haG++zUq6uiL2AVp/A9uyz+cw==
|
||||
dependencies:
|
||||
"@vue/compiler-ssr" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
|
||||
"@vue/shared@3.2.35":
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.35.tgz#fb60530fa009dc21473386a7639eed833877cb0f"
|
||||
integrity sha512-/sxDqMcy0MsfQ3LQixKYDxIinDYNy1dXTsF2Am0pv0toImWabymFQ8cFmPJnPt+gh5ElKwwn7KzQcDbLHar60A==
|
||||
|
||||
"@webassemblyjs/ast@1.11.1":
|
||||
version "1.11.1"
|
||||
@ -1516,11 +1595,6 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
bluebird@^3.1.1:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||
|
||||
body-parser@1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
@ -1644,7 +1718,7 @@ caniuse-lite@^1.0.30001248:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001251.tgz#6853a606ec50893115db660f82c094d18f096d85"
|
||||
integrity sha512-HOe1r+9VkU4TFmnU70z+r7OLmtR+/chB1rdcJUeQlAinjEeb0cKL20tlAtOagNZhbrtLnCvV19B4FmF1rgzl6A==
|
||||
|
||||
chalk@^2.0.0, chalk@^2.4.2:
|
||||
chalk@^2.0.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
@ -1653,7 +1727,7 @@ chalk@^2.0.0, chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^4.0:
|
||||
chalk@^4.0, chalk@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@ -1787,13 +1861,6 @@ connect-history-api-fallback@^1.6.0:
|
||||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
||||
|
||||
consolidate@^0.15.1:
|
||||
version "0.15.1"
|
||||
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
|
||||
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
|
||||
dependencies:
|
||||
bluebird "^3.1.1"
|
||||
|
||||
content-disposition@0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||
@ -1986,10 +2053,10 @@ csso@^4.2.0:
|
||||
dependencies:
|
||||
css-tree "^1.1.2"
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
||||
csstype@^2.6.8:
|
||||
version "2.6.20"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
|
||||
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
@ -2229,6 +2296,11 @@ estraverse@^5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
|
||||
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
|
||||
|
||||
estree-walker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
@ -2528,15 +2600,10 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hash-sum@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
|
||||
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
|
||||
|
||||
he@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
hash-sum@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a"
|
||||
integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
@ -2948,7 +3015,7 @@ loader-runner@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
|
||||
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
|
||||
|
||||
loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.4.0:
|
||||
loader-utils@^1.1.0, loader-utils@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
||||
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
|
||||
@ -2957,6 +3024,15 @@ loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.4.0:
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
|
||||
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
|
||||
dependencies:
|
||||
big.js "^5.2.2"
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^2.1.2"
|
||||
|
||||
locate-path@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
|
||||
@ -3006,14 +3082,6 @@ lodash@^4.17.14:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
lru-cache@^4.1.2:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
|
||||
dependencies:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
@ -3021,6 +3089,13 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
make-dir@^3.0.2, make-dir@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
@ -3050,13 +3125,6 @@ merge-descriptors@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
merge-source-map@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
|
||||
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
@ -3181,6 +3249,11 @@ nanoid@^3.1.25:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
|
||||
integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==
|
||||
|
||||
nanoid@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||
|
||||
negotiator@0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
@ -3410,6 +3483,11 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
@ -3687,14 +3765,14 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
|
||||
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
|
||||
|
||||
postcss@^7.0.36:
|
||||
version "7.0.36"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb"
|
||||
integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==
|
||||
postcss@^8.1.10:
|
||||
version "8.4.14"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
|
||||
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.1.0"
|
||||
nanoid "^3.3.4"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.2.15, postcss@^8.3.5:
|
||||
version "8.3.7"
|
||||
@ -3705,11 +3783,6 @@ postcss@^8.2.15, postcss@^8.3.5:
|
||||
nanoid "^3.1.25"
|
||||
source-map-js "^0.6.2"
|
||||
|
||||
prettier@^1.18.2:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
@ -3723,11 +3796,6 @@ proxy-addr@~2.0.5:
|
||||
forwarded "0.2.0"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
|
||||
|
||||
punycode@1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||
@ -4133,6 +4201,11 @@ source-map-js@^0.6.2:
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
|
||||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.20"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
|
||||
@ -4146,7 +4219,7 @@ source-map@^0.5.0:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
|
||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
|
||||
source-map@^0.6.0, source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
@ -4156,6 +4229,11 @@ source-map@~0.7.2:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
spdy-transport@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
|
||||
@ -4235,13 +4313,6 @@ supports-color@^5.3.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
@ -4413,47 +4484,25 @@ vendors@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"
|
||||
integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==
|
||||
|
||||
vue-hot-reload-api@^2.3.0:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
|
||||
|
||||
vue-loader@^15.7.0:
|
||||
version "15.9.8"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.8.tgz#4b0f602afaf66a996be1e534fb9609dc4ab10e61"
|
||||
integrity sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==
|
||||
vue-loader@^16.0.0:
|
||||
version "16.8.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087"
|
||||
integrity sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==
|
||||
dependencies:
|
||||
"@vue/component-compiler-utils" "^3.1.0"
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.1.0"
|
||||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
chalk "^4.1.0"
|
||||
hash-sum "^2.0.0"
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
vue-style-loader@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
|
||||
integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
|
||||
vue@^3.1.0:
|
||||
version "3.2.35"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.35.tgz#e0a63a8b2fcc0334a936d484d646e31b571e3f80"
|
||||
integrity sha512-mc/15B0Wjd/4JMMGOcXUQAeXfjyg8MImA2EVZucNdyDPJe1nXhMNbYXOEVPEGfk/mCeyszCzl44dSAhHhQVH8g==
|
||||
dependencies:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-template-compiler@^2.6.10:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763"
|
||||
integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue@^2.6.10:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
|
||||
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
|
||||
"@vue/compiler-dom" "3.2.35"
|
||||
"@vue/compiler-sfc" "3.2.35"
|
||||
"@vue/runtime-dom" "3.2.35"
|
||||
"@vue/server-renderer" "3.2.35"
|
||||
"@vue/shared" "3.2.35"
|
||||
|
||||
watchpack@^2.2.0:
|
||||
version "2.2.0"
|
||||
@ -4623,11 +4672,6 @@ ws@^8.1.0:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.2.tgz#ca684330c6dd6076a737250ed81ac1606cb0a63e"
|
||||
integrity sha512-Q6B6H2oc8QY3llc3cB8kVmQ6pnJWVQbP7Q5algTcIxx7YEpc0oU4NBVHlztA7Ekzfhw2r0rPducMUiCGWKQRzw==
|
||||
|
||||
yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
|
Loading…
Reference in New Issue
Block a user