diff --git a/app/controllers/public/api/v1/portals/articles_controller.rb b/app/controllers/public/api/v1/portals/articles_controller.rb index a5d98159c..f2df4add4 100644 --- a/app/controllers/public/api/v1/portals/articles_controller.rb +++ b/app/controllers/public/api/v1/portals/articles_controller.rb @@ -3,6 +3,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController before_action :set_portal before_action :set_category before_action :set_article, only: [:show] + layout 'portal' def index @articles = @portal.articles @@ -15,6 +16,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController def set_article @article = @category.articles.find(params[:id]) + @parsed_content = render_article_content(@article.content) end def set_category @@ -28,4 +30,10 @@ class Public::Api::V1::Portals::ArticlesController < PublicController def list_params params.permit(:query) end + + def render_article_content(content) + # rubocop:disable Rails/OutputSafety + CommonMarker.render_html(content).html_safe + # rubocop:enable Rails/OutputSafety + end end diff --git a/app/controllers/public/api/v1/portals/categories_controller.rb b/app/controllers/public/api/v1/portals/categories_controller.rb index 1f8e2093e..a36be13a1 100644 --- a/app/controllers/public/api/v1/portals/categories_controller.rb +++ b/app/controllers/public/api/v1/portals/categories_controller.rb @@ -2,6 +2,7 @@ class Public::Api::V1::Portals::CategoriesController < PublicController before_action :ensure_custom_domain_request, only: [:show, :index] before_action :set_portal before_action :set_category, only: [:show] + layout 'portal' def index @categories = @portal.categories @@ -12,7 +13,7 @@ class Public::Api::V1::Portals::CategoriesController < PublicController private def set_category - @category = @portal.categories.find_by!(locale: params[:locale]) + @category = @portal.categories.find_by!(locale: params[:locale], slug: params[:category_slug]) end def set_portal diff --git a/app/controllers/public/api/v1/portals_controller.rb b/app/controllers/public/api/v1/portals_controller.rb index bd3b4a911..276549eae 100644 --- a/app/controllers/public/api/v1/portals_controller.rb +++ b/app/controllers/public/api/v1/portals_controller.rb @@ -1,6 +1,7 @@ class Public::Api::V1::PortalsController < PublicController before_action :ensure_custom_domain_request, only: [:show] before_action :set_portal + layout 'portal' def show; end diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue index f410fd5a0..ce4808099 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue @@ -4,6 +4,7 @@ diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue index 4aa2d923d..d2e14b6a8 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue @@ -69,7 +69,7 @@ export default { }, portalLink() { const slug = this.$route.params.portalSlug; - return `/public/api/v1/portals/${slug}`; + return `/hc/${slug}`; }, }, methods: { diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue index c91702da9..656c8fecf 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue @@ -86,7 +86,7 @@ export default { }, portalLink() { const slug = this.$route.params.portalSlug; - return `/public/api/v1/portals/${slug}`; + return `/hc/${slug}`; }, }, mounted() { diff --git a/app/javascript/packs/portal.js b/app/javascript/packs/portal.js new file mode 100644 index 000000000..2f7736deb --- /dev/null +++ b/app/javascript/packs/portal.js @@ -0,0 +1,16 @@ +// This file is automatically compiled by Webpack, along with any other files +// present in this directory. You're encouraged to place your actual application logic in +// a relevant structure within app/javascript and only use these pack files to reference +// that code so that it will be compiled. + +import Rails from '@rails/ujs'; +import Turbolinks from 'turbolinks'; + +import { navigateToLocalePage } from '../portal/portalHelpers'; + +import '../portal/application.scss'; + +Rails.start(); +Turbolinks.start(); + +document.addEventListener('DOMContentLoaded', navigateToLocalePage); diff --git a/app/javascript/portal/application.scss b/app/javascript/portal/application.scss new file mode 100644 index 000000000..8e744f28d --- /dev/null +++ b/app/javascript/portal/application.scss @@ -0,0 +1,78 @@ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + +@import 'widget/assets/scss/reset'; +@import 'widget/assets/scss/variables'; +@import 'widget/assets/scss/buttons'; +@import 'widget/assets/scss/mixins'; +@import 'widget/assets/scss/forms'; +@import 'shared/assets/fonts/widget_fonts'; + +html, +body { + font-family: $font-family; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + height: 100%; +} + +.woot-survey-wrap { + height: 100%; +} + +.blog-content { + @apply text-lg; + @apply font-sans; + @apply text-slate-800; + @apply leading-normal; + + h1, + h2, + h3, + h4, + h5, + h6 { + @apply font-sans leading-relaxed font-extrabold text-slate-900; + @apply mb-4; + @apply mt-8; + } + + h1 { + @apply text-5xl leading-normal; + } + + h2 { + @apply text-4xl leading-normal; + } + + h3 { + @apply text-3xl leading-normal; + } + + h4 { + @apply text-2xl leading-normal; + } + + p { + @apply text-lg; + @apply font-sans; + @apply text-slate-800; + @apply leading-relaxed; + @apply mb-4; + } + + ul { + @apply list-disc; + @apply pl-8; + @apply ml-4; + } + + li { + @apply text-lg; + @apply font-sans; + @apply text-slate-800; + @apply leading-relaxed; + @apply mb-2; + } +} diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js new file mode 100644 index 000000000..d31d1b6ba --- /dev/null +++ b/app/javascript/portal/portalHelpers.js @@ -0,0 +1,8 @@ +export const navigateToLocalePage = () => { + const allLocaleSwitcher = document.querySelector('.locale-switcher'); + + const { portalSlug } = allLocaleSwitcher.dataset; + allLocaleSwitcher.addEventListener('change', event => { + window.location = `/hc/${portalSlug}/${event.target.value}/`; + }); +}; diff --git a/app/javascript/portal/specs/portal.spec.js b/app/javascript/portal/specs/portal.spec.js new file mode 100644 index 000000000..31dc06890 --- /dev/null +++ b/app/javascript/portal/specs/portal.spec.js @@ -0,0 +1,23 @@ +import { navigateToLocalePage } from '../portalHelpers'; + +describe('#navigateToLocalePage', () => { + it('returns correct cookie name', () => { + const elemDiv = document.createElement('div'); + elemDiv.classList.add('locale-switcher'); + document.body.appendChild(elemDiv); + + const allLocaleSwitcher = document.querySelector('.locale-switcher'); + + allLocaleSwitcher.addEventListener = jest + .fn() + .mockImplementationOnce((event, callback) => { + callback({ target: { value: 1 } }); + }); + + navigateToLocalePage(); + expect(allLocaleSwitcher.addEventListener).toBeCalledWith( + 'change', + expect.any(Function) + ); + }); +}); diff --git a/app/views/layouts/portal.html.erb b/app/views/layouts/portal.html.erb new file mode 100644 index 000000000..8f0e8eff9 --- /dev/null +++ b/app/views/layouts/portal.html.erb @@ -0,0 +1,42 @@ +<%# +# Application Layout + +This view template is used as the layout +for every page that Administrate generates. + +By default, it renders: +- Navigation +- Content for a search bar + (if provided by a `content_for` block in a nested page) +- Flashes +- Links to stylesheets and JavaScripts +%> + + + + + + + + + <%= javascript_pack_tag 'portal' %> + <%= stylesheet_pack_tag 'portal' %> + + + <%= csrf_meta_tags %> + <%= @portal.page_title%> + + + + +
+
+ <%= render "public/api/v1/portals/header", portal: @portal %> + <%= yield %> + <%= render "public/api/v1/portals/footer" %> +
+
+ + + + diff --git a/app/views/public/api/v1/portals/_category-block.html.erb b/app/views/public/api/v1/portals/_category-block.html.erb new file mode 100644 index 000000000..e7d8f1d32 --- /dev/null +++ b/app/views/public/api/v1/portals/_category-block.html.erb @@ -0,0 +1,44 @@ + +
+
+

