Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Theme for Zendesk Help Center #154

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"extends": "algolia",
"rules": {
"no-var": 2,
"algolia/relative-require": 2,
"algolia/force-import-root": 2,
"algolia/relative-require": 1,
"algolia/force-import-root": 1,
"algolia/no-require": 1,
"algolia/no-module-exports": 2
"algolia/no-module-exports": 1
}
}
10 changes: 10 additions & 0 deletions docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ In most cases, this should be enough to have an up-to-date search.
However, if you'd rather have it updated right now, like when you add a lot of support articles, you can manually trigger a full reindex.
On this page, just click the "Reindex" button in the bottom right corner. A few minutes later, your search index will be updated.

## Adding the Algolia Help Center Theme

You can use the Algolia Help Center Theme. You can import the theme from the Zendesk Theme Marketplace.
Also, you can download from here: https://cdn.jsdelivr.net/gh/algolia/algoliasearch-zendesk@new_theme/theme/Algolia-search/Algolia-search.zip
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should have the .zip directly with the source code


This theme has an admin section where you update your index, keys and other settings
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/algolia/algoliasearch-zendesk@new_theme/theme/Algolia-search/screenshots/AdminScreen.png" alt="Admin Section" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, won't those links break when we'll delete the branch?

</div>

## Updating your Help Center theme

Once your data has been extracted to Algolia, you need to update your Help Center theme in order to replace the search feature by Algolia.
Expand Down
Binary file added theme/Algolia-search/Algolia-search.zip
Binary file not shown.
70 changes: 70 additions & 0 deletions theme/Algolia-search/assets/algoliaSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

const setupSearch = function(appName, algoliaApiKey, index, useAutocomplete, querySuggestionIndex, useDebounce) {

const searchClient = algoliasearch(appName, algoliaApiKey);

// Initialize a Router
// See https://www.algolia.com/doc/api-reference/widgets/history-router/js/
const historyRouter = instantsearch.routers.history();

const search = instantsearch({
indexName: index,
searchClient,
routing: historyRouter,
});

const virtualSearchBox = instantsearch.connectors.connectSearchBox(() => {})({});
const realSearchBox = instantsearch.widgets.searchBox({
container: '#searchbox',
});
const searchBox = (useAutocomplete)? virtualSearchBox : realSearchBox;

const truncate = (input) => input.length > 50 ? `${input.substring(0, 50)}...` : input;


search.addWidgets([
searchBox,
instantsearch.widgets.hierarchicalMenu({
container: '#category-list',
attributes: ['category.title', 'section.full_path'],
}),
instantsearch.widgets.refinementList({
container: '#section-list',
attribute: 'section.title',
}),
instantsearch.widgets.hits({
container: '#hits',
templates: {
item(hit) {
let createdAt = moment(hit.created_at_iso).fromNow();
let lastComment = '';
const body = truncate(hit.body_safe);
const urlLabel = hit.title.replace(/[\s,\?, \!]/g,'-');
const url = '/hc/en-us/articles/' + hit.id + '-' + urlLabel;
console.log(url);
return `<div>
<div class="hit-description">${createdAt} <span style="float:right;">${lastComment} votes ${hit.vote_sum}<span></div>
<div class="hit-name"><a href="${url}" target="_blank">${hit.title}</a></div>
<div class="hit-description">${body}</div>
<div class="hit-description">${hit.label_names}</div>
</div>`;
}
}
}),
instantsearch.widgets.pagination({
container: '#pagination',
}),
]);

search.start();

if ( useAutocomplete ) {
const settings = {
articleIndex: index,
querySuggestionIndex: querySuggestionIndex,
useDebounce: useDebounce
}
setupAutocomplete(settings, searchClient, search, historyRouter);
}

}
201 changes: 201 additions & 0 deletions theme/Algolia-search/assets/autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
function setupAutocomplete(settings, searchClient, search, historyRouter) {

function setInstantSearchUiState(indexUiState) {
search.setUiState(uiState => ({
...uiState,
[settings.articleIndex]: {
...uiState[settings.articleIndex],
// We reset the page when the search state changes.
page: 1,
...indexUiState,
},
}));
}

// Return the InstantSearch index UI state.
function getInstantSearchUiState() {
const uiState = historyRouter.read()

return (uiState && uiState[settings.articleIndex]) || {}
}

// Build URLs that InstantSearch understands.
function getInstantSearchUrl(indexUiState) {
return search.createURL({ [settings.articleIndex]: indexUiState });
}

// Detect when an event is modified with a special key to let the browser
// trigger its default behavior.
function isModifierEvent(event) {
const isMiddleClick = event.button === 1;

return (
isMiddleClick ||
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey
);
}

function onSelect({ setIsOpen, setQuery, event, query }) {
// You want to trigger the default browser behavior if the event is modified.
if (isModifierEvent(event)) {
return;
}

setQuery(query);
setIsOpen(false);
setInstantSearchUiState({ query });
}

function getItemUrl({ query }) {
return getInstantSearchUrl({ query });
}

function createItemWrapperTemplate({ query, children, html}) {
const uiState = { query };
return html`<a
class="aa-ItemLink"
href="${getInstantSearchUrl(uiState)}"
onClick="${(event) => {
if (!isModifierEvent(event)) {
// Bypass the original link behavior if there's no event modifier
// to set the InstantSearch UI state without reloading the page.
event.preventDefault();
}
}}"
>
${children}
</a>`;
}

const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
key: 'instantsearch',
limit: 3,
transformSource({ source }) {
return {
...source,
getItemUrl({ item }) {
return getItemUrl({
query: item.label,
});
},
onSelect({ setIsOpen, setQuery, item, event }) {
onSelect({
setQuery,
setIsOpen,
event,
query: item.label,
});
},
// Update the default `item` template to wrap it with a link
// and plug it to the InstantSearch router.
templates: {
...source.templates,
item(params) {
const { children } = source.templates.item(params).props;

return createItemWrapperTemplate({
query: params.item.label,
children,
html: params.html,
});
},
},
};
},
});

