diff --git a/src/content-handlers/iiif/extensions/uv-openseadragon-extension/config/config.json b/src/content-handlers/iiif/extensions/uv-openseadragon-extension/config/config.json
index 2f02fb281..2d5191fa0 100644
--- a/src/content-handlers/iiif/extensions/uv-openseadragon-extension/config/config.json
+++ b/src/content-handlers/iiif/extensions/uv-openseadragon-extension/config/config.json
@@ -288,13 +288,13 @@
"autocompleteAllowWords": false,
"galleryButtonEnabled": true,
"imageSelectionBoxEnabled": false,
- "pageModeEnabled": false,
+ "pageModeEnabled": true,
"pagingToggleEnabled": true,
"centerOptionsEnabled": true,
"localeToggleEnabled": false,
"settingsButtonEnabled": true,
"helpEnabled": true,
- "modeOptionsEnabled": true
+ "modeOptionsEnabled": false
},
"content": {
"close": "$close",
diff --git a/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/PagingHeaderPanel.ts b/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/PagingHeaderPanel.ts
index 43237080e..7dfad8071 100644
--- a/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/PagingHeaderPanel.ts
+++ b/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/PagingHeaderPanel.ts
@@ -1,5 +1,5 @@
const $ = require("jquery");
-import { AutoComplete } from "../uv-shared-module/AutoComplete";
+import { PagingAutoComplete } from "../uv-shared-module/PagingAutoComplete";
import { IIIFEvents } from "../../IIIFEvents";
import { OpenSeadragonExtensionEvents } from "../../extensions/uv-openseadragon-extension/Events";
import { HeaderPanel } from "../uv-shared-module/HeaderPanel";
@@ -135,20 +135,25 @@ export class PagingHeaderPanel extends HeaderPanel<
);
this.$search.append(this.$autoCompleteBox);
- new AutoComplete(
+ new PagingAutoComplete(
+ //element
this.$autoCompleteBox,
+
+ //autoCompleteFunc
(term: string, cb: (results: string[]) => void) => {
const results: string[] = [];
const canvases: Canvas[] = this.extension.helper.getCanvases();
+
// if in page mode, get canvases by label.
if (this.isPageModeEnabled()) {
+
for (let i = 0; i < canvases.length; i++) {
const canvas: Canvas = canvases[i];
const label: string | null = LanguageMap.getValue(
canvas.getLabel()
);
- if (label && label.startsWith(term)) {
+ if (label && label.includes(term)) {
results.push(label);
}
}
@@ -163,14 +168,27 @@ export class PagingHeaderPanel extends HeaderPanel<
}
cb(results);
},
+
+ //parseResultsFunc
(results: any) => {
return results;
},
+
+ //onSelect
(terms: string) => {
this.search(terms);
},
- 300,
+
+ //delay
0,
+
+ // minChars
+ 0,
+
+ //positionAbove
+ false,
+
+ //allowWords
Bools.getBool(this.options.autocompleteAllowWords, false)
);
} else if (Bools.getBool(this.options.imageSelectionBoxEnabled, true)) {
@@ -418,6 +436,13 @@ export class PagingHeaderPanel extends HeaderPanel<
if (this.options.modeOptionsEnabled === false) {
this.$modeOptions.hide();
this.$centerOptions.addClass("modeOptionsDisabled");
+
+ //JM also hide other parts of the panel otherwise viewable in imageMode
+ this.$searchButton.hide();
+ this.$total.hide();
+
+ //JM and add class to autocomplate input to re-style
+ this.$autoCompleteBox.addClass("pageMode");
}
// Search is shown as default
@@ -456,6 +481,16 @@ export class PagingHeaderPanel extends HeaderPanel<
if (!Bools.getBool(this.options.pagingToggleEnabled, true)) {
this.$pagingToggleButtons.hide();
}
+
+ $(document).on("click", (e) => {
+ if (
+ !this.$autoCompleteBox.is($(e.target)[0]) &&
+ this.$autoCompleteBox.has($(e.target)[0]).length === 0
+ ) {
+ this.setSearchFieldValue(this.extension.helper.canvasIndex);
+ }
+ })
+
}
openGallery(): void {
@@ -753,5 +788,8 @@ export class PagingHeaderPanel extends HeaderPanel<
if (this.pagingToggleIsVisible()) this.$pagingToggleButtons.show();
if (this.galleryIsVisible()) this.$galleryButton.show();
}
- }
+ };
+
+
}
+
diff --git a/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/css/styles.less b/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/css/styles.less
index 8c11982d8..c4584bad2 100644
--- a/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/css/styles.less
+++ b/src/content-handlers/iiif/modules/uv-pagingheaderpanel-module/css/styles.less
@@ -53,7 +53,7 @@
.search {
float: left;
- margin: 6px 0 0 5px;
+ margin: 6px 5px 0 5px;
width: 113px;
.searchText {
@@ -102,13 +102,22 @@
padding: 0;
margin-top: 6px;
color: black;
+
+ &.pageMode {
+ width: 113px;
+ text-align: center;
+ height: 25px;
+ margin-top: 1px;
+ }
+
}
.autocomplete {
position: absolute;
background: #fff;
- width: 60px;
- border: 2px solid @brand-primary-lighter;
+ width: 113px;
+ // border: 2px solid @brand-primary-lighter;
+ border: 0px;
list-style-type: none;
-webkit-margin-before: 0px;
-webkit-margin-after: 0px;
@@ -123,7 +132,8 @@
z-index: 1000;
.result {
- padding: 4px;
+ padding: 0 0 0 5px;
+ height: 25px;
width: 270px;
overflow: hidden;
@@ -137,6 +147,26 @@
&.selected {
background: @gray-lighter;
}
+
+ &:hover {
+ background: @gray-lighter;
+ }
+
+ a {
+ display: block;
+ height: 25px;
+ line-height: 2.2em;
+ border: 0;
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ &:active, &:focus {
+ outline: 0;
+ border: none;
+ }
+ }
}
}
}
diff --git a/src/content-handlers/iiif/modules/uv-shared-module/AutoComplete.ts b/src/content-handlers/iiif/modules/uv-shared-module/AutoComplete.ts
index 02b5e1720..d226fcd76 100644
--- a/src/content-handlers/iiif/modules/uv-shared-module/AutoComplete.ts
+++ b/src/content-handlers/iiif/modules/uv-shared-module/AutoComplete.ts
@@ -124,7 +124,7 @@ export class AutoComplete {
// if there are more than x chars
// update the autocomplete list.
- if (val && val.length > that._minChars && that._searchForWords(val)) {
+ if (val && val.length >= that._minChars && that._searchForWords(val)) {
that._search(val);
} else {
// otherwise, hide the autocomplete list.
@@ -166,7 +166,7 @@ export class AutoComplete {
}
private _getTerms(): string {
- return this._$element.val().trim();
+ return this._$element.val().trim();
}
private _setSelectedResultIndex(direction: number): void {
diff --git a/src/content-handlers/iiif/modules/uv-shared-module/NewAutoComplete.ts b/src/content-handlers/iiif/modules/uv-shared-module/NewAutoComplete.ts
new file mode 100644
index 000000000..71d85bbc4
--- /dev/null
+++ b/src/content-handlers/iiif/modules/uv-shared-module/NewAutoComplete.ts
@@ -0,0 +1,296 @@
+const $ = require("jquery");
+import * as KeyCodes from "@edsilv/key-codes";
+import { Keyboard } from "@edsilv/utils";
+import { isVisible } from "../../../../Utils";
+export class AutoComplete {
+ private _results: any;
+ private _selectedResultIndex: number;
+ private _$element: JQuery;
+ private _autoCompleteFunc: (
+ terms: string,
+ cb: (results: string[]) => void
+ ) => void;
+ private _delay: number;
+ private _minChars: number;
+ private _onSelect: (terms: string) => void;
+ private _parseResultsFunc: (results: string[]) => string[];
+ private _positionAbove: boolean;
+ private _allowWords: boolean;
+
+ private _$searchResultsList: JQuery;
+ private _$searchResultTemplate: JQuery;
+
+ constructor(
+ element: JQuery,
+ autoCompleteFunc: (terms: string, cb: (results: string[]) => void) => void,
+ parseResultsFunc: (results: any) => string[],
+ onSelect: (terms: string) => void,
+ delay: number = 300,
+ minChars: number = 2,
+ positionAbove: boolean = false,
+ allowWords: boolean = false
+ ) {
+ this._$element = element;
+ this._autoCompleteFunc = autoCompleteFunc;
+ this._delay = delay;
+ this._minChars = minChars;
+ this._onSelect = onSelect;
+ this._parseResultsFunc = parseResultsFunc;
+ this._positionAbove = positionAbove;
+ this._allowWords = allowWords;
+
+ // create ui.
+ this._$searchResultsList = $('
');
+
+ if (this._positionAbove) {
+ this._$element.parent().prepend(this._$searchResultsList);
+ } else {
+ this._$element.parent().append(this._$searchResultsList);
+ }
+
+ this._$searchResultTemplate = $(
+ ''
+ );
+
+ // init ui.
+
+ // callback after set period.
+ const typewatch = (function() {
+ let timer: number = 0;
+ return function(cb: Function, ms: number) {
+ clearTimeout(timer);
+ timer = setTimeout(cb, ms);
+ };
+ })();
+
+ const that = this;
+
+ this._$element.on("keydown", function(e: JQueryEventObject) {
+ const originalEvent: KeyboardEvent = e.originalEvent;
+ //that._lastKeyDownWasNavigation = that._isNavigationKeyDown(originalEvent);
+ const charCode: number = Keyboard.getCharCode(originalEvent);
+ let cancelEvent: boolean = false;
+
+ if (charCode === KeyCodes.KeyDown.LeftArrow) {
+ cancelEvent = true;
+ } else if (charCode === KeyCodes.KeyDown.RightArrow) {
+ cancelEvent = true;
+ }
+
+ if (cancelEvent) {
+ originalEvent.cancelBubble = true;
+ if (originalEvent.stopPropagation) originalEvent.stopPropagation();
+ }
+ });
+
+ // this._$element.on("blur", () => {
+ // that._clearResults();
+ // that._hideResults();
+ // });
+
+ // auto complete
+ this._$element.on("keyup", function(e) {
+ // if pressing enter without a list item selected
+ if (
+ !that._getSelectedListItem().length &&
+ e.keyCode === KeyCodes.KeyDown.Enter
+ ) {
+ // enter
+ that._onSelect(that._getTerms());
+ return;
+ }
+
+ if (e.keyCode === KeyCodes.KeyDown.Tab) {
+ return;
+ }
+
+ // If there are search results
+ if (isVisible(that._$searchResultsList) && that._results.length) {
+ if (e.keyCode === KeyCodes.KeyDown.Enter) {
+ that._searchForItem(that._getSelectedListItem());
+ } else if (e.keyCode === KeyCodes.KeyDown.DownArrow) {
+ that._setSelectedResultIndex(1);
+ return;
+ } else if (e.keyCode === KeyCodes.KeyDown.UpArrow) {
+ that._setSelectedResultIndex(-1);
+ return;
+ }
+ }
+
+ if (e.keyCode !== KeyCodes.KeyDown.Enter) {
+ // after a delay, show autocomplete list.
+ typewatch(() => {
+ const val = that._getTerms();
+
+ // if there are more than x chars
+ // update the autocomplete list.
+ if (val && val.length > that._minChars && that._searchForWords(val)) {
+ that._search(val);
+ } else {
+ // otherwise, hide the autocomplete list.
+ that._clearResults();
+ that._hideResults();
+ }
+ }, that._delay);
+ }
+ });
+
+ // hide results if clicked outside.
+ $(document).on("mouseup", (e) => {
+ if (this._$searchResultsList.parent().has($(e.target)[0]).length === 0) {
+ this._clearResults();
+ this._hideResults();
+ }
+ });
+
+ // hide results if focus moves on.
+ $(document).on("focusin", (e) => {
+ if (
+ this._$searchResultsList.has($(e.target)[0]).length === 0 &&
+ !this._$element.is($(e.target)[0])
+ ) {
+ this._clearResults();
+ this._hideResults();
+ }
+ });
+
+ this._hideResults();
+ }
+
+ private _searchForWords(search: string): boolean {
+ if (this._allowWords || !search.includes(" ")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private _getTerms(): string {
+ if (this._$element.val()) {
+ return this._$element.val().trim();
+ } else {
+ return ""
+ }
+
+ }
+
+ private _setSelectedResultIndex(direction: number): void {
+ let nextIndex: number;
+
+ if (direction === 1) {
+ nextIndex = this._selectedResultIndex + 1;
+ } else {
+ nextIndex = this._selectedResultIndex - 1;
+ }
+
+ const $items: JQuery = this._$searchResultsList.find("li");
+
+ if (nextIndex < 0) {
+ nextIndex = $items.length - 1;
+ } else if (nextIndex > $items.length - 1) {
+ nextIndex = 0;
+ }
+
+ this._selectedResultIndex = nextIndex;
+
+ $items.removeClass("selected");
+
+ const $selectedItem: JQuery = $items.eq(this._selectedResultIndex);
+
+ $selectedItem.addClass("selected");
+
+ const top = $selectedItem.outerHeight(true) * this._selectedResultIndex;
+
+ this._$searchResultsList.scrollTop(top);
+ }
+
+ private _search(term: string): void {
+ this._results = [];
+
+ this._clearResults();
+ this._showResults();
+ this._$searchResultsList.append('');
+
+ this._updateListPosition();
+
+ const that = this;
+
+ this._autoCompleteFunc(term, (results: string[]) => {
+ that._listResults(results);
+ });
+ }
+
+ private _clearResults(): void {
+ this._$searchResultsList.empty();
+ }
+
+ private _hideResults(): void {
+ this._$searchResultsList.hide();
+ }
+
+ private _showResults(): void {
+ this._selectedResultIndex = -1;
+ this._$searchResultsList.show();
+ }
+
+ private _updateListPosition(): void {
+ if (this._positionAbove) {
+ this._$searchResultsList.css({
+ top: this._$searchResultsList.outerHeight(true) * -1,
+ });
+ } else {
+ this._$searchResultsList.css({
+ top: this._$element.outerHeight(true),
+ });
+ }
+ }
+
+ private _listResults(results: string[]): void {
+ // get an array of strings
+ this._results = this._parseResultsFunc(results);
+
+ this._clearResults();
+
+ if (!this._results.length) {
+ // don't do this, because there still may be results for the PHRASE but not the word.
+ // they won't know until they do the search.
+ //this.searchResultsList.append('no results');
+ this._hideResults();
+ return;
+ }
+
+ for (let i = 0; i < this._results.length; i++) {
+ const result = this._results[i];
+ const $resultItem = this._$searchResultTemplate.clone();
+ const $a = $resultItem.find("a");
+ $a.text(result);
+ this._$searchResultsList.append($resultItem);
+ }
+
+ this._updateListPosition();
+
+ const that = this;
+
+ const $listItems = this._$searchResultsList.find("li");
+
+ $listItems.each((_idx, item) => {
+ $(item).on("click", function(e: any) {
+ e.preventDefault();
+ that._searchForItem($(this));
+ });
+ });
+ }
+
+ private _searchForItem($item: JQuery): void {
+ const term: string = $item.find("a").text();
+ this._$element.val(term);
+ this._hideResults();
+ this._onSelect(term);
+ this._clearResults();
+ this._hideResults();
+ }
+
+ private _getSelectedListItem() {
+ return this._$searchResultsList.find("li.selected");
+ }
+}
diff --git a/src/content-handlers/iiif/modules/uv-shared-module/PagingAutoComplete.ts b/src/content-handlers/iiif/modules/uv-shared-module/PagingAutoComplete.ts
new file mode 100644
index 000000000..59c6cc0b1
--- /dev/null
+++ b/src/content-handlers/iiif/modules/uv-shared-module/PagingAutoComplete.ts
@@ -0,0 +1,296 @@
+const $ = require("jquery");
+import * as KeyCodes from "@edsilv/key-codes";
+import { Keyboard } from "@edsilv/utils";
+import { isVisible } from "../../../../Utils";
+
+export class PagingAutoComplete {
+ private _results: any;
+ private _selectedResultIndex: number;
+ private _$element: JQuery;
+ private _autoCompleteFunc: (
+ terms: string,
+ cb: (results: string[]) => void
+ ) => void;
+ private _delay: number;
+ private _minChars: number;
+ private _onSelect: (terms: string) => void;
+ private _parseResultsFunc: (results: string[]) => string[];
+ private _positionAbove: boolean;
+ private _allowWords: boolean;
+
+ private _$searchResultsList: JQuery;
+ private _$searchResultTemplate: JQuery;
+
+ constructor(
+ element: JQuery,
+ autoCompleteFunc: (terms: string, cb: (results: string[]) => void) => void,
+ parseResultsFunc: (results: any) => string[],
+ onSelect: (terms: string) => void,
+ delay: number = 300,
+ minChars: number = 2,
+ positionAbove: boolean = false,
+ allowWords: boolean = false,
+ ) {
+ this._$element = element;
+ this._autoCompleteFunc = autoCompleteFunc;
+ this._delay = delay;
+ this._minChars = minChars;
+ this._onSelect = onSelect;
+ this._parseResultsFunc = parseResultsFunc;
+ this._positionAbove = positionAbove;
+ this._allowWords = allowWords;
+
+ // create ui.
+ this._$searchResultsList = $('');
+
+ if (this._positionAbove) {
+ this._$element.parent().prepend(this._$searchResultsList);
+ } else {
+ this._$element.parent().append(this._$searchResultsList);
+ }
+
+ this._$searchResultTemplate = $(
+ ''
+ );
+
+ // init ui.
+
+ // callback after set period.
+ const typewatch = (function() {
+ let timer: number = 0;
+ return function(cb: Function, ms: number) {
+ clearTimeout(timer);
+ timer = setTimeout(cb, ms);
+ };
+ })();
+
+ const that = this;
+
+ this._$element.on("keydown", function(e: JQueryEventObject) {
+ const originalEvent: KeyboardEvent = e.originalEvent;
+ //that._lastKeyDownWasNavigation = that._isNavigationKeyDown(originalEvent);
+ const charCode: number = Keyboard.getCharCode(originalEvent);
+ let cancelEvent: boolean = false;
+
+ if (charCode === KeyCodes.KeyDown.LeftArrow) {
+ cancelEvent = true;
+ } else if (charCode === KeyCodes.KeyDown.RightArrow) {
+ cancelEvent = true;
+ }
+
+ if (cancelEvent) {
+ originalEvent.cancelBubble = true;
+ if (originalEvent.stopPropagation) originalEvent.stopPropagation();
+ }
+ });
+
+ // this._$element.on("blur", () => {
+ // that._clearResults();
+ // that._hideResults();
+ // });
+
+ // auto complete
+ this._$element.on("keyup", function(e) {
+ // if pressing enter without a list item selected
+ if (
+ !that._getSelectedListItem().length &&
+ e.keyCode === KeyCodes.KeyDown.Enter
+ ) {
+ // enter
+ that._onSelect(that._getTerms());
+ return;
+ }
+
+ if (e.keyCode === KeyCodes.KeyDown.Tab) {
+ that._clearResults();
+ that._hideResults();
+ return;
+ }
+
+ // If there are search results
+ if (isVisible(that._$searchResultsList) && that._results.length) {
+ if (e.keyCode === KeyCodes.KeyDown.Enter) {
+ that._searchForItem(that._getSelectedListItem());
+ } else if (e.keyCode === KeyCodes.KeyDown.DownArrow) {
+ that._setSelectedResultIndex(1);
+ return;
+ } else if (e.keyCode === KeyCodes.KeyDown.UpArrow) {
+ that._setSelectedResultIndex(-1);
+ return;
+ }
+ }
+
+ if (e.keyCode !== KeyCodes.KeyDown.Enter) {
+ // after a delay, show autocomplete list.
+ typewatch(() => {
+ const val = that._getTerms();
+ that._search(val)
+
+ // if there are more than x chars
+ // update the autocomplete list.
+ if (val && val.length > that._minChars && that._searchForWords(val)) {
+ that._search(val);
+ } else {
+ // otherwise, hide the autocomplete list.
+ that._clearResults();
+ that._hideResults();
+ }
+ }, that._delay);
+ }
+ });
+
+ //empty input if clicked/focussed on (and give all canvase labels as results)
+ this._$element.on("focusin", (e) => {
+ this._$element.val('');
+ this._search('')
+ });
+
+ this._$element.parent().on("blur", (e) => {
+ this._clearResults();
+ this._hideResults();
+ });
+
+ // // hide results if clicked outside.
+ $(document).on("click", (e) => {
+ if (this._$searchResultsList.parent().has($(e.target)[0]).length === 0) {
+ this._clearResults();
+ this._hideResults();
+ }
+ });
+
+
+ }
+
+ private _searchForWords(search: string): boolean {
+ if (this._allowWords || !search.includes(" ")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private _getTerms(): string {
+ return this._$element.val().trim();
+ }
+
+ private _setSelectedResultIndex(direction: number): void {
+ let nextIndex: number;
+
+ if (direction === 1) {
+ nextIndex = this._selectedResultIndex + 1;
+ } else {
+ nextIndex = this._selectedResultIndex - 1;
+ }
+
+ const $items: JQuery = this._$searchResultsList.find("li");
+
+ if (nextIndex < 0) {
+ nextIndex = $items.length - 1;
+ } else if (nextIndex > $items.length - 1) {
+ nextIndex = 0;
+ }
+
+ this._selectedResultIndex = nextIndex;
+
+ $items.removeClass("selected");
+
+ const $selectedItem: JQuery = $items.eq(this._selectedResultIndex);
+
+ $selectedItem.addClass("selected");
+
+ const top = $selectedItem.outerHeight(true) * this._selectedResultIndex;
+
+ this._$searchResultsList.scrollTop(top);
+ }
+
+ private _search(term: string): void {
+ this._results = [];
+
+ this._clearResults();
+ this._showResults();
+ this._$searchResultsList.append('');
+
+ this._updateListPosition();
+
+ const that = this;
+
+ this._autoCompleteFunc(term, (results: string[]) => {
+ that._listResults(results);
+ });
+ }
+
+ private _clearResults(): void {
+ this._$searchResultsList.empty();
+ }
+
+ private _hideResults(): void {
+ this._$searchResultsList.hide();
+ }
+
+ private _showResults(): void {
+ this._selectedResultIndex = -1;
+ this._$searchResultsList.show();
+ }
+
+ private _updateListPosition(): void {
+ if (this._positionAbove) {
+ this._$searchResultsList.css({
+ top: this._$searchResultsList.outerHeight(true) * -1,
+ });
+ } else {
+ this._$searchResultsList.css({
+ top: this._$element.outerHeight(true),
+ });
+ }
+ }
+
+ private _listResults(results: string[]): void {
+ // get an array of strings
+ this._results = this._parseResultsFunc(results);
+
+ this._clearResults();
+
+ if (!this._results.length) {
+ // don't do this, because there still may be results for the PHRASE but not the word.
+ // they won't know until they do the search.
+ //this.searchResultsList.append('no results');
+ this._hideResults();
+ return;
+ }
+
+ for (let i = 0; i < this._results.length; i++) {
+ const result = this._results[i];
+ const $resultItem = this._$searchResultTemplate.clone();
+ const $a = $resultItem.find("a");
+ $a.text(result);
+ this._$searchResultsList.append($resultItem);
+ }
+
+ this._updateListPosition();
+
+ const that = this;
+
+ const $listItems = this._$searchResultsList.find("li");
+
+ $listItems.each((_idx, item) => {
+ $(item).on("click", function(e: any) {
+ e.preventDefault();
+ that._searchForItem($(this));
+ });
+ });
+ }
+
+ private _searchForItem($item: JQuery): void {
+ const term: string = $item.find("a").text();
+ this._$element.val(term);
+ this._hideResults();
+ this._onSelect(term);
+ this._clearResults();
+ this._hideResults();
+ }
+
+ private _getSelectedListItem() {
+ return this._$searchResultsList.find("li.selected");
+ }
+
+}