+ <%= category.name %> +

<%= category.articles.published.size %> articles +
+
+ <% if category.articles.published.size == 0 %> +
+

No articles here

+
+ <% else %> + <% category.articles.published.take(5).each do |article| %> +
+ <%= article.title %> + + + + + +
+ <% end %> + <% end %> + +
+
+ + + View all articles + + + + + + +
+
diff --git a/app/views/public/api/v1/portals/_footer.html.erb b/app/views/public/api/v1/portals/_footer.html.erb new file mode 100644 index 000000000..e7961d310 --- /dev/null +++ b/app/views/public/api/v1/portals/_footer.html.erb @@ -0,0 +1,6 @@ + + diff --git a/app/views/public/api/v1/portals/_header.html.erb b/app/views/public/api/v1/portals/_header.html.erb new file mode 100644 index 000000000..d6a7b81a8 --- /dev/null +++ b/app/views/public/api/v1/portals/_header.html.erb @@ -0,0 +1,37 @@ + +
+ +
diff --git a/app/views/public/api/v1/portals/_hero.html.erb b/app/views/public/api/v1/portals/_hero.html.erb new file mode 100644 index 000000000..80f3469f9 --- /dev/null +++ b/app/views/public/api/v1/portals/_hero.html.erb @@ -0,0 +1,7 @@ + +
+
+