const querySuggestionsPlugin = createQuerySuggestionsPlugin({
searchClient,
indexName: settings.querySuggestionIndex,
getSearchParams() {
// This creates a shared `hitsPerPage` value once the duplicates
// between recent searches and Query Suggestions are removed.
return recentSearchesPlugin.data.getAlgoliaSearchParams({
hitsPerPage: 6,
});
},
transformSource({ source }) {
return {
...source,
sourceId: 'querySuggestionsPlugin',
getItemUrl({ item }) {
return getItemUrl({
query: item.name,
});
},
onSelect({ setIsOpen, setQuery, event, item }) {
onSelect({
setQuery,
setIsOpen,
event,
query: item.label,
});
},
getItems(params) {
// We don't display Query Suggestions when there's no query.
if (!params.state.query) {
return [];
}
return source.getItems(params);
},
templates: {
...source.templates,
item(params) {
const { children } = source.templates.item(params).props;
return createItemWrapperTemplate({
query: params.item.name,
children,
html: params.html,
});
},
},
};
},
});

const searchPageState = getInstantSearchUiState();

function debounce(fn, time) {
let timerId = undefined

return function(...args) {
if (timerId) {
clearTimeout(timerId)
}

timerId = setTimeout(() => fn(...args), time)
}
}

const debouncedSetInstantSearchUiState = debounce(setInstantSearchUiState, 500)

autocomplete({
container: '#autocomplete',
placeholder: 'Search for Articles',
detachedMediaQuery: 'none',
openOnFocus: true,
plugins: [recentSearchesPlugin,querySuggestionsPlugin],
initialState: {
query: searchPageState.query || '',
},
onSubmit({ state }) {
setInstantSearchUiState({ query: state.query })
},
onReset() {
setInstantSearchUiState({ query: '' })
},
onStateChange({ prevState, state }) {
if (prevState.query !== state.query) {
if ( settings.useDebounce ) {
debouncedSetInstantSearchUiState({ query: state.query })
} else {
setInstantSearchUiState({ query: state.query });
}
}
},
});
}

Loading