Partial of new uploader

This commit is contained in:
Kira 2019-05-05 08:49:13 -07:00
parent f29e90f508
commit ae77880b10
12 changed files with 13086 additions and 159 deletions

View File

@ -59,7 +59,7 @@ class UploadsController < ApplicationController
def upload_params def upload_params
permitted_params = %i[ permitted_params = %i[
file direct_url source tag_string rating status parent_id artist_commentary_title file direct_url source tag_string rating parent_id description artist_commentary_title
artist_commentary_desc include_artist_commentary referer_url artist_commentary_desc include_artist_commentary referer_url
md5_confirmation as_pending md5_confirmation as_pending
] ]

View File

@ -41,7 +41,7 @@ class TagAlias < TagRelationship
nil nil
end end
ForumUpdater.new( ForumUpdater.new(
forum_topic, forum_topic,
forum_post: post, forum_post: post,
expected_title: TagAliasRequest.topic_title(antecedent_name, consequent_name), expected_title: TagAliasRequest.topic_title(antecedent_name, consequent_name),
skip_update: !TagRelationship::SUPPORT_HARD_CODED skip_update: !TagRelationship::SUPPORT_HARD_CODED
@ -178,7 +178,7 @@ class TagAlias < TagRelationship
def rename_wiki_and_artist def rename_wiki_and_artist
antecedent_wiki = WikiPage.titled(antecedent_name).first antecedent_wiki = WikiPage.titled(antecedent_name).first
if antecedent_wiki.present? if antecedent_wiki.present?
if WikiPage.titled(consequent_name).blank? if WikiPage.titled(consequent_name).blank?
CurrentUser.scoped(creator, creator_ip_addr) do CurrentUser.scoped(creator, creator_ip_addr) do
antecedent_wiki.update(title: consequent_name, skip_secondary_validations: true) antecedent_wiki.update(title: consequent_name, skip_secondary_validations: true)

View File

@ -201,8 +201,8 @@ class TagImplication < TagRelationship
nil nil
end end
ForumUpdater.new( ForumUpdater.new(
forum_topic, forum_topic,
forum_post: post, forum_post: post,
expected_title: TagImplicationRequest.topic_title(antecedent_name, consequent_name), expected_title: TagImplicationRequest.topic_title(antecedent_name, consequent_name),
skip_update: !TagRelationship::SUPPORT_HARD_CODED skip_update: !TagRelationship::SUPPORT_HARD_CODED
) )

View File

@ -1,153 +1,692 @@
<div id="c-uploads"> <%= render partial: "uploads/upload_partials/css" %>
<div id="a-new"> <%= render partial: "uploads/upload_partials/tag_preview" %>
<h1>Upload</h1> <%= render partial: "uploads/upload_partials/source" %>
<%= render partial: "uploads/upload_partials/related" %>
<% if CurrentUser.can_upload? %> <%= render partial: "uploads/upload_partials/uploader" %>
<div id="upload-guide-notice"> <div id="post-add">
<%= format_text(@upload_notice_wiki.try(&:body)) %> <div style="margin-bottom: 1rem;" class="section">
</div> <h2>Before uploading, read
the <%= link_to "how to upload guide", wiki_pages_path(id: "howto:upload") %>.</h2>
<% unless CurrentUser.can_upload_free? %> <p>Make sure you're not posting something on
<p>Upload limit: <strong><%= CurrentUser.user.presenter.upload_limit(self) %></strong>.</p> the <%= link_to "Avoid Posting List", help_pages_path(id: 'avoid_posting') %><br>
<% end %> Review the <%= link_to "Uploading Guidelines", help_pages_path(id: "uploading_guidelines") %>
<br>
<%= render "image" %> Unsure what to tag your post
<%= render "related_posts", source: @source %> with? <%= link_to "Tagging Checklist", help_pages_path(id: "tagging_checklist") %></p>
<%= render "sources/info" %> </div>
<%# if current_user.is_member_or_lower? && (current_user.upload_limit <= 5 || current_user.post_upload_throttle <= 5) %>
<div id="client-errors" class="error-messages ui-state-error ui-corner-all" style="display:none"></div> <!-- <div id="post-uploads-remaining" class="section<%# if [current_user.upload_limit, current_user.post_upload_throttle].min <= 0 %> sect_red<%# end %>" style="width:640px;">-->
<!-- <p>-->
<%= form_for(@upload, :html => {:multipart => true, :class => "simple_form", :id => "form"}) do |f| %> <!-- You currently have <span class="post-uploads-remaining-count"><%#= current_user.upload_limit %></span> upload<%#= current_user.upload_limit!=1?"s":"" %> remaining.-->
<%= f.hidden_field :md5_confirmation %>
<%= f.hidden_field :referer_url, value: params[:ref] %> <%# if current_user.pending_flagged_posts.count > 0 %>
<!-- This number will go up as some of your <%#= link_to "<span class='post-uploads-remaining-count'>#{current_user.pending_flagged_posts.count}</span> pending/flagged posts", action: "index",tags: "user:#{current_user.name} status:pending" %> are approved.-->
<% if CurrentUser.can_upload_free? %> <%# end %>
<div class="input"> <!-- </p>-->
<label for="upload_as_pending"> <!-- You have <span class="post-uploads-remaining-count"><%#= current_user.post_upload_throttle %></span> uploads remaining this hour.-->
<%= f.check_box :as_pending, :checked => params[:as_pending].present? %> <!-- See <%#= link_to "here", controller: "user", action: "upload_limit" %> for more details.-->
Upload for approval <!-- </div>-->
</label> <%# elsif current_user.post_upload_throttle <= 5 %>
</div> <!-- <div id="post-uploads-remaining" class="section<%# if current_user.post_upload_throttle <= 0 %> sect_red<%# end %>" style="width:640px;">-->
<% end %> <!-- You have <span class="post-uploads-remaining-count"><%#= current_user.post_upload_throttle %></span> uploads remaining this hour.-->
<!-- See <%#= link_to "here", controller: "user", action: "upload_limit" %> for more details.-->
<div class="input"> <!-- </div>-->
<%= f.label :file %> <%# end %>
<%= f.file_field :file, :size => 50 %> <post-creator :safe="<%= CurrentUser.safe_mode? %>"></post-creator>
</div> </div>
<!--<script type="text/javascript">-->
<div class="input"> <!-- var userUploadTags = <%#= current_user.uploaded_tags_with_types.to_json %>;-->
<%= f.label :direct_url %> <!-- var userRecentTags = <%#= current_user.recent_tags_with_types.to_json %>;-->
<%= f.text_field :direct_url, size: 50, value: params[:url] %> <!-- var userArtistTags = <%#= @artists.to_json %>;-->
<%= button_tag "Similar", :id => "similar-button", :type => "button", :class => "ui-button ui-widget ui-corner-all sub" %> <!--</script>-->
<div id="whitelist-warning"></div> <script>
<span class="hint">You can enter a URL to have <%= Danbooru.config.app_name %> automatically download and process it</span> var userUploadTags = [];
</div> var userRecentTags = [];
var userArtistTags = [];
<div class="input"> </script>
<%= f.label :source, "Sources" %> <script src="/vendor/vue.js"></script>
<%= f.text_area :source, size: "60x5", value: params[:url] %> <script type="text/javascript">
</div> const thumbURLs = [
"/images/notfound-preview.png",
<div class="input"> "/images/download-preview.png",
<%= f.label :rating %> "/images/webm-preview.png",
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
<fieldset class="ratings"> ];
<%= f.radio_button :rating, :e, :checked => (params[:rating] == "e") %> const thumbs = {
<%= f.label :rating_e, "Explicit", :title => "Hardcore porn, visible genitals" %> webm: "/images/webm-preview.png",
flash: "/images/download-preview.png",
<%= f.radio_button :rating, :q, :checked => (params[:rating] == "q") %> notfound: "/images/notfound-preview.png",
<%= f.label :rating_q, "Questionable", :title => "Nudity, anything erotic" %> none: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
};
<%= f.radio_button :rating, :s, :checked => (params[:rating] == "s") %> const artist_checks = [
<%= f.label :rating_s, "Safe", :title => "Everything else" %> {name: 'Unknown Artist'},
</fieldset> {name: 'Anonymous Artist'}];
</div>
const sex_checks = [
<div class="input"> {name: 'Male'},
<%= f.label :parent_id, "Parent ID" %> {name: 'Female'},
<%= f.text_field :parent_id, :value => params[:parent_id] %> {name: 'Cuntboy'},
</div> {name: 'Dickgirl'},
{name: 'Hermaphrodite', tag: 'herm'},
<div class="input"> {name: 'Male-Herm', tag: 'maleherm'},
<strong>Commentary</strong> {name: 'Ambiguous', tag: 'ambiguous_gender'}];
<a href="#" id="toggle-artist-commentary">show »</a>
const pairing_checks = [
<div class="artist-commentary" style="display: none;"> {name: 'Male/Male'},
<div class="input"> {name: 'Male/Female'},
<%= f.label :artist_commentary_title, "Title" %> {name: 'Female/Female'},
<%= f.text_field :artist_commentary_title, :value => params[:artist_commentary_title] %> {name: 'Intersex/Male'},
</div> {name: 'Intersex/Female'},
{name: 'Intersex/Intersex'}
<div class="input"> ];
<%= f.label :artist_commentary_desc, "Description" %>
<%= f.text_area :artist_commentary_desc, :size => "60x5", :value => params[:artist_commentary_desc] %> const char_count_checks = [
</div> {name: 'Solo'},
{name: 'Duo'},
<div class="input"> {name: 'Group'},
<label for="upload_include_artist_commentary"> {name: 'Zero Pictured'}];
<%= f.check_box :include_artist_commentary, :checked => params[:include_artist_commentary].present? %>
Include Commentary const body_type_checks = [
</label> {name: 'Anthro'},
</div> {name: 'Feral'},
</div> {name: 'Humanoid'},
</div> {name: 'Human'},
{name: 'Taur'}];
<div class="input" id="tags-container">
<div class="header"> function updatePreviewDims(e) {
<%= f.label :tag_string, "Tags" %> var img = e.target;
if (thumbURLs.filter(function (x) {
<span class="options"> return img.src.indexOf(x) !== -1;
<i id="face" class="fas"></i> }).length !== 0)
<span class="count"></span> return;
<a href="javascript:void(0)"><i id="open-edit-dialog" class="fas fa-arrows-alt" title="detach" data-shortcut="shift+e"></i></a> this.previewHeight = img.naturalHeight;
</span> this.previewWidth = img.naturalWidth;
</div> this.overDims = (img.naturalHeight > 15000 || img.naturalWidth > 15000);
updateDimensionTag.call(this);
<div> }
<%= f.text_area :tag_string, :size => "60x5", :spellcheck => false, :"data-autocomplete" => "tag-edit", :"data-shortcut" => "e", :value => params[:tag_string] %>
</div> function previewError() {
<%= render "related_tags/buttons" %> this.previewWidth = this.previewHeight = 0;
</div> this.overDims = false;
if (this.uploadURL === '' && !this.$refs['post_file']) {
<div class="input"> this.previewURL = thumbs.none;
<%= submit_tag "Submit", :id => "submit-button", :class => "large ui-button ui-widget ui-corner-all", data: { disable_with: false } %> } else {
</div> this.previewURL = thumbs.notfound;
}
<%= render "related_tags/container" %> }
<% end %>
<% else %> function updatePreviewFile() {
<h2 style="margin-bottom: 1em;">You <%= CurrentUser.user.upload_limited_reason %></h2> var self = this;
<% end %> var reader = new FileReader();
</div> var file = this.$refs['post_file'].files[0];
</div> this.previewHeight = 0;
this.previewWidth = 0;
<% content_for(:page_title) do %> reader.onload = function (e) {
Upload - <%= Danbooru.config.app_name %> var src = e.target.result;
<% end %>
if (file.type.match('video/webm'))
<% content_for(:html_header) do %> src = thumbs.webm;
<script async src="/vendor/spark-md5-3.0.min.js" integrity="sha384-5VJv9Bpdgn6Hi1U9X+oyrPZZ6vIihhKUi6Dc3DoCBGph3XbthcAH6va41dxo1KUO" crossorigin="anonymous"></script> else if (file.type.match('application/x-shockwave-flash'))
<script> src = thumbs.flash;
$(function() { self.previewURL = src;
var enabled = true; };
var maxFilesize = <%= Danbooru.config.max_file_size.to_json %> / (1024 * 1024); reader.readAsDataURL(file);
if (!window.FileReader) { this.disableURLUpload = true;
enabled = false; }
}
function updatePreviewURL() {
// this.on("addedfile", function(file) { var self = this;
// var reader = new FileReader(); if (this.uploadURL.length === 0 || (this.$refs['post_file'] && this.$refs['post_file'].files.length > 0)) {
// reader.addEventListener("loadend", function() { this.disableFileUpload = false;
// var buf = new SparkMD5.ArrayBuffer(); this.oldDomain = '';
// buf.append(this.result); return;
// var hash = buf.end(); }
// $("#upload_md5_confirmation").val(hash); this.disableFileUpload = true;
// }); var domain = $j("<a>").prop("href", this.uploadURL).prop("hostname");
// reader.readAsArrayBuffer(file);
// }); if (domain && domain != this.oldDomain) {
}); $j.getJSON("/upload_whitelist/is_whitelisted", {url: this.uploadURL}, function (data) {
</script> if (data.domain)
<% end %> self.whitelistWarning(data.is_whitelisted, data.domain);
});
<%= render "uploads/secondary_links" %> }
this.oldDomain = domain;
var src = thumbs.none;
if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(jpg|jpeg|gif|png)/))
src = this.uploadURL;
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(swf)$/))
src = thumbs.flash;
else if (this.uploadURL.match(/^(https?\:\/\/|www).*?\.(webm)$/))
src = thumbs.webm;
this.previewURL = src;
}
function updateDimensionTag() {
var self = this;
if (!(self.previewHeight || self.previewWidth))
return;
var otherTags = ['low_res', 'hi_res', 'superabsurd_res', 'absurd_res'];
var ourTag = function (h, w) {
if (!(h && w)) {
return null;
}
if ((h <= 500) && (w <= 500))
return 'low_res';
if ((h >= 10000) || (w >= 10000))
return 'superabsurb_res';
if ((h >= 2400) || (w >= 3200))
return 'absurd_res';
if ((h >= 1200) || (w >= 1600))
return 'hi_res';
return null;
}(self.previewHeight, self.previewWidth);
var tagIdx = otherTags.indexOf(ourTag);
if (tagIdx > 0)
otherTags.splice(tagIdx, 1);
if (ourTag)
self.pushTag(ourTag, true);
for(var i = 0; i < otherTags.length; i++) {
self.pushTag(otherTags[i], false);
}
}
function updatePreview() {
if (this.$refs['post_file'] && this.$refs['post_file'].files[0])
updatePreviewFile.call(this);
else
updatePreviewURL.call(this);
}
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: /\d+\.media\.tumblr\.com/gi}, // Tumblr broke raws.
{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=/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.previewURL = thumbs.none;
this.previewHeight = this.previewWidth = 0;
this.updatePreview();
}
var source = Vue.extend({
template: '#source-template',
props: ['value', 'index', 'last'],
data: function () {
return {
backendValue: this.value
};
},
computed: {
'realValue': {
get: function () {
return this.backendValue;
},
set: function (v) {
this.backendValue = v;
this.$emit('input', v);
}
}
},
methods: {
add: function () {
this.$emit('add');
},
remove: function () {
this.$emit('delete');
}
},
watch: {
value: function (v) {
this.backendValue = v;
}
}
});
var checkbox = Vue.extend({
template: '#checkbox-template',
props: ['check', 'checks'],
computed: {
value: {
get: function () {
if (this.checks[this.tagName] === undefined)
return false;
return this.checks[this.tagName];
},
set: function (v) {
this.$emit('set', this.tagName, v);
}
},
tagName: function () {
return this.check.tag || this.check.name.toLowerCase().replace(/ /g, '_');
}
}
});
var tagSorter = function (a, b) {
return a[0] > b[0] ? 1 : -1;
};
var relatedTags = Vue.extend({
template: '#related-tags',
props: ['tags', 'related', 'loading'],
data: function () {
return {
uploaded: (userUploadTags || []),
recent: (userRecentTags || []).sort(tagSorter),
artists: (userArtistTags || []).sort(tagSorter)
};
},
methods: {
toggle: function (tag) {
this.$emit('tag-active', tag[0], !this.tagActive(tag));
},
tagLink: function (tag) {
return '/post/index?tags=' + encodeURIComponent(tag[0]);
},
tagActive: function (tag) {
return this.tags.indexOf(tag[0]) !== -1;
},
tagClasses: function (tag) {
var classes = {'tag-active': this.tagActive(tag)};
classes['tag-type-' + tag[2]] = true;
return classes;
},
splitTags: function (tags) {
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(tags, 15);
}
},
computed: {
tagGroups: {
get: function () {
var groups = [];
if (this.uploaded && this.uploaded.length) {
groups.push({
title: "Quick Tags",
tags: this.uploaded
});
}
if (this.recent && this.recent.length) {
groups.push({
title: "Recent",
tags: this.recent
});
}
if (this.artists && this.artists.length) {
groups.push({
title: "Artists",
tags: this.artists
});
}
if (this.related && this.related.length) {
for (var i = 0; i < this.related.length; i++) {
groups.push(this.related[i]);
}
}
if (this.loading) {
groups.push({title: 'Loading Related Tags', tags: [['', '', '']]});
}
return groups;
}
}
}
});
var tagPreviewTag = Vue.extend({
functional: true,
props: ['tag'],
render: function (h, ctx) {
var tag = ctx.props.tag;
switch (tag.type) {
default:
case 'tag':
return h('a', {
staticClass: 'tag-preview tag-type-' + tag.tagType
}, tag.a);
case 'alias':
return h('span', {staticClass: 'tag-preview tag-preview-alias'}, [
h('del', undefined, [
h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.a)
]), ' → ', h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.b)
]);
case 'implication':
return h('span', {staticClass: 'tag-preview tag-preview-implication'}, [
h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.a), ' ⇐ ', h('a', {staticClass: 'tag-type-' + tag.tagType}, tag.b)
]);
}
}
});
var tagPreview = Vue.extend({
template: '#tag-preview',
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);
}
}
});
var editor = Vue.extend({
template: '#post-creator',
props: ['safe'],
components: {
'image-source': source,
'image-checkbox': checkbox,
'related-tags': relatedTags,
'tag-preview': tagPreview
},
data: function () {
var allChecks = {};
var addChecks = function (check) {
if (typeof check['tag'] !== "undefined") {
allChecks[check.tag] = true;
return
}
allChecks[check.name.toLowerCase().replace(' ', '_')] = true;
};
artist_checks.forEach(addChecks);
sex_checks.forEach(addChecks);
pairing_checks.forEach(addChecks);
char_count_checks.forEach(addChecks);
body_type_checks.forEach(addChecks);
return {
showErrors: false,
whitelist: {
visible: false,
allowed: false,
domain: ''
},
submitting: false,
disableFileUpload: false,
disableURLUpload: false,
previewHeight: 0,
previewWidth: 0,
overDims: false,
uploadURL: '',
previewURL: thumbs.none,
oldDomain: '',
noSource: false,
sources: [''],
normalMode: true,
checkboxes: {
artist: artist_checks,
sex: sex_checks,
pairing: pairing_checks,
count: char_count_checks,
body: body_type_checks,
selected: {},
all: allChecks
},
tagEntries: {
character: '',
sex: '',
bodyType: '',
theme: '',
other: ''
},
preview: {
loading: false,
show: false,
tags: []
},
relatedTags: [],
loadingRelated: false,
parentID: '',
description: '',
rating: ''
};
},
methods: {
updatePreview: updatePreview,
updatePreviewDims: updatePreviewDims,
previewError: previewError,
clearFile: clearFileUpload,
whitelistWarning: function (allowed, domain) {
this.whitelist.allowed = allowed;
this.whitelist.domain = domain;
this.whitelist.visible = true;
},
removeSource: function (i) {
this.sources.splice(i, 1);
},
addSource: function () {
if (this.sources.length < 5)
this.sources.push('');
},
setCheck: function (tag, value) {
Vue.set(this.checkboxes.selected, tag, value);
},
submit: function () {
this.showErrors = true;
if (this.preventUpload || this.submitting)
return;
var self = this;
this.submitting = true;
var data = new FormData();
var post_file = this.$refs['post_file'];
if (post_file && post_file.files && post_file.files.length) {
data.append('post[file]', this.$refs['post_file'].files[0]);
} else {
data.append('post[direct_url]', this.uploadURL);
}
data.append('upload[tag_string]', this.tags);
data.append('upload[rating]', this.rating);
data.append('upload[source]', this.sources.join('\n'));
// data.append('upload[description]', this.description);
data.append('upload[parent_id]', this.parentID);
jQuery.ajax('/uploads.json', {
contentType: false,
processData: false,
method: 'POST',
type: 'POST',
data: data,
success: function (data) {
self.submitting = false;
notice('Post uploaded successfully.');
location.assign(data.location);
},
error: function (data) {
self.submitting = false;
try {
var data2 = JSON.parse(data.responseText);
if (data2 && data2.location) {
error('Error: Post already exists. <a href="' + data2.location + '">View it.</a>');
} else {
error('Error: ' + data2.reason);
}
} catch (e) {
error('Error: Unknown error! ' + data);
}
}
});
},
pushTag: function (tag, add) {
this.preview.show = false;
var isCheck = typeof this.checkboxes.all[tag] !== "undefined";
// In advanced mode we need to push these into the tags area because there are no checkboxes or other
// tag fields so we can't see them otherwise.
if (isCheck && this.normalMode) {
this.setCheck(tag, add);
return;
}
var tags = this.tagEntries.other ? this.tagEntries.other.trim().split(' ') : [];
var tagIdx = tags.indexOf(tag);
if (add) {
if (tagIdx === -1)
tags.push(tag);
} else {
if (tagIdx === -1)
return;
tags.splice(tagIdx, 1);
}
this.tagEntries.other = tags.join(' ') + ' ';
},
previewFinalTags: function () {
if (this.preview.loading)
return;
if (this.preview.show) {
this.preview.show = false;
return;
}
this.preview.loading = true;
this.preview.show = true;
this.preview.tags = [];
var self = this;
var data = {tags: this.tags};
jQuery.ajax("/tag/preview.json", {
method: 'POST',
type: 'POST',
data: data,
success: function (result) {
self.preview.loading = false;
self.preview.tags = result;
},
error: function (result) {
self.preview.loading = false;
self.preview.tags = [];
self.preview.show = false;
error('Error loading tag preview ' + result);
}
})
},
findRelated: function (type) {
var self = this;
var convertResponse = function (respData) {
var sortedRelated = [];
for (var key in respData) {
if (!respData.hasOwnProperty(key)) {
continue;
}
sortedRelated.push({title: 'Related: ' + key, tags: respData[key].sort(tagSorter)});
}
return sortedRelated;
};
var getSelectedTags = function () {
var field = self.$refs['otherTags'];
if (!field.hasOwnProperty('selectionStart'))
return null;
var length = field.selectionEnd - field.selectionStart;
if (length)
return field.value.substr(field.selectionStart, length);
return null;
};
this.loadingRelated = true;
this.relatedTags = [];
var selectedTags = getSelectedTags();
var params = selectedTags ? {tags: selectedTags} : {tags: this.tags};
if (type)
params['type'] = type;
new Ajax.Request("/tag/related.json", {
method: 'POST',
parameters: params,
onComplete: function (resp) {
var respJ = resp.responseJSON;
self.relatedTags = convertResponse(respJ);
self.loadingRelated = false;
},
onError: function () {
self.loadingRelated = false;
}
});
}
},
computed: {
tags: function () {
var self = this;
var checked = Object.keys(this.checkboxes.selected).filter(function (x) {
return self.checkboxes.selected[x] === true;
});
return checked.concat([this.tagEntries.other, this.tagEntries.sex, this.tagEntries.bodyType,
this.tagEntries.theme, this.tagEntries.character]).join(' ').replace(',', ' ').trim().replace(/ +/g, ' ');
},
tagsArray: function () {
return this.tags.toLowerCase().split(' ');
},
previewDimensions: function () {
if (this.previewWidth && this.previewHeight)
return this.previewHeight + '×' + this.previewWidth;
return '';
},
directURLProblem: function () {
return directURLCheck(this.uploadURL);
},
badDirectURL: function () {
return !!this.directURLProblem;
},
sourceWarning: function () {
var validSourceCount = this.sources.filter(function (i) {
return i.length > 0;
}).length;
return !this.noSource && (validSourceCount === 0);
},
tagCount: function () {
return this.tags.split(' ').filter(function (x) {
return x;
}).length;
},
notEnoughTags: function () {
return this.tagCount < 4;
},
invalidRating: function () {
return !this.rating;
},
preventUpload: function () {
return this.sourceWarning || this.badDirectURL || this.notEnoughTags
|| this.invalidRating;
}
}
});
var main = new Vue({
el: '#post-add',
components: {
'post-creator': editor
}
});
</script>