<%= portal.header_text %>

+

Search for the articles here or browse the categories below.

+
+
diff --git a/app/views/public/api/v1/portals/articles/index.html.erb b/app/views/public/api/v1/portals/articles/index.html.erb new file mode 100644 index 000000000..98f35e7c0 --- /dev/null +++ b/app/views/public/api/v1/portals/articles/index.html.erb @@ -0,0 +1,35 @@ + +
+
+
+
+ <%= @portal.name %> Home + / + / +
+ <% @articles.each do |article| %> +

+ <%= article.title %>

+
+
+ +
+
<%= article.author.name %>
+

+ <%= article.author.updated_at.strftime("%B %d %Y") %>

+
+
+
+ <% end %> +
+
+ +
+
+
+
+
+
+
+ diff --git a/app/views/public/api/v1/portals/articles/show.html.erb b/app/views/public/api/v1/portals/articles/show.html.erb new file mode 100644 index 000000000..bf3c6c302 --- /dev/null +++ b/app/views/public/api/v1/portals/articles/show.html.erb @@ -0,0 +1,35 @@ +
+
+ +
+ <%= @portal.name %> Home + / + <%= @article.category.name %> + / +
+

+ <%= @article.title %>

+
+
+ +
+
<%= @article.author.name %>
+

+ <%= @article.author.updated_at.strftime("%B %d %Y") %>

+
+
+
+ +
+ +
+
+
+
+

<%= @parsed_content %>

+
+
+
diff --git a/app/views/public/api/v1/portals/categories/_category-block.html.erb b/app/views/public/api/v1/portals/categories/_category-block.html.erb new file mode 100644 index 000000000..e7d8f1d32 --- /dev/null +++ b/app/views/public/api/v1/portals/categories/_category-block.html.erb @@ -0,0 +1,44 @@ + +
+
+

+ <%= category.name %> +

<%= category.articles.published.size %> articles +
+
+ <% if category.articles.published.size == 0 %> +
+

No articles here

+
+ <% else %> + <% category.articles.published.take(5).each do |article| %> +
+ <%= article.title %> + + + + + +
+ <% end %> + <% end %> + +
+
+ + + View all articles + + + + + + +
+
diff --git a/app/views/public/api/v1/portals/categories/_hero.html.erb b/app/views/public/api/v1/portals/categories/_hero.html.erb new file mode 100644 index 000000000..80f3469f9 --- /dev/null +++ b/app/views/public/api/v1/portals/categories/_hero.html.erb @@ -0,0 +1,7 @@ + +
+
+

<%= portal.header_text %>

+

Search for the articles here or browse the categories below.

+
+
diff --git a/app/views/public/api/v1/portals/categories/index.html.erb b/app/views/public/api/v1/portals/categories/index.html.erb new file mode 100644 index 000000000..5e6ffdd89 --- /dev/null +++ b/app/views/public/api/v1/portals/categories/index.html.erb @@ -0,0 +1,11 @@ +<%= render "hero", portal: @portal %> + +
+
+ <% @categories.each do |category| %> + <%= render "category-block", category: category, portal: @portal %> + <% end %> +
+
+ + diff --git a/app/views/public/api/v1/portals/categories/show.html.erb b/app/views/public/api/v1/portals/categories/show.html.erb new file mode 100644 index 000000000..755e79c33 --- /dev/null +++ b/app/views/public/api/v1/portals/categories/show.html.erb @@ -0,0 +1,41 @@ + +
+
+ <%= @portal.name %> Home + / + +
+

+ <%= @category.name %>

+ <%= @category.articles.published.size %> articles +
+
+ +
+
+ +
+ <% if @category.articles.published.size == 0 %> +
+

No articles here

