DEV: Update CI workflows, fix linting issues (#32)

Included:

* DEV: Update CI workflows
* DEV: Update the plugin URL
* DEV: Update Gemfile.lock
* DEV: Remove an old rubocop artifact
* DEV: Add .prettierrc
* DEV: Clean up .gitignore
* DEV: Add license
* DEV: Update package.json
* DEV: Fix template issues
* DEV: Re-format code
* DEV: Use `@action`
* DEV: `inject as controller`
* DEV: Use `@computed`
* DEV: Avoid naming collision and extra indirection
* DEV: Use `discourse-common/utils/decorators`
* DEV: Use `role="button"`

Co-authored-by: Jarek Radosz <jradosz@gmail.com>
Co-authored-by: jjaffeux <j.jaffeux@gmail.com>
This commit is contained in:
discoursebot 2021-04-27 14:49:34 -04:00 committed by GitHub
parent 50f7c25de2
commit f75c9d5e32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 314 additions and 274 deletions

View File

@ -20,18 +20,13 @@ jobs:
node-version: 12 node-version: 12
- name: Set up ruby - name: Set up ruby
uses: actions/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 2.7 ruby-version: 2.7
bundler-cache: true
- name: Setup bundler
run: gem install bundler -v 2.1.4 --no-doc
- name: Setup gems
run: bundle install --jobs 4
- name: Yarn install - name: Yarn install
run: yarn install --dev run: yarn install
- name: ESLint - name: ESLint
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
@ -46,5 +41,8 @@ jobs:
yarn prettier --list-different "test/**/*.{js,es6}" ; \ yarn prettier --list-different "test/**/*.{js,es6}" ; \
fi fi
- name: Ember template lint
run: yarn ember-template-lint assets/javascripts
- name: Rubocop - name: Rubocop
run: bundle exec rubocop . run: bundle exec rubocop .

View File

@ -10,14 +10,15 @@ on:
jobs: jobs:
build: build:
name: ${{ matrix.build_type }} name: ${{ matrix.build_type }}
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
container: discourse/discourse_test:release
timeout-minutes: 60 timeout-minutes: 60
env: env:
DISCOURSE_HOSTNAME: www.example.com DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072 RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test RAILS_ENV: test
PGHOST: localhost PGHOST: postgres
PGUSER: discourse PGUSER: discourse
PGPASSWORD: discourse PGPASSWORD: discourse
@ -26,8 +27,7 @@ jobs:
matrix: matrix:
build_type: ["backend", "frontend"] build_type: ["backend", "frontend"]
os: [ubuntu-latest] ruby: ["2.7"]
ruby: ["2.6"]
postgres: ["12"] postgres: ["12"]
redis: ["4.x"] redis: ["4.x"]
@ -47,13 +47,13 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
with: with:
repository: discourse/discourse repository: discourse/discourse
fetch-depth: 1 fetch-depth: 1
- name: Install plugin - name: Install plugin
uses: actions/checkout@master uses: actions/checkout@v2
with: with:
path: plugins/${{ github.event.repository.name }} path: plugins/${{ github.event.repository.name }}
fetch-depth: 1 fetch-depth: 1
@ -75,46 +75,30 @@ jobs:
git config --global user.email "ci@ci.invalid" git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI" git config --global user.name "Discourse CI"
- name: Setup packages
run: |
sudo apt-get update
sudo apt-get -yqq install postgresql-client libpq-dev gifsicle jpegoptim optipng jhead
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh
- name: Update imagemagick
if: matrix.build_type == 'backend'
run: |
wget https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-imagemagick
chmod +x install-imagemagick
sudo ./install-imagemagick
- name: Setup redis - name: Setup redis
uses: shogo82148/actions-setup-redis@v1 uses: shogo82148/actions-setup-redis@v1
with: with:
redis-version: ${{ matrix.redis }} redis-version: ${{ matrix.redis }}
- name: Setup ruby
uses: actions/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Setup bundler
run: |
gem install bundler -v 2.1.4 --no-doc
bundle config deployment 'true'
bundle config without 'development'
- name: Bundler cache - name: Bundler cache
uses: actions/cache@v2 uses: actions/cache@v2
id: bundler-cache
with: with:
path: vendor/bundle path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gem- ${{ runner.os }}-${{ matrix.ruby }}-gem-
- name: Setup gems - name: Setup gems
run: bundle install --jobs 4 run: |
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Lint English locale
if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory - name: Get yarn cache directory
id: yarn-cache-dir id: yarn-cache-dir
@ -130,7 +114,7 @@ jobs:
${{ runner.os }}-${{ matrix.os }}-yarn- ${{ runner.os }}-${{ matrix.os }}-yarn-
- name: Yarn install - name: Yarn install
run: yarn install --dev run: yarn install
- name: Migrate database - name: Migrate database
run: | run: |

3
.gitignore vendored
View File

@ -1,4 +1 @@
.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml
node_modules node_modules
yarn-error.log
.rubocop-https---raw-githubusercontent-com-discourse-*

1
.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,2 +0,0 @@
inherit_gem:
rubocop-discourse: default.yml

View File

@ -1,31 +1,32 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
ast (2.4.1) ast (2.4.2)
parallel (1.19.2) parallel (1.20.1)
parser (2.7.1.5) parser (3.0.1.0)
ast (~> 2.4.1) ast (~> 2.4.1)
rainbow (3.0.0) rainbow (3.0.0)
regexp_parser (1.8.1) regexp_parser (2.1.1)
rexml (3.2.4) rexml (3.2.5)
rubocop (0.92.0) rubocop (1.13.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.1.5) parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7) regexp_parser (>= 1.8, < 3.0)
rexml rexml
rubocop-ast (>= 0.5.0) rubocop-ast (>= 1.2.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (0.7.1) rubocop-ast (1.4.1)
parser (>= 2.7.1.5) parser (>= 2.7.1.5)
rubocop-discourse (2.3.2) rubocop-discourse (2.4.1)
rubocop (>= 0.69.0) rubocop (>= 1.1.0)
rubocop-rspec (>= 1.39.0) rubocop-rspec (>= 2.0.0)
rubocop-rspec (1.43.2) rubocop-rspec (2.2.0)
rubocop (~> 0.87) rubocop (~> 1.0)
ruby-progressbar (1.10.1) rubocop-ast (>= 1.1.0)
unicode-display_width (1.7.0) ruby-progressbar (1.11.0)
unicode-display_width (2.0.0)
PLATFORMS PLATFORMS
ruby ruby
@ -34,4 +35,4 @@ DEPENDENCIES
rubocop-discourse rubocop-discourse
BUNDLED WITH BUNDLED WITH
2.1.4 2.2.16

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Civilized Discourse Construction Kit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -3,14 +3,9 @@ import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ export default Component.extend({
tagName: "", tagName: "",
@discourseComputed("category") @discourseComputed("category")
categoryName(category) { categoryName(category) {
return this.site.categories.findBy("id", category.id).name; return this.site.categories.findBy("id", category.id).name;
}, },
actions: {
selectCategory() {
this.selectCategory(this.category);
return false;
},
},
}); });