View File

@ -0,0 +1,178 @@
<style>
.toggle-button {
box-sizing: border-box;
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: center;
user-select: none;
padding: .175em .5rem;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.25rem;
margin-right: 5px;
}
.toggle-button.active {
background-color: #FDF5D9;
}
.flex-grid-outer {
display: flex;
padding: 10px 0;
}
.upload_preview_container.in-editor {
display: none;
}
.upload_preview_container.in-sidebar {
position: sticky;
top: 20px;
}
.flex-grid {
display: flex;
padding: 10px 0;
}
.flex-wrap {
display: flex;
flex-wrap: wrap;
}
.col {
flex: 1 0 0;
margin-right: 5px;
}
.col2 {
flex: 2 1 0;
}
.border-bottom {
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
}
.section-label {
white-space: normal;
}
.come-together-now {
padding-bottom: 0;
}
.over-me {
padding-top: 0;
}
.related-section {
display: flex;
flex-direction: row;
flex: 0 1 10%;
padding: 5px 10px;
}
.related-items {
display: flex;
flex-direction: column;
margin: 0 -5px;
padding: 0 5px;
}
.related-item {
padding: 0 5px;
max-width: 140px;
word-wrap: break-word;
overflow-wrap: break-word;
}
.related-title {
padding: 0 5px;
font-weight: bold;
}
.tag-active {
background: rgb(0, 111, 250);
color: white;
}
.tag-preview {
border: 1px solid rgba(0, 0, 0, 0.15);
background: rgba(1, 1, 1, 0.15);
border-radius: 2px;
padding: 3px;
margin-right: 5px;
box-sizing: border-box;
}
.tag-preview-alias {
background-color: rgba(150, 0, 0, 0.25);
}
.tag-preview-implication {
background-color: rgba(0, 150, 0, 0.25);
}
.tag-textarea {
display: inline-block; /* Why were we even unsetting this? It breaks EVERYTHING. */
font-size: 1rem;
width: 100%;
resize: vertical;
}
/* Need to override this so it shows up at all. */
#whitelist-warning {
display: block;
float: none;
}
@media only screen and (orientation: portrait), (max-width: 1100px) {
#preview-sidebar {
display: none;
}
.upload_preview_container.in-editor {
display: flex;
flex-direction: column-reverse;
}
.upload_preview_container.in-sidebar {
display: none;
}
.upload_preview_dims {
text-align: center;
}
.upload_preview_img {
display: block;
margin-left: auto;
margin-right: auto;
max-height: 500px;
}
.below-upload > .upload_preview_img {
max-height: 150px;
}
.related-section {
flex: 0 1 50%;
}
.flex-grid {
flex-direction: column;
}
input {
max-width: 90%;
}
}
.the_secret_switch {
height: 10px;
width: 100%;
}
</style>