+
+ <% else %> + <% @category.articles.published.each do |article| %> +
+ <%= article.title %> + + + + + +
+ <% end %> + <% end %> +
+
+ diff --git a/app/views/public/api/v1/portals/show.html.erb b/app/views/public/api/v1/portals/show.html.erb new file mode 100644 index 000000000..93f214d4a --- /dev/null +++ b/app/views/public/api/v1/portals/show.html.erb @@ -0,0 +1,12 @@ + +<%= render "hero", portal: @portal %> + +
+
+ <% @portal.categories.each do |category| %> + <%= render "category-block", category: category, portal: @portal %> + <% end %> +
+
+ + diff --git a/config/routes.rb b/config/routes.rb index b69faaac6..681227ef6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -287,11 +287,11 @@ Rails.application.routes.draw do end end - get 'hc/:slug/:locale', to: 'public/api/v1/portals#show', format: 'json' - get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index', format: 'json' - get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show', format: 'json' - get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index', format: 'json' - get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show', format: 'json' + get 'hc/:slug/:locale', to: 'public/api/v1/portals#show' + get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index' + get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show' + get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index' + get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show' # ---------------------------------------------------------------------- # Used in mailer templates diff --git a/package.json b/package.json index d4a4935d3..9ee0819f2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@chatwoot/utils": "^0.0.10", "@hcaptcha/vue-hcaptcha": "^0.3.2", "@rails/actioncable": "6.1.3", + "@rails/ujs": "^7.0.3-1", "@rails/webpacker": "5.3.0", "@sentry/tracing": "^6.19.7", "@sentry/vue": "^6.19.7", @@ -50,6 +51,7 @@ "semver": "7.3.5", "spinkit": "~1.2.5", "tailwindcss": "^1.9.6", + "turbolinks": "^5.2.0", "url-loader": "^2.0.0", "v-tooltip": "~2.1.3", "videojs-record": "^4.5.0", diff --git a/spec/controllers/public/api/v1/portals/articles_controller_spec.rb b/spec/controllers/public/api/v1/portals/articles_controller_spec.rb index fcee25486..53c455c1b 100644 --- a/spec/controllers/public/api/v1/portals/articles_controller_spec.rb +++ b/spec/controllers/public/api/v1/portals/articles_controller_spec.rb @@ -20,10 +20,6 @@ RSpec.describe 'Public Articles API', type: :request do get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/articles" expect(response).to have_http_status(:success) - json_response = JSON.parse(response.body) - - expect(json_response['payload'].length).to eql portal.articles.count - expect(json_response['meta']['articles_count']).to be json_response['payload'].size end it 'get all articles with searched text query' do @@ -39,9 +35,6 @@ RSpec.describe 'Public Articles API', type: :request do headers: agent.create_new_auth_token, params: { query: 'funny' } expect(response).to have_http_status(:success) - json_response = JSON.parse(response.body) - expect(json_response['payload'].count).to be 1 - expect(json_response['meta']['articles_count']).to be json_response['payload'].size end end @@ -50,9 +43,6 @@ RSpec.describe 'Public Articles API', type: :request do get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/#{article.id}" expect(response).to have_http_status(:success) - json_response = JSON.parse(response.body) - - expect(json_response['title']).to eql article.title end end end diff --git a/spec/controllers/public/api/v1/portals_controller_spec.rb b/spec/controllers/public/api/v1/portals_controller_spec.rb index c52f6ba94..7234c62b5 100644 --- a/spec/controllers/public/api/v1/portals_controller_spec.rb +++ b/spec/controllers/public/api/v1/portals_controller_spec.rb @@ -14,9 +14,6 @@ RSpec.describe 'Public Portals API', type: :request do get "/hc/#{portal.slug}/en" expect(response).to have_http_status(:success) - json_response = JSON.parse(response.body) - expect(json_response['slug']).to eql 'test-portal' - expect(json_response['meta']['articles_count']).to be 0 end it 'Throws unauthorised error for unknown domain' do diff --git a/yarn.lock b/yarn.lock index 9a896cb10..6d2234d26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1878,6 +1878,11 @@ resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.3.tgz#c8a67ec4d22ecd6931f7ebd98143fddbc815419a" integrity sha512-m02524MR9cTnUNfGz39Lkx9jVvuL0tle4O7YgvouJ7H83FILxzG1nQ5jw8pAjLAr9XQGu+P1sY4SKE3zyhCNjw== +"@rails/ujs@^7.0.3-1": + version "7.0.3-1" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.0.3-1.tgz#0a0f4f2b22b887bcbf6e0b0a72b8c86665cd31d9" + integrity sha512-g3LgpBAsWmW97xFxh5OTDgyEJLt63fEENJUYb/iNFRXY6aKLI/by6MjFw7x492DSP/+vKQa3oMEdNnjI9+yZgQ== + "@rails/webpacker@5.3.0": version "5.3.0" resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.3.0.tgz#9d7a615735f850572b9c5e2ad4c57f4af70d70fd" @@ -15087,6 +15092,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +turbolinks@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.2.0.tgz#e6877a55ea5c1cb3bb225f0a4ae303d6d32ff77c" + integrity sha512-pMiez3tyBo6uRHFNNZoYMmrES/IaGgMhQQM+VFF36keryjb5ms0XkVpmKHkfW/4Vy96qiGW3K9bz0tF5sK9bBw== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"