Start work on JS side

This commit is contained in:
Kane York 2015-06-25 13:26:31 -07:00
parent 44bbc78160
commit 01b38207d9
30 changed files with 142 additions and 329 deletions

View File

@ -1,3 +0,0 @@
{{#if canEditTags}}
{{tag-chooser tags=model.tags tabIndex="4"}}
{{/if}}

View File

@ -1,3 +0,0 @@
{{#if canEditTags}}
{{tag-chooser tags=buffered.tags}}
{{/if}}

View File

@ -1,13 +0,0 @@
{{#if tags_changes}}
<div class='row'>
{{i18n "tagging.changed"}}
{{#each t in previousTagChanges}}
{{discourse-tag tagId=t}}
{{/each}}
&rarr;
&nbsp;
{{#each t in currentTagChanges}}
{{discourse-tag tagId=t}}
{{/each}}
</div>
{{/if}}

View File

@ -1,3 +0,0 @@
<li>
{{#link-to 'tags'}}{{i18n "tagging.tags"}}{{/link-to}}
</li>

View File

@ -1,3 +0,0 @@
{{#each t in topic.tags}}
{{discourse-tag tagId=t}}
{{/each}}

View File

@ -0,0 +1,3 @@
import buildPluginAdapter from 'discourse/adapters/build-plugin';
export default buildPluginAdapter('explorer');

View File

@ -1,7 +0,0 @@
import RESTAdapter from 'discourse/adapters/rest';
export default RESTAdapter.extend({
pathFor(type, id) {
return "/tags/" + id + "/notifications";
}
});

View File

@ -1,33 +0,0 @@
export default Ember.Component.extend({
tagName: 'a',
classNameBindings: [':discourse-tag'],
attributeBindings: ['href', 'style'],
href: function() {
return "/tags/" + this.get('tagId');
}.property('tagId'),
style: function() {
const count = parseFloat(this.get('count')),
minCount = parseFloat(this.get('minCount')),
maxCount = parseFloat(this.get('maxCount'));
if (count && maxCount && minCount) {
let ratio = (count - minCount) / maxCount;
if (ratio) {
ratio = ratio + 1.0;
return "font-size: " + ratio + "em";
}
}
}.property('count', 'scaleTo'),
render(buffer) {
buffer.push(Handlebars.Utils.escapeExpression(this.get('tagId')));
},
click(e) {
e.preventDefault();
Discourse.URL.routeTo(this.get('href'));
return true;
}
});

View File

@ -1,88 +0,0 @@
function formatTag(t) {
const ret = "<a href class='discourse-tag'>" + Handlebars.Utils.escapeExpression(t.id) + "</a>";
return (t.count) ? ret + " <span class='discourse-tag-count'>x" + t.count + "</span>" : ret;
}
export default Ember.TextField.extend({
classNameBindings: [':tag-chooser'],
attributeBindings: ['tabIndex'],
_setupTags: function() {
const tags = this.get('tags') || [];
this.set('value', tags.join(", "));
}.on('init'),
_valueChanged: function() {
const tags = this.get('value').split(',').map(v => v.trim()).reject(v => v.length === 0).uniq();
this.set('tags', tags);
}.observes('value'),
_initializeTags: function() {
const site = this.site,
filterRegexp = new RegExp(this.site.tags_filter_regexp, "g");
this.$().select2({
tags: true,
placeholder: I18n.t('tagging.choose_for_topic'),
maximumInputLength: this.siteSettings.max_tag_length,
maximumSelectionSize: this.siteSettings.max_tags_per_topic,
initSelection(element, callback) {
const data = [];
function splitVal(string, separator) {
var val, i, l;
if (string === null || string.length < 1) return [];
val = string.split(separator);
for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
return val;
}
$(splitVal(element.val(), ",")).each(function () {
data.push({
id: this,
text: this
});
});
callback(data);
},
createSearchChoice: function(term, data) {
term = term.replace(filterRegexp, '').trim();
// No empty terms, make sure the user has permission to create the tag
if (!term.length || !site.get('can_create_tag')) { return; }
if ($(data).filter(function() {
return this.text.localeCompare(term) === 0;
}).length === 0) {
return { id: term, text: term };
}
},
createSearchChoicePosition: function(list, item) {
// Search term goes on the bottom
list.push(item);
},
formatSelectionCssClass: function () { return "discourse-tag"; },
formatResult: formatTag,
// formatSelection: formatTag,
multiple: true,
ajax: {
quietMillis: 200,
cache: true,
url: "/tags/filter/search",
dataType: 'json',
data: function (term) {
return { q: term };
},
results: function (data) {
return data;
}
},
});
}.on('didInsertElement'),
_destroyTags: function() {
this.$().select2('destroy');
}.on('willDestroyElement')
});

View File

@ -1,11 +0,0 @@
import NotificationsButton from 'discourse/components/notifications-button';
export default NotificationsButton.extend({
classNames: ['notification-options', 'tag-notification-menu'],
buttonIncludesText: false,
i18nPrefix: 'tagging.notifications',
clicked(id) {
this.sendAction('action', id);
}
});

View File

@ -0,0 +1,5 @@
export default Ember.ArrayController.extend({
});

View File

@ -0,0 +1,46 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
export default Ember.Controller.extend(ModalFunctionality, {
notReady: Em.computed.not('ready'),
needs: ['admin-plugins-explorer'],
ready: function() {
let parsed;
try {
parsed = JSON.parse(this.get('queryFile'));
} catch (e) {
return false;
}
return !!parsed["query"];
}.property('queryFile'),
actions: {
doImport: function() {
const self = this;
const object = JSON.parse(this.get('queryFile')).query;
// Slight fixup before creating object
delete object.id;
this.set('loading', true);
this.store.createRecord('query', object).save().then(function(query) {
self.send('closeModal');
self.set('loading', false);
const parentController = self.get('controllers.admin-plugins-explorer');
parentController.pushObject(query);
parentController.set('selectedItem', query);
}).catch(function(xhr) {
self.set('loading', false);
if (xhr.responseJSON) {
bootbox.alert(xhr.responseJSON.errors.join("<br>"));
} else {
bootbox.alert(I18n.t('generic_error'));
}
});
}
}
});

View File

@ -1,15 +0,0 @@
export default Ember.Controller.extend({
tag: null,
list: null,
loadMoreTopics() {
return this.get('list').loadMore();
},
actions: {
changeTagNotification(id) {
const tagNotification = this.get('tagNotification');
tagNotification.update({ notification_level: id });
}
}
});

View File

@ -0,0 +1,8 @@
export default {
resource: 'admin.adminPlugins',
path: '/plugins',
map() {
this.route('explorer');
this.route('explorer-show', {path: 'explorer/:id'});
}
};

View File

@ -0,0 +1,7 @@
export default {
name: 'add-query-pluralization',
initialize(container) {
container.lookup('store:main').addPluralization('query', 'queries');
}
};

View File

@ -1,44 +0,0 @@
import ComposerController from 'discourse/controllers/composer';
import HistoryController from 'discourse/controllers/history';
import TopicController from 'discourse/controllers/topic';
import { needsSecondRowIf } from 'discourse/components/header-extra-info';
// Work around a quirk of custom fields -- an array of one element
// is returned as just that element. We should fix this properly
// in custom fields and remove this.
function customTagArray(fieldName) {
return function() {
var val = this.get(fieldName);
if (!val) { return val; }
if (!Array.isArray(val)) { val = [val]; }
return val;
}.property(fieldName);
}
export default {
name: 'extend-for-tagging',
initialize() {
Discourse.Composer.serializeOnCreate('tags');
Discourse.Composer.serializeToTopic('tags', 'topic.tags');
TopicController.reopen({
canEditTags: Ember.computed.not('isPrivateMessage')
});
HistoryController.reopen({
previousTagChanges: customTagArray('tags_changes.previous'),
currentTagChanges: customTagArray('tags_changes.current')
});
ComposerController.reopen({
canEditTags: function() {
return !this.site.mobileView &&
this.get('model.canEditTitle') &&
!this.get('model.creatingPrivateMessage');
}.property('model.canEditTitle', 'model.creatingPrivateMessage')
});
// Show a second row in the header if there are any tags on the topic
needsSecondRowIf('topic.tags.length', tagsLength => parseInt(tagsLength) > 0);
}
};

View File

@ -0,0 +1,6 @@
export default Discourse.Route.extend({
model(params) {
return this.store.find('query', params.get('id'));
}
});

View File

@ -0,0 +1,22 @@
import showModal from 'discourse/lib/show-modal';
export default Discourse.Route.extend({
controllerName: 'admin-plugins-explorer',
model() {
return this.store.findAll('query');
},
setupController(controller, model) {
},
actions: {
create() {
},
importQuery() {
showModal('import-query');
}
}
});

View File

@ -1,5 +0,0 @@
export default Discourse.Route.extend({
model() {
return Discourse.ajax("/tags/filter/cloud.json");
}
});

View File

@ -1,32 +0,0 @@
export default Discourse.Route.extend({
model(tag) {
tag.tag_id = Handlebars.Utils.escapeExpression(tag.tag_id);
if (this.get('currentUser')) {
// If logged in, we should get the tag's user settings
const self = this;
return this.store.find('tagNotification', tag.tag_id).then(function(tn) {
self.set('tagNotification', tn);
return tag;
});
}
return tag;
},
afterModel(tag) {
const self = this;
return Discourse.TopicList.list('tags/' + tag.tag_id).then(function(list) {
self.set('list', list);
});
},
setupController(controller, model) {
controller.setProperties({
tag: model,
list: this.get('list'),
tagNotification: this.get('tagNotification')
});
}
});

View File

@ -1,5 +0,0 @@
export default function() {
this.resource('tags', function() {
this.route('show', {path: ':tag_id'});
});
}

View File

@ -0,0 +1,8 @@
{{d-button action="create" label="explorer.create" icon="plus"}}
{{d-button action="importQuery" label="explorer.import.label" icon="upload"}}
{{#each query in model}}
<div class="row">
{{query.name}}
</div>
{{/each}}

View File

@ -0,0 +1,8 @@
<form {{action "dummy" on="submit"}}>
<div class='modal-body'>
{{json-file-uploader value=queryFile extension=".dcquery.json"}}
</div>
<div class="modal-footer">
{{d-button class='btn-primary' action='doImport' type='submit' disabled=notReady icon="plus" label='explorer.import.label'}}
</div>
</form>

View File

@ -1,9 +0,0 @@
<div class="container list-container">
<div class="row">
<div class="full-width">
<div id='list-area'>
{{outlet}}
</div>
</div>
</div>
</div>

View File

@ -1,10 +0,0 @@
<h2>{{i18n "tagging.all_tags"}}</h2>
<div class='tag-cloud'>
{{#each tag in cloud}}
{{discourse-tag tagId=tag.id
count=tag.count
maxCount=model.max_count
minCount=model.min_count}}
{{/each}}
</div>

View File

@ -1,9 +0,0 @@
{{#if tagNotification}}
{{tag-notifications-button tag=tag.tag_id
action="changeTagNotification"
notificationLevel=tagNotification.notification_level}}
{{/if}}
<h2>{{{i18n "tagging.topics_tagged" tag=tag.tag_id}}}</h2>
{{topic-list topics=list.topics}}

View File

@ -0,0 +1,6 @@
import ModalBodyView from "discourse/views/modal-body";
export default ModalBodyView.extend({
templateName: 'modal/import-query',
title: I18n.t('explorer.import.modal')
});

View File

@ -1,3 +0,0 @@
import DiscoveryTopicsView from "discourse/views/discovery-topics";
export default DiscoveryTopicsView;

View File

@ -20,23 +20,11 @@ en:
errors: errors:
explorer: explorer:
no_semicolons: "Remove the semicolons from the query." no_semicolons: "Remove the semicolons from the query."
tagging: explorer:
all_tags: "All Tags" title: "Data Explorer"
changed: "tags changed:" create: "Create New"
tags: "Tags" import:
choose_for_topic: "choose optional tags for this topic" label: "Import"
topics_tagged: "Topics tagged <span class='discourse-tag'>{{tag}}</span>" modal: "Import A Query"
export: "Export"
notifications:
watching:
title: "Watching"
description: "You will automatically watch all new topics in this tag. You will be notified of all new posts and topics, plus the count of unread and new posts will also appear next to the topic."
tracking:
title: "Tracking"
description: "You will automatically track all new topics in this tag. A count of unread and new posts will appear next to the topic."
regular:
title: "Regular"
description: "You will be notified if someone mentions your @name or replies to your post."
muted:
title: "Muted"
description: "You will not be notified of anything about new topics in this tag, and they will not appear on your unread tab."

View File

@ -204,14 +204,14 @@ SQL
end end
require_dependency 'application_controller' require_dependency 'application_controller'
class DataExplorer::ExplorerController < ::ApplicationController class DataExplorer::QueryController < ::ApplicationController
requires_plugin DataExplorer.plugin_name requires_plugin DataExplorer.plugin_name
skip_before_filter :check_xhr, only: [:show] skip_before_filter :check_xhr, only: [:show]
def index def index
# guardian.ensure_can_use_data_explorer! # guardian.ensure_can_use_data_explorer!
queries = DataExplorer::Query.all queries = DataExplorer::Query.all
render_serialized queries, DataExplorer::QuerySerializer render_serialized queries, DataExplorer::QuerySerializer, root: 'queries'
end end
def show def show
@ -225,7 +225,7 @@ SQL
end end
# guardian.ensure_can_see! query # guardian.ensure_can_see! query
render_serialized query, DataExplorer::QuerySerializer render_serialized query, DataExplorer::QuerySerializer, root: 'queries'
end end
# Helper endpoint for logic # Helper endpoint for logic
@ -243,7 +243,7 @@ SQL
end end
query.save query.save
render_serialized query, DataExplorer::QuerySerializer render_serialized query, DataExplorer::QuerySerializer, root: 'queries'
end end
def update def update
@ -253,12 +253,13 @@ SQL
end end
query.save query.save
render_serialized query, DataExplorer::QuerySerializer render_serialized query, DataExplorer::QuerySerializer, root: 'queries'
end end
def destroy def destroy
query = DataExplorer::Query.find(params[:id].to_i) query = DataExplorer::Query.find(params[:id].to_i)
query.destroy query.destroy
render nothing: true
end end
def run def run
@ -314,18 +315,19 @@ SQL
end end
DataExplorer::Engine.routes.draw do DataExplorer::Engine.routes.draw do
# GET /explorer -> explorer#index root to: "query#index"
# POST /explorer -> explorer#create get 'queries' => "query#index"
# GET /explorer/:id -> explorer#show # POST /query -> explorer#create
# PUT /explorer/:id -> explorer#update # GET /query/:id -> explorer#show
# DELETE /explorer/:id -> explorer#destroy # PUT /query/:id -> explorer#update
resources :explorer # DELETE /query/:id -> explorer#destroy
get 'explorer/parse_params' => "explorer#parse_params" resources :query
post 'explorer/:id/run' => "explorer#run" get 'query/parse_params' => "query#parse_params"
post 'query/:id/run' => "query#run"
end end
Discourse::Application.routes.append do Discourse::Application.routes.append do
mount ::DataExplorer::Engine, at: '/admin/plugins/', constraints: AdminConstraint.new mount ::DataExplorer::Engine, at: '/admin/plugins/explorer', constraints: AdminConstraint.new
end end
end end