View File

@ -0,0 +1,12 @@
<template id="related-tags">
<div class="related-tags flex-wrap">
<div class="related-section" v-for="group in tagGroups" :key="group.title">
<div class="related-items" v-for="tags, i in splitTags(group.tags)" :key="i">
<div class="related-title" v-if="i === 0">{{group.title}}</div>
<div class="related-item" v-for="tag in tags" :key="tag[0]">
<a :class="tagClasses(tag)" :href="tagLink(tag)" @click.prevent="toggle(tag)">{{tag[0]}}</a>
</div>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,7 @@
<template id="source-template">
<div>
<input type="text" size="50" v-model="realValue" @keyup.enter="add"/>
<button @click="remove" v-if="index !== 0">-</button>
<button @click="add" v-if="last && index < 4">+</button>
</div>
</template>

View File

@ -0,0 +1,13 @@
<template id="tag-preview">
<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>

View File

@ -0,0 +1,228 @@
<template id="checkbox-template">
<button class="toggle-button" :class="{active: value}" @click="value = !value">{{check.name}}</button>
</template>
<template id="post-creator">
<div class="flex-grid-outer">
<div class="col box-section" style="flex: 2 0 0;">
<div class="the_secret_switch" @click="normalMode = !normalMode"></div>
<% unless @upload.new_record? %>
<div class="box-section" style="margin-bottom: 1rem;">
<p>This post was already uploaded
(<%= link_to "post ##{@upload.id}", {action: "show", id: @upload.id}, target: "_blank" %>).</p>
</div>
<% end %>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label" for="post_file">Image</label>
</div>
<div class="col2">
<div v-if="!disableFileUpload">
<label>File:
<input type="file" ref="post_file" @change="updatePreview" :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/show/howto:sites_and_sources">the sourcing guide</a>.
</div>
<label>{{!disableFileUpload ? '(or) ' : '' }}URL:
<input type="text" size="50" v-model="uploadURL" @keyup="updatePreview" :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. (<a href='/upload_whitelist'>View whitelisted domains</a>)</span>
</div>
</div>
</div>
</div>
<div class="box-section upload_preview_container in-editor below-upload">
<div class="upload_preview_dims">{{ previewDimensions }}</div>
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;"/>
</div>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label" for="post_sources">Sources</label>
<div>You should include: A link to the artists page where this was obtained, and a link to the submission page
where this image was obtained. No available source should ONLY be used if the content has never been posted
online anywhere else.
</div>
</div>
<div class="col2">
<div class="box-section sect_red" v-show="showErrors && sourceWarning">A source must be provided or you must
select that there
is no available source.
</div>
<div v-if="!noSource">
<image-source :last="i === (sources.length-1)" :index="i" v-model="sources[i]" v-for="s, i in sources"
@delete="removeSource(i)" @add="addSource" :key="i"></image-source>
</div>
<div>
<label class="section-label"><input type="checkbox" id="no_source" v-model="noSource"/> No available source
/ I am the source.</label>
</div>
</div>
</div>
<template v-if="normalMode">
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label" for="names">Artists</label>
</div>
<div class="col2">
<div>
<textarea class="tag-textarea" v-model="tagEntries.character" id="post_characters" rows="2"
placeholder="Ex: artist_name etc."></textarea>
</div>
<div class="flex-wrap">
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.artist" @set="setCheck"
:key="check.name"></image-checkbox>
</div>
</div>
</div>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label" for="post_sex_tags">Characters</label>
<div>Select (and write in) all that apply. Character sex is based only on what is visible in the image.
Outside information or other images should not be used when deciding what tags are used.
</div>
</div>
<div class="col2">
<div class="flex-wrap">
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.sex" @set="setCheck"
:key="check.name"></image-checkbox>
</div>
<hr>
<div class="flex-wrap">
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.count" @set="setCheck"
:key="check.name"></image-checkbox>
</div>
<hr>
<div class="flex-wrap">
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.pairing" @set="setCheck"
:key="check.name"></image-checkbox>
</div>
<textarea class="tag-textarea" rows="2" v-model="tagEntries.sex" id="post_sexes"
placeholder="Ex: character_name solo_focus etc."></textarea>
</div>
</div>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label">Body Types and Species</label>
</div>
<div class="col2">
<div class="flex-wrap">
<image-checkbox :check="check" :checks="checkboxes.selected" v-for="check in checkboxes.body" @set="setCheck"
:key="check.name"></image-checkbox>
</div>
<textarea class="tag-textarea" rows="2" v-model="tagEntries.bodyType" id="post_bodyTypes"
placeholder="Ex: bear dragon hyena rat newt etc."></textarea>
</div>
</div>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label">Contentious Content</label>
<div>
These allow users to find or blacklist content with ease. Make sure that you are tagging these upon
initial upload.
</div>
</div>
<div class="col2">
<textarea class="tag-textarea" v-model="tagEntries.theme" id="post_themes" rows="2"
placeholder="Ex: cub young gore scat watersports diaper my_little_pony vore not_furry rape etc."></textarea>
</div>
</div>
</template>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label" for="post_rating_questionable">Rating</label>
<div>Explicit tags include sex, pussy, penis, masturbation, fellatio, etc.
(<%= link_to "help", help_pages_path(title: 'ratings'), target: "_blank" %>)
</div>
</div>
<div class="col2">
<div class="box-section sect_red" v-if="showErrors && invalidRating">
You must select an appropriate rating for this image.
</div>
<div>
<template v-if="!safe">
<button class="toggle-button" :class="{active: rating==='e'}" @click="rating = 'e'">Explicit</button>
<button class="toggle-button" :class="{active: rating==='q'}" @click="rating = 'q'">Questionable</button>
</template>
<button class="toggle-button" :class="{active: rating==='s'}" @click="rating = 's'">Safe</button>
</div>
</div>
</div>
<div class="flex-grid come-together-now">
<div class="col">
<label class="section-label" for="post_tags">Other Tags</label>
<div>
Separate tags with spaces. (<%= link_to "help", help_pages_path(title: 'tags'), target: "_blank" %>)
</div>
</div>
<div class="col2">
<div class="box-section upload_preview_container in-editor">
<div class="upload_preview_dims">{{ previewDimensions }}</div>
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;"/>
</div>
<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.
</div>
<div v-show="!preview.show">
<textarea class="tag-textarea" id="post_tags" v-model="tagEntries.other" rows="5" ref="otherTags"></textarea>
</div>
<div v-show="preview.show">
<tag-preview :tags="preview.tags" :loading="preview.loading" @close="previewFinalTags"></tag-preview>
</div>
<div class="related-tag-functions">
<a href="#" @click.prevent="findRelated">Related Tags</a> |
<a href="#" @click.prevent="findRelated('artist')">Related Artists</a> |
<a href="#" @click.prevent="findRelated('char')">Related Characters</a> |
<a href="#" @click.prevent="findRelated('copyright')">Related Copyrights</a> |
<a href="#" @click.prevent="previewFinalTags">Preview Final Tags</a>
</div>
</div>
</div>
<div class="flex-grid border-bottom over-me">
<related-tags :tags="tagsArray" :related="relatedTags" :loading="loadingRelated" @tag-active="pushTag"></related-tags>
</div>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label">Parent Post ID</label>
</div>
<div class="col2">
<input type="number" v-model.number="parentID" placeholder="Ex. 12345"/>
</div>
</div>
<div class="flex-grid border-bottom">
<div class="col">
<label class="section-label" for="post_description">Description</label>
</div>
<div class="col2">
<textarea class="tag-textarea" id="post_description" v-model="description" rows="5"></textarea>
</div>
</div>
<div class="flex-grid">
<div class="col"></div>
<div class="col2">
<div class="box-section sect_red" v-show="preventUpload && showErrors">
Unmet requirements above prevent the submission of the post.
</div>
<div class="box-section sect_green" v-show="submitting">
Submitting your post, please wait.
</div>
<button @click="submit" :disabled="(showErrors && preventUpload) || submitting" accesskey="s">{{ submitting ? 'Uploading...' :
'Upload' }}
</button>
</div>
</div>
</div>
<div id="preview-sidebar" class="col box-section" style="margin-left: 10px; padding: 10px;">
<div class="upload_preview_container in-sidebar">
<div class="upload_preview_dims">{{ previewDimensions }}</div>
<img class="upload_preview_img" :src="previewURL" style="max-width: 100%;" @load="updatePreviewDims" @error="previewError"/>
</div>
</div>
</div>
</template>

View File

@ -10,7 +10,8 @@
"qtip2": "^3.0.3", "qtip2": "^3.0.3",
"rails-erb-loader": "^5.4.2", "rails-erb-loader": "^5.4.2",
"script-loader": "^0.7.2", "script-loader": "^0.7.2",
"webpack-cli": "^3.0.8" "webpack-cli": "^3.0.8",
"vue": "^2.6.10"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^5.3.0", "eslint": "^5.3.0",

11944
public/vendor/vue.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6682,6 +6682,11 @@ vm-browserify@0.0.4:
dependencies: dependencies:
indexof "0.0.1" indexof "0.0.1"
vue@^2.6.10:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
watchpack@^1.4.0: watchpack@^1.4.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"