View File

@ -1,5 +1,6 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { debounce } from "@ember/runloop"; import { debounce } from "@ember/runloop";
import { action } from "@ember/object";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
export default Component.extend({ export default Component.extend({
@ -12,14 +13,14 @@ export default Component.extend({
debounceFunc(this, "onSearch", term, 500); debounceFunc(this, "onSearch", term, 500);
}, },
actions: { @action
onSearchTermChange(term) { onSearchTermChange(term) {
this.debouncedSearch(term); this.debouncedSearch(term);
}, },
clearSearch() { @action
this.set("searchTerm", ""); clearSearch() {
this.onSearch(""); this.set("searchTerm", "");
}, this.onSearch("");
}, },
}); });

View File

@ -1,10 +1,5 @@
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
tagName: "", tagName: "",
actions: {
selectTag() {
this.selectTag(this.tag);
return false;
},
},
}); });

View File

@ -1,8 +1,10 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ export default Component.extend({
classNames: "docs-topic-list", classNames: "docs-topic-list",
@discourseComputed("order") @discourseComputed("order")
sortTitle(order) { sortTitle(order) {
return order === "title"; return order === "title";
@ -22,14 +24,15 @@ export default Component.extend({
} }
}, },
actions: { @action
sortListActivity() { sortListActivity() {
this.sortBy("activity"); this.sortBy("activity");
return false; return false;
}, },
sortListTitle() {
this.sortBy("title"); @action
return false; sortListTitle() {
}, this.sortBy("title");
return false;
}, },
}); });

View File

@ -1,20 +1,22 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { reads } from "@ember/object/computed"; import { reads } from "@ember/object/computed";
import { computed } from "@ember/object"; import computed from "discourse-common/utils/decorators";
export default Component.extend({ export default Component.extend({
classNames: "docs-topic", classNames: "docs-topic",
originalPostContent: reads("post.cooked"), originalPostContent: reads("post.cooked"),
post: computed("topic", function () { @computed("topic")
post() {
return this.store.createRecord( return this.store.createRecord(
"post", "post",
this.topic.post_stream.posts.firstObject this.topic.post_stream.posts.firstObject
); );
}), },
model: computed("post", "topic", function () { @computed("post", "topic")
model() {
const post = this.post; const post = this.post;
if (!post.topic) { if (!post.topic) {
@ -22,7 +24,7 @@ export default Component.extend({
} }
return post; return post;
}), },
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);

View File

@ -1,5 +1,6 @@
import Controller, { inject } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed, { on } from "discourse-common/utils/decorators"; import discourseComputed, { on } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
import { alias, equal, readOnly } from "@ember/object/computed"; import { alias, equal, readOnly } from "@ember/object/computed";
import Docs from "discourse/plugins/discourse-docs/discourse/models/docs"; import Docs from "discourse/plugins/discourse-docs/discourse/models/docs";
import { getOwner } from "@ember/application"; import { getOwner } from "@ember/application";
@ -14,14 +15,12 @@ export default Controller.extend({
searchTerm: "search", searchTerm: "search",
selectedTopic: "topic", selectedTopic: "topic",
}, },
application: inject(),
application: controller(),
isLoading: false, isLoading: false,
isLoadingMore: false, isLoadingMore: false,
loadMoreUrl: alias("model.topics.load_more_url"),
isTopicLoading: false, isTopicLoading: false,
categories: readOnly("model.categories"),
topics: alias("model.topics.topic_list.topics"),
tags: readOnly("model.tags"),
filterTags: null, filterTags: null,
filterCategories: null, filterCategories: null,
filterSolved: false, filterSolved: false,
@ -31,7 +30,13 @@ export default Controller.extend({
expandedFilters: false, expandedFilters: false,
ascending: null, ascending: null,
orderColumn: null, orderColumn: null,
loadMoreUrl: alias("model.topics.load_more_url"),
categories: readOnly("model.categories"),
topics: alias("model.topics.topic_list.topics"),
tags: readOnly("model.tags"),
topicCount: alias("model.topic_count"), topicCount: alias("model.topic_count"),
emptyResults: equal("topicCount", 0),
@on("init") @on("init")
_setupFilters() { _setupFilters() {
@ -61,8 +66,6 @@ export default Controller.extend({
return isSearching || filterSolved; return isSearching || filterSolved;
}, },
emptyResults: equal("topicCount", 0),
@discourseComputed @discourseComputed
canFilterSolved() { canFilterSolved() {
return ( return (
@ -76,108 +79,117 @@ export default Controller.extend({
return !!filterTags; return !!filterTags;
}, },
actions: { @action
setSelectedTopic(topicId) { setSelectedTopic(topicId) {
this.set("selectedTopic", topicId); this.set("selectedTopic", topicId);
window.scrollTo(0, 0); window.scrollTo(0, 0);
}, },
onChangeFilterSolved(solvedFilter) { @action
this.set("filterSolved", solvedFilter); onChangeFilterSolved(solvedFilter) {
}, this.set("filterSolved", solvedFilter);
},
updateSelectedTags(tag) { @action
let filter = this.filterTags; updateSelectedTags(tag) {
if (filter && filter.includes(tag.id)) { let filter = this.filterTags;
filter = filter.replace(tag.id, "").replace(/^\|+|\|+$/g, ""); if (filter && filter.includes(tag.id)) {
} else if (filter) { filter = filter.replace(tag.id, "").replace(/^\|+|\|+$/g, "");
filter = `${filter}|${tag.id}`; } else if (filter) {
} else { filter = `${filter}|${tag.id}`;
filter = tag.id; } else {
} filter = tag.id;
}
this.setProperties({ this.setProperties({
filterTags: filter, filterTags: filter,
selectedTopic: null, selectedTopic: null,
}); });
}, },
updateSelectedCategories(category) { @action
let filter = this.filterCategories; updateSelectedCategories(category) {
if (filter && filter.includes(category.id)) { let filter = this.filterCategories;
filter = filter.replace(category.id, "").replace(/^\|+|\|+$/g, ""); if (filter && filter.includes(category.id)) {
} else if (filter) { filter = filter.replace(category.id, "").replace(/^\|+|\|+$/g, "");
filter = `${filter}|${category.id}`; } else if (filter) {
} else { filter = `${filter}|${category.id}`;
filter = category.id; } else {
} filter = category.id;
}
this.setProperties({ this.setProperties({
filterCategories: filter, filterCategories: filter,
selectedTopic: null, selectedTopic: null,
}); });
},
performSearch(term) { return false;
if (term === "") { },
this.set("searchTerm", null);
return false;
}
if (term.length < this.siteSettings.min_search_term_length) { @action
return false; performSearch(term) {
} if (term === "") {
this.set("searchTerm", null);
return false;
}
this.setProperties({ if (term.length < this.siteSettings.min_search_term_length) {
searchTerm: term, return false;
selectedTopic: null, }
});
},
sortBy(column) { this.setProperties({
const order = this.orderColumn; searchTerm: term,
const ascending = this.ascending; selectedTopic: null,
if (column === "title") { });
this.set("orderColumn", "title"); },
} else if (column === "activity") {
this.set("orderColumn", "activity");
}
if (!ascending && order) { @action
this.set("ascending", true); sortBy(column) {
} else { const order = this.orderColumn;
this.set("ascending", ""); const ascending = this.ascending;
} if (column === "title") {
}, this.set("orderColumn", "title");
} else if (column === "activity") {
this.set("orderColumn", "activity");
}
loadMore() { if (!ascending && order) {
if (this.canLoadMore && !this.isLoadingMore) { this.set("ascending", true);
this.set("isLoadingMore", true); } else {
this.set("ascending", "");
}
},
Docs.loadMore(this.loadMoreUrl).then((result) => { @action
const topics = this.topics.concat(result.topics.topic_list.topics); loadMore() {
if (this.canLoadMore && !this.isLoadingMore) {
this.set("isLoadingMore", true);
this.setProperties({ Docs.loadMore(this.loadMoreUrl).then((result) => {
topics, const topics = this.topics.concat(result.topics.topic_list.topics);
loadMoreUrl: result.topics.load_more_url || null,
isLoadingMore: false, this.setProperties({
}); topics,
loadMoreUrl: result.topics.load_more_url || null,
isLoadingMore: false,
}); });
} });
}, }
},
toggleFilters() { @action
if (!this.expandedFilters) { toggleFilters() {
this.set("expandedFilters", true); if (!this.expandedFilters) {
} else { this.set("expandedFilters", true);
this.set("expandedFilters", false); } else {
} this.set("expandedFilters", false);
}, }
},
returnToList() { @action
this.set("selectedTopic", null); returnToList() {
getOwner(this).lookup("router:main").transitionTo("docs"); this.set("selectedTopic", null);
}, getOwner(this).lookup("router:main").transitionTo("docs");
}, },
}); });

View File

@ -1,19 +1,24 @@
import Controller, { inject } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { action } from "@ember/object";
export default Controller.extend({ export default Controller.extend({
indexController: inject("docs.index"), indexController: controller("docs.index"),
actions: {
updateSelectedCategories(category) { @action
this.indexController.send("updateSelectedCategories", category); updateSelectedCategories(category) {
return false; this.indexController.send("updateSelectedCategories", category);
}, return false;
updateSelectedTags(tag) { },
this.indexController.send("updateSelectedTags", tag);
return false; @action
}, updateSelectedTags(tag) {
performSearch(term) { this.indexController.send("updateSelectedTags", tag);
this.indexController.send("performSearch", term); return false;
return false; },
},
@action
performSearch(term) {
this.indexController.send("performSearch", term);
return false;
}, },
}); });

View File

@ -7,6 +7,7 @@ const Docs = EmberObject.extend({});
Docs.reopenClass({ Docs.reopenClass({
list(params) { list(params) {
let filters = []; let filters = [];
if (params.filterCategories) { if (params.filterCategories) {
filters.push(`category=${params.filterCategories}`); filters.push(`category=${params.filterCategories}`);
} }
@ -53,10 +54,7 @@ Docs.reopenClass({
promise = promise.then((data) => { promise = promise.then((data) => {
data.topics.topic_list.topics = data.topics.topic_list.topics.map( data.topics.topic_list.topics = data.topics.topic_list.topics.map(
(topic) => { (topic) => Topic.create(topic)
topic = Topic.create(topic);
return topic;
}
); );
return data; return data;
}); });

View File

@ -15,6 +15,7 @@ export default DiscourseRoute.extend({
refreshModel: true, refreshModel: true,
}, },
}, },
model(params) { model(params) {
this.controllerFor("docs.index").set("isLoading", true); this.controllerFor("docs.index").set("isLoading", true);
return Docs.list(params).then((result) => { return Docs.list(params).then((result) => {

View File

@ -1,10 +1,12 @@
<a {{action "selectCategory"}} class="docs-item docs-category {{if category.active 'selected'}}"> <a href {{action "selectCategory"}} class="docs-item docs-category {{if category.active "selected"}}">
{{#unless category.active}} {{#unless category.active}}
{{d-icon "plus"}} {{d-icon "plus"}}
{{/unless}} {{/unless}}
{{#if category.active}} {{#if category.active}}
{{d-icon "times-circle"}} {{d-icon "times-circle"}}
{{/if}} {{/if}}
<span class="docs-item-id category-id">{{categoryName}}</span> <span class="docs-item-id category-id">{{categoryName}}</span>
<span class="docs-item-count category-count">{{category.count}}</span> <span class="docs-item-count category-count">{{category.count}}</span>
</a> </a>

View File

@ -1,10 +1,12 @@
<a {{action "selectTag"}} class="docs-item docs-tag {{if tag.active 'selected'}} {{if subtag 'subtag'}}"> <a href {{action "selectTag"}} class="docs-item docs-tag {{if tag.active "selected"}} {{if subtag "subtag"}}">
{{#unless tag.active}} {{#unless tag.active}}
{{d-icon "plus"}} {{d-icon "plus"}}
{{/unless}} {{/unless}}
{{#if tag.active}} {{#if tag.active}}
{{d-icon "times-circle"}} {{d-icon "times-circle"}}
{{/if}} {{/if}}
<span class="docs-item-id tag-id">{{tag.id}}</span> <span class="docs-item-id tag-id">{{tag.id}}</span>
<span class="docs-item-count tag-count">{{tag.count}}</span> <span class="docs-item-count tag-count">{{tag.count}}</span>
</a> </a>

View File

@ -1,27 +1,31 @@
{{#load-more selector=".topic-list tr" action=loadMore}} {{#load-more selector=".topic-list tr" action=loadMore}}
<table class="topic-list"> <table class="topic-list">
<thead> <thead>
<th {{action "sortListTitle"}}> <th role="button" {{action "sortListTitle"}}>
{{i18n 'docs.column_titles.topic'}} {{i18n "docs.column_titles.topic"}}
{{#if sortTitle}} {{#if sortTitle}}
{{#if ascending}} {{#if ascending}}
{{d-icon 'angle-up'}} {{d-icon "angle-up"}}
{{else}} {{else}}
{{d-icon 'angle-down'}} {{d-icon "angle-down"}}
{{/if}} {{/if}}
{{/if}} {{/if}}
</th> </th>
<th {{action "sortListActivity"}}>
{{i18n 'docs.column_titles.activity'}} <th role="button" {{action "sortListActivity"}}>
{{i18n "docs.column_titles.activity"}}
{{#if sortActivity}} {{#if sortActivity}}
{{#if ascending}} {{#if ascending}}
{{d-icon 'angle-up'}} {{d-icon "angle-up"}}
{{else}} {{else}}
{{d-icon 'angle-down'}} {{d-icon "angle-down"}}
{{/if}} {{/if}}
{{/if}} {{/if}}
</th> </th>
</thead> </thead>
<tbody> <tbody>
{{#each topics as |topic|}} {{#each topics as |topic|}}
{{raw "docs-topic-list-item" topic=topic}} {{raw "docs-topic-list-item" topic=topic}}

View File

@ -5,7 +5,8 @@
}} }}
<div class="topic-content"> <div class="topic-content">
<h1>{{{topic.fancyTitle}}}</h1> <h1>{{html-safe topic.fancyTitle}}</h1>
{{mount-widget {{mount-widget
widget="post" widget="post"
model=model model=model
@ -16,7 +17,7 @@
</div> </div>
<a class="docs-nav-link more" href="/t/{{topic.id}}"> <a class="docs-nav-link more" href="/t/{{topic.id}}">
{{d-icon "far-comment"}} {{i18n 'docs.topic.navigate_to_topic'}} {{d-icon "far-comment"}} {{i18n "docs.topic.navigate_to_topic"}}
</a> </a>
{{plugin-outlet name="after-docs-topic"}} {{plugin-outlet name="after-docs-topic"}}

View File

@ -1,15 +1,16 @@
{{#conditional-loading-spinner condition=isLoading}} {{#conditional-loading-spinner condition=isLoading}}
{{#if emptyTopics}} {{#if emptyTopics}}
<span class="no-topics-found">{{html-safe (i18n 'docs.no_topics')}}</span> <span class="no-topics-found">{{html-safe (i18n "docs.no_topics")}}</span>
{{else}} {{else}}
<div class="docs-browse"> <div class="docs-browse">
{{#if site.mobileView}} {{#if site.mobileView}}
{{#unless selectedTopic}} {{#unless selectedTopic}}
{{d-button class="docs-expander" icon=(if expandedFilters "angle-up" "angle-down") action=(action "toggleFilters") label="docs.filter_button"}} {{d-button class="docs-expander" icon=(if expandedFilters "angle-up" "angle-down") action=(action "toggleFilters") label="docs.filter_button"}}
{{/unless}} {{/unless}}
{{/if}} {{/if}}
<div class="docs-filters">
{{#if expandedFilters}} <div class="docs-filters">
{{#if expandedFilters}}
{{#if canFilterSolved}} {{#if canFilterSolved}}
<div class="docs-items docs-solved"> <div class="docs-items docs-solved">
<label class="checkbox-label docs-item"> <label class="checkbox-label docs-item">
@ -21,11 +22,11 @@
{{i18n "docs.filter_solved"}} {{i18n "docs.filter_solved"}}
</label> </label>
</div> </div>
{{/if}} {{/if}}
{{#if categories}} {{#if categories}}
<div class="docs-items docs-categories"> <div class="docs-items docs-categories">
<h3>{{i18n 'docs.categories'}}</h3> <h3>{{i18n "docs.categories"}}</h3>
{{#each categories as |category|}} {{#each categories as |category|}}
{{docs-category {{docs-category
category=category category=category
@ -35,9 +36,10 @@
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
{{#if tags}} {{#if tags}}
<div class="docs-items docs-tags"> <div class="docs-items docs-tags">
<h3>{{i18n 'docs.tags'}}</h3> <h3>{{i18n "docs.tags"}}</h3>
{{#each tags as |tag|}} {{#each tags as |tag|}}
{{docs-tag {{docs-tag
tag=tag tag=tag
@ -46,41 +48,41 @@
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
{{/if}}
</div>
{{#if selectedTopic}}
{{#conditional-loading-spinner condition=isTopicLoading}}
{{docs-topic topic=topic return=(action "returnToList")}}
{{/conditional-loading-spinner}}
{{else}}
<div class="docs-results">
{{#if isSearchingOrFiltered}}
{{#if emptyResults}}
<div class="result-count no-result">
{{i18n "search.no_results"}}
</div>
{{plugin-outlet name="after-docs-empty-results"}}
{{else}}
<div class="result-count">
{{i18n "docs.search.results" count=topicCount}}
</div>
{{/if}}
{{/if}}
{{#unless emptyResults}}
{{docs-topic-list
topics=topics
ascending=ascending
order=orderColumn
sortBy=(action "sortBy")
selectTopic=(action "setSelectedTopic")
loadMore=(action "loadMore")
loading=isLoadingMore
}}
{{/unless}}
</div>
{{/if}} {{/if}}
</div> </div>
{{#if selectedTopic}}
{{#conditional-loading-spinner condition=isTopicLoading}}
{{docs-topic topic=topic return=(action "returnToList")}}
{{/conditional-loading-spinner}}
{{else}}
<div class="docs-results">
{{#if isSearchingOrFiltered}}
{{#if emptyResults}}
<div class="result-count no-result">
{{i18n 'search.no_results'}}
</div>
{{plugin-outlet name="after-docs-empty-results"}}
{{else}}
<div class="result-count">
{{i18n 'docs.search.results'
count=topicCount
}}
</div>
{{/if}}
{{/if}}
{{#unless emptyResults}}
{{docs-topic-list
topics=topics
ascending=ascending
order=orderColumn
sortBy=(action "sortBy")
selectTopic=(action "setSelectedTopic")
loadMore=(action "loadMore")
loading=isLoadingMore
}}
{{/unless}}
</div>
{{/if}}
</div>
{{/if}} {{/if}}
{{/conditional-loading-spinner}} {{/conditional-loading-spinner}}

View File

@ -1,5 +1,14 @@
<div class="docs"> <div class="docs">
{{plugin-outlet name="before-docs-search" args=(hash selectCategory=(action "updateSelectedCategories") selectTag=(action "updateSelectedTags") tags=indexController.tags categories=indexController.categories )}} {{plugin-outlet
name="before-docs-search"
args=(hash
selectCategory=(action "updateSelectedCategories")
selectTag=(action "updateSelectedTags")
tags=indexController.tags
categories=indexController.categories
)
}}
{{docs-search {{docs-search
searchTerm=(readonly indexController.searchTerm) searchTerm=(readonly indexController.searchTerm)
onSearch=(action "performSearch") onSearch=(action "performSearch")

View File

@ -127,6 +127,10 @@
th { th {
min-width: 5em; min-width: 5em;
&[role="button"] {
cursor: pointer;
}
&:hover { &:hover {
background-color: var(--primary-low, $primary-low); background-color: var(--primary-low, $primary-low);
} }

View File

@ -1,4 +1,7 @@
{ {
"name": "discourse-docs",
"version": "0.1",
"repository": "https://github.com/discourse/discourse-docs",
"author": "Discourse", "author": "Discourse",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {

View File

@ -4,7 +4,7 @@
# about: A plugin to make it easy to explore and find knowledge base documents in Discourse # about: A plugin to make it easy to explore and find knowledge base documents in Discourse
# version: 0.1 # version: 0.1
# author: Justin DiRose # author: Justin DiRose
# url: https://github.com/discourse/discourse-knowledge-explorer # url: https://github.com/discourse/discourse-docs
enabled_site_setting :docs_enabled enabled_site_setting :docs_enabled
@ -34,6 +34,7 @@ after_initialize do
end end
end end
end end
add_to_class(:topic_query, :list_docs_topics) do add_to_class(:topic_query, :list_docs_topics) do
default_results(@options) default_results(@options)
end end