├── LICENSE
├── README.md
└── SPO-Search-Improvements
├── .gitignore
├── package.json
├── readme.md
├── screenshots
├── example.png
├── synonym-test1.png
├── synonym-test2-without-code.png
└── synonym-test2.png
├── search.queryVariableInjector.js
├── search.queryVariableInjector.min.js
├── search.queryVariableInjector.ts
├── synonym-list.png
├── tsconfig.json
├── typings
├── SharePoint
│ └── SharePoint.d.ts
├── microsoft-ajax
│ └── microsoft.ajax.d.ts
├── pluralize
│ └── pluralize.d.ts
└── q
│ └── q.d.ts
└── webpack.config.js
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 |
4 | Copyright (c) 2014 #SPCSR
5 | Individual Authors annotated in each script.
6 |
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 |
16 | The above copyright notice and this permission notice shall be included in all
17 | copies or substantial portions of the Software.
18 |
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HelperFunctions
2 | ===============
3 |
4 | This repository is used to house help function style javascript libraries that can be used in Display Templates and other CSR scripts
5 |
--------------------------------------------------------------------------------
/SPO-Search-Improvements/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
--------------------------------------------------------------------------------
/SPO-Search-Improvements/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sharepoint-search-synonyms",
3 | "version": "1.0.0",
4 | "description": "This is a script for SPO to support search synonyms",
5 | "main": "search.queryVariableInjector.ts",
6 | "author": "Mikael Svenson & Elio Struyf",
7 | "license": "ISC",
8 | "dependencies": {
9 | "pluralize": "2.0.0",
10 | "q": "1.4.1",
11 | "ts-loader": "0.8.2",
12 | "typescript": "1.8.10",
13 | "webpack": "1.14.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SPO-Search-Improvements/readme.md:
--------------------------------------------------------------------------------
1 | # SharePoint Online / 2013 / 2016 - User Profile Properties, Noise Words, and Synonyms
2 | ## Description
3 | The query system in SharePoint Search allows for the expansion of query variables query time to create contextual search experiences. Query variables are used in the query templates, specified in web parts, result sources or in query rules.
4 |
5 | > An overview of query variables can be found at [S15E03 Query Variables – Constant Trouble](http://www.techmikael.com/2014/05/s15e03-query-variables-constant-trouble.html).
6 |
7 | While the query system is quite flexible it has some shortcomings. This project aims to solve the below scenarios, as well as to provide sample code on how you can intercept the query cycle on a SharePoint
8 | page to inject your own asynchronous custom logic before the search query is sent to the server for execution.
9 |
10 | 1. Synonym expansion without the need for query rules (which becomes very unmanageable over time)
11 | 1. Alternative expansion of {User.} variables without a negative impact on SharePoint servers
12 | 1. Remove custom noise words from the query
13 | 1. Load business data asynchronously to be used in custom query variables
14 | 1. Add a mechanism to trigger query rules by user segments with client side code [Server approach MSDN](https://msdn.microsoft.com/en-us/library/office/jj870831.aspx)
15 |
16 | > **Note:** In order to inject data before the first search query, the web part must run in asynchronous mode to allow this script to load and intercept.
17 |
18 | ## Installation
19 | 1. Clone this repo
20 | 2. Open a command prompt
21 | 3. Navigate to your folder
22 | 4. Execute
23 | ``
24 | npm install
25 | ``
26 |
27 | **Important**: You need to have webpack installed - ``npm install webpack -p``
28 |
29 | ## Compile the code
30 | 1. Run ``webpack`` or ``webpack -p`` (if you want the minimized version)
31 |
32 | ## Script configuration
33 | With the script you are able to automatically retrieve all the user profile properties, remove noise words from the query and search for synonyms.
34 |
35 | You have the option to define which type of actions you want to include in your environment:
36 | ```javascript
37 | // Retrieve all user profile properties
38 | const GetUserProfileProperties = true;
39 | // Query and show synonyms
40 | const ShowSynonyms = true;
41 | // Remove noise words from your search queries
42 | const RemoveNoiseWords = true;
43 | // Add custom date variables
44 | const UseDateVariables = true;
45 | // Synonym list title
46 | const SynonymsList = 'Synonyms';
47 | // Empty array runs on all web parts or add the name of the query group.
48 | // See https://skodvinhvammen.wordpress.com/2015/11/30/how-to-connect-search-result-webparts-using-query-groups/
49 | const RunOnWebParts = [];
50 | // Names of weekdays
51 | const Weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
52 | // Names of months
53 | const Months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
54 |
55 | ```
56 | # List of added query variables
57 |
58 | Query variable| Description|
59 | ------------- | -------------|
60 | {SynonymQuery} | Re-written query to include synonyms in addition to the original query|
61 | {Synonyms} | Variable containing all the expanded synonyms|
62 | {spcsrUser.<property>} |The property can be any user profile property synchronized to the hidden user information list in SharePoint (*/_catalogs/users/simple.aspx*). The following properties should be available by default:
- Name
- Account
- WorkEmail
- MobilePhone
- AboutMe
- SIPAddress
- Department
- Title
- FirstName
- LastName
- WorkPhone
- UserName
- AskMeAbout
- Office
Multi value properties should be expanded as suchusing the **{|...}** syntax, for example **{|{spcsrUser.AskMeAbout}}**
63 | {Date} | Date of the month 1-31
64 | {UTCDate} | Date of the month 1-31, based on the UTC time zone
65 | {WeekDay}| Name of weekday in English *(can be edited in the file)*
66 | {UTCWeekDay}| Name of weekday in English *(can be edited in the file)*, based on the UTC time zone
67 | {Hours}| Hour of the day 0-23
68 | {UTCHours}| Hour of the day 0-23, based on the UTC time zone
69 | {Month}| Name of the month in English *(can be edited in the file)*
70 | {UTCMonth}| Name of the month in English *(can be edited in the file)*, based on the UTC time zone
71 | {MonthNumber}| Number of the month 1-12
72 | {UTCMonthNumber}|Number of the month 1-12, based on the UTC time zone
73 | {Year}| Four digit year
74 | {UTCYear}| Four digit year, based on the UTC time zone
75 | {Week}| Week number according to ISO-8601
76 | {UTCWeek}| Week number according to ISO-8601, based on the UTC time zone
77 |
78 | # Synonyms
79 | In SharePoint Online the only option you have for synonym expansion is to use query rules which works, but turn into a very tedious task. Also, if you use search operators as part of the query, query rules will not trigger at all.
80 |
81 | Using a SharePoint list to handle synonyms makes more sense from a maintenance perspective, and our solution is also more robust.
82 |
83 | What happens when the page loads is that it will read all the synonyms from the list, and for each query it will add the re-written query to a query variable **{SynonymQuery}** with the synonyms itself in **{Synonyms}**.
84 |
85 | ## Creating the Synonyms List
86 | Create a **Synonyms** (list name) SharePoint list with the following fields: **Title**, **Synonym** (multiple lines without markup), **TwoWay** (yes/no).
87 |
88 | 
89 |
90 | **Important**: insert the synonyms comma seperated like you can see in the screenshot.
91 |
92 | ### Info
93 | By default if you create a thesaurus csv for SharePoint and want to make the synonym work in both ways, you have to specify multiple entries.
94 |
95 | ```
96 | Key,Synonym,Language
97 | HR,Human Resources
98 | ```
99 |
100 | In this example, if you search on HR you will also get results for Human Resouces, but not the other way around. In order to get that working you have to specify it like this:
101 |
102 | ```
103 | Key,Synonym,Language
104 | HR,Human Resources
105 | Human Resources,HR
106 | ```
107 |
108 | The **TwoWay** field is in place to solve this issue so that you only have to specify one rule for each synony. So when the synonym should work in both ways, you set the field to **yes**. This also works when you enter multiple values in the Synonym field.
109 |
110 | ## Usage
111 | 1. Upload the file to your SharePoint Site.
112 | 2. Copy the file reference
113 | 3. On each search page, add a script editor web part
114 | 4. Specify the script reference in the web part ````
115 | 5. Edit the **Search Results Web Part** and click on **Change query**
116 | 6. Replace **{SearchBoxQuery}** with **{SynonymQuery}**
117 | 7. Go to the **Settings** tab and set the **loading behavior** to **Async option: Issue query from the browser**
118 | 8. Store these settings and publish the page
119 |
120 | ## Result
121 | If I do a search query for **mp** on my environment, I should also get results for **managed property**.
122 |
123 | 
124 |
125 | # TRigger query rules on User Segments
126 | <TODO>
127 |
128 | #Technical details
129 | In order to modify a SharePoint search query before it's being executed you need to hook in your logic at the right stage in the pages JavaScript lifecycle.
130 | This is achieved with the following code snippet:
131 |
132 | ```javascript
133 | function hookCustomQueryVariables() {
134 | // Override both executeQuery and executeQueries
135 | Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQuery = function (query) {
136 | loadDataAndSearch();
137 | return new SP.JsonObjectResult();
138 | };
139 | Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQueries = function (queryIds, queries, handleExceptions) {
140 | loadDataAndSearch();
141 | return new SP.JsonObjectResult();
142 | };
143 | // Highlight synonyms and remove noise
144 | Srch.U.getHighlightedProperty = function (itemId, crntItem, mp) {
145 | return setSynonymHighlighting(itemId, crntItem, mp);
146 | };
147 | }
148 | ExecuteOrDelayUntilBodyLoaded(function () {
149 | Sys.Application.add_init(hookCustomQueryVariables);
150 | });
151 | ```
152 | **ExecuteOrDelayUntilBodyLoaded** is first in the life cycle, and ensures our script runs before the SharePoint search web parts. Then we override the single and multi-query functions which allows us to stop the query cycle and perform any asynchronous loading operation before the query continues.
153 |
154 | Included is the loading of a users profile properties and synonyms from a list, but this could be a call out to any system which have information you need when crafting a search query.
155 |
156 | # Credits
157 | Thank you [Mikael Svenson](https://twitter.com/mikaelsvenson) for creating the initial script, and to [Elio Struyf](https://twitter.com/eliostruyf) for doing the synonym list implementation.
158 |
--------------------------------------------------------------------------------
/SPO-Search-Improvements/screenshots/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPCSR/HelperFunctions/cd3fd014be01c05320ed9b81558bac2df2f1d23b/SPO-Search-Improvements/screenshots/example.png
--------------------------------------------------------------------------------
/SPO-Search-Improvements/screenshots/synonym-test1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPCSR/HelperFunctions/cd3fd014be01c05320ed9b81558bac2df2f1d23b/SPO-Search-Improvements/screenshots/synonym-test1.png
--------------------------------------------------------------------------------
/SPO-Search-Improvements/screenshots/synonym-test2-without-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPCSR/HelperFunctions/cd3fd014be01c05320ed9b81558bac2df2f1d23b/SPO-Search-Improvements/screenshots/synonym-test2-without-code.png
--------------------------------------------------------------------------------
/SPO-Search-Improvements/screenshots/synonym-test2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SPCSR/HelperFunctions/cd3fd014be01c05320ed9b81558bac2df2f1d23b/SPO-Search-Improvements/screenshots/synonym-test2.png
--------------------------------------------------------------------------------
/SPO-Search-Improvements/search.queryVariableInjector.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
40 | /******/ }
41 | /******/ };
42 | /******/
43 | /******/ // define __esModule on exports
44 | /******/ __webpack_require__.r = function(exports) {
45 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
46 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
47 | /******/ }
48 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
49 | /******/ };
50 | /******/
51 | /******/ // create a fake namespace object
52 | /******/ // mode & 1: value is a module id, require it
53 | /******/ // mode & 2: merge all properties of value into the ns
54 | /******/ // mode & 4: return value when already ns object
55 | /******/ // mode & 8|1: behave like require
56 | /******/ __webpack_require__.t = function(value, mode) {
57 | /******/ if(mode & 1) value = __webpack_require__(value);
58 | /******/ if(mode & 8) return value;
59 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
60 | /******/ var ns = Object.create(null);
61 | /******/ __webpack_require__.r(ns);
62 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
63 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
64 | /******/ return ns;
65 | /******/ };
66 | /******/
67 | /******/ // getDefaultExport function for compatibility with non-harmony modules
68 | /******/ __webpack_require__.n = function(module) {
69 | /******/ var getter = module && module.__esModule ?
70 | /******/ function getDefault() { return module['default']; } :
71 | /******/ function getModuleExports() { return module; };
72 | /******/ __webpack_require__.d(getter, 'a', getter);
73 | /******/ return getter;
74 | /******/ };
75 | /******/
76 | /******/ // Object.prototype.hasOwnProperty.call
77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
78 | /******/
79 | /******/ // __webpack_public_path__
80 | /******/ __webpack_require__.p = "";
81 | /******/
82 | /******/
83 | /******/ // Load entry module and return exports
84 | /******/ return __webpack_require__(__webpack_require__.s = 0);
85 | /******/ })
86 | /************************************************************************/
87 | /******/ ([
88 | /* 0 */
89 | /***/ (function(module, exports, __webpack_require__) {
90 |
91 | "use strict";
92 | ///
93 | ///
94 | ///
95 | /*
96 |
97 | Authors: Mikael Svenson - Elio Struyf
98 | Twitter: @mikaelsvenson - @eliostruyf
99 |
100 | Description
101 | -----------
102 |
103 | Script which hooks into the query execution flow of a page using search web parts to inject custom query variables using JavaScript
104 |
105 | You can attach this script on any page with a script editor web part, content editor web part, custom action or similar.
106 |
107 | Usecase 1 - Static variables
108 | ----------------------------
109 | Any variable which is persistant for the user across sessions should be loaded
110 |
111 |
112 |
113 |
114 |
115 | Query
116 | -----------
117 | The following query has to be set in the search web part:
118 |
119 | {SynonymQuery}
120 |
121 | */
122 |
123 | Object.defineProperty(exports, "__esModule", { value: true });
124 | /*****************************************************************************
125 | * The following variables can be used to configure the script to your needs *
126 | *****************************************************************************/
127 | var GetUserProfileProperties = false;
128 | var ShowSynonyms = true;
129 | var RemoveNoiseWords = true;
130 | var UseDateVariables = true;
131 | var SynonymsList = 'Synonyms';
132 | var RunOnWebParts = []; //Empty array runs on all web parts, if not add the name of the query group - See https://skodvinhvammen.wordpress.com/2015/11/30/how-to-connect-search-result-webparts-using-query-groups/
133 | var Weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
134 | var Months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
135 | var Q = __webpack_require__(1);
136 | var pluralize = __webpack_require__(6);
137 | var spcsr;
138 | (function (spcsr) {
139 | var Search;
140 | (function (Search) {
141 | var VariableInjection;
142 | (function (VariableInjection) {
143 | var _loading = false;
144 | var _userDefinedVariables = {};
145 | var _synonymTable = {};
146 | var _dataProviders = [];
147 | var _processedIds = [];
148 | var _origExecuteQuery;
149 | var _origExecuteQueries;
150 | var _getHighlightedProperty = Srch.U.getHighlightedProperty;
151 | var _siteUrl = _spPageContextInfo.webAbsoluteUrl;
152 | var PROP_SYNONYMQUERY = "SynonymQuery";
153 | var PROP_SYNONYM = "Synonyms";
154 | var HIGHLIGHTED_PROPERTIES = 'HitHighlightedProperties';
155 | var HIGHLIGHTED_SUMMARY = 'HitHighlightedSummary';
156 | var NOISE_WORDS = "about,after,all,also,an,another,any,are,as,at,be,because,been,before,being,between,both,but,by,came,can,come,could,did,do,each,for,from,get,got,has,had,he,have,her,here,him,himself,his,how,if,in,into,is,it,like,make,many,me,might,more,most,much,must,my,never,now,of,on,only,other,our,out,over,said,same,see,should,since,some,still,such,take,than,that,the,their,them,then,there,these,they,this,those,through,to,too,under,up,very,was,way,we,well,were,what,where,which,while,who,with,would,you,your,a".split(',');
157 | // Load user poperties and synonyms
158 | function loadDataAndSearch() {
159 | if (!_loading) {
160 | _loading = true;
161 | // run all async code needed to pull in data for variables
162 | Q.all([loadSynonyms(), loadUserVariables()]).done(function () {
163 | // add date variables
164 | if (UseDateVariables) {
165 | setDateVariables();
166 | }
167 | // set loaded data as custom query variables
168 | injectCustomQueryVariables();
169 | // reset to original function
170 | Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQuery = _origExecuteQuery;
171 | Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQueries = _origExecuteQueries;
172 | // re-issue query for the search web parts
173 | for (var i = 0; i < _dataProviders.length; i++) {
174 | // complete the intercepted event
175 | _dataProviders[i].raiseResultReadyEvent(new Srch.ResultEventArgs(_dataProviders[i].get_initialQueryState()));
176 | // re-issue query
177 | _dataProviders[i].issueQuery();
178 | }
179 | });
180 | }
181 | }
182 | function splitSynonyms(value) {
183 | return value.split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
184 | }
185 | // Function to load synonyms asynchronous - poor mans synonyms
186 | function loadSynonyms() {
187 | var defer = Q.defer();
188 | // Check if the code has to retrieve synonyms
189 | if (!ShowSynonyms) {
190 | defer.resolve();
191 | return defer.promise;
192 | }
193 | var urlSynonymsList = _siteUrl + "/_api/Web/Lists/getByTitle('" + SynonymsList + "')/Items?$top=1000&$select=Title,Synonym,TwoWay";
194 | var req = new XMLHttpRequest();
195 | req.onreadystatechange = function () {
196 | if (this.readyState === 4) {
197 | if (this.status === 200) {
198 | var data = JSON.parse(this.responseText);
199 | if (typeof data.d !== 'undefined') {
200 | if (typeof data.d.results === 'undefined') {
201 | defer.reject(null);
202 | }
203 | var results = data.d.results;
204 | if (results.length) {
205 | var _loop_1 = function (i) {
206 | var item = results[i];
207 | if (item.TwoWay) {
208 | var synonyms = splitSynonyms(item.Synonym);
209 | // Set the default synonym
210 | _synonymTable[item.Title.toLowerCase()] = synonyms;
211 | // Loop over the list of synonyms
212 | var tmpSynonyms_1 = synonyms;
213 | tmpSynonyms_1.push(item.Title.toLowerCase().trim());
214 | synonyms.forEach(function (s) {
215 | _synonymTable[s.toLowerCase().trim()] = tmpSynonyms_1.filter(function (fItem) { return fItem !== s; });
216 | });
217 | }
218 | else {
219 | // Set a single synonym
220 | _synonymTable[item.Title.toLowerCase()] = splitSynonyms(item.Synonym);
221 | }
222 | };
223 | for (var i = 0; i < results.length; i++) {
224 | _loop_1(i);
225 | }
226 | }
227 | }
228 | defer.resolve();
229 | }
230 | else if (this.status >= 400) {
231 | //console.error("getJSON failed, status: " + this.textStatus + ", error: " + this.error);
232 | defer.reject(this.statusText);
233 | }
234 | }
235 | };
236 | req.open('GET', urlSynonymsList, true);
237 | req.setRequestHeader('Accept', 'application/json;odata=verbose');
238 | req.setRequestHeader("Content-type", "application/json;odata=verbose");
239 | req.send();
240 | return defer.promise;
241 | }
242 | function formatSynonym(value) {
243 | value = value.trim().replace(/"/g, '').trim();
244 | value = '"' + value + '"';
245 | return value;
246 | }
247 | function formatSynonymsSearchQuery(items) {
248 | var result = '';
249 | for (var i = 0; i < items.length; i++) {
250 | var item = items[i];
251 | if (item.length > 0) {
252 | item = formatSynonym(item);
253 | result += item;
254 | if (i < items.length - 1) {
255 | result += ' OR ';
256 | }
257 | }
258 | }
259 | return result;
260 | }
261 | // Function to inject synonyms at run-time
262 | function processCustomQuery(query, dataProvider) {
263 | // Remove complex query parts AND/OR/NOT/ANY/ALL/parenthasis/property queries/exclusions - can probably be improved
264 | var cleanQuery = query.replace(/(-\w+)|(-"\w+.*?")|(-?\w+[:=<>]+\w+)|(-?\w+[:=<>]+".*?")|((\w+)?\(.*?\))|(AND)|(OR)|(NOT)/g, '');
265 | var queryParts = cleanQuery.match(/("[^"]+"|[^"\s]+)/g);
266 | var synonyms = [];
267 | // code which should modify the current query based on context for each new query
268 | if (ShowSynonyms) {
269 | if (queryParts) {
270 | for (var i = 0; i < queryParts.length; i++) {
271 | var key = queryParts[i].toLowerCase();
272 | var value = _synonymTable[key];
273 | if (value) {
274 | // Replace the current query part in the query with all the synonyms
275 | query = query.replace(queryParts[i], String.format('({0} OR {1})', formatSynonym(queryParts[i]), formatSynonymsSearchQuery(value)));
276 | synonyms.push(value);
277 | }
278 | }
279 | }
280 | }
281 | // remove noise words from the search query
282 | if (RemoveNoiseWords) {
283 | // Call function to remove the noise words from the search query
284 | query = replaceNoiseWords(query);
285 | }
286 | // Update the keyword query
287 | dataProvider.get_properties()[PROP_SYNONYMQUERY] = query;
288 | dataProvider.get_properties()[PROP_SYNONYM] = synonyms;
289 | }
290 | // Function that replaces the noise words with nothing
291 | function replaceNoiseWords(query) {
292 | var t = NOISE_WORDS.length;
293 | while (t--) {
294 | query = query.replace(new RegExp('\\b' + NOISE_WORDS[t] + '\\b', "ig"), '');
295 | }
296 | return query.trim();
297 | }
298 | // Sample function to load user variables asynchronous
299 | function loadUserVariables() {
300 | var defer = Q.defer();
301 | // Check if the code has to retrieve the user profile properties
302 | if (!GetUserProfileProperties) {
303 | defer.resolve();
304 | return defer.promise;
305 | }
306 | SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {
307 | // Query user hidden list - not accessible via REST
308 | // If you want TERM guid's you need to mix and match the use of UserProfileManager and TermStore and cache client side
309 | var urlCurrentUser = _siteUrl + "/_vti_bin/listdata.svc/UserInformationList?$filter=Id eq " + _spPageContextInfo.userId;
310 | var req = new XMLHttpRequest();
311 | req.onreadystatechange = function () {
312 | if (this.readyState === 4) {
313 | if (this.status === 200) {
314 | var data = JSON.parse(this.responseText);
315 | var user = data['d']['results'][0];
316 | for (var property in user) {
317 | if (user.hasOwnProperty(property)) {
318 | var val = user[property];
319 | if (typeof val == "number") {
320 | //console.log(property + " : " + val);
321 | _userDefinedVariables["spcsrUser." + property] = val;
322 | }
323 | else if (typeof val == "string") {
324 | //console.log(property + " : " + val);
325 | _userDefinedVariables["spcsrUser." + property] = val.split(/[\s,]+/);
326 | }
327 | }
328 | }
329 | defer.resolve();
330 | }
331 | else if (this.status >= 400) {
332 | //console.error("getJSON failed, status: " + this.textStatus + ", error: " + this.error);
333 | defer.reject(this.statusText);
334 | }
335 | }
336 | };
337 | req.open('GET', urlCurrentUser, true);
338 | req.setRequestHeader('Accept', 'application/json;odata=verbose');
339 | req.setRequestHeader("Content-type", "application/json;odata=verbose");
340 | req.send();
341 | });
342 | return defer.promise;
343 | }
344 | function getWeekNumber(d) {
345 | d.setHours(0, 0, 0);
346 | d.setDate(d.getDate() + 4 - (d.getDay() || 7));
347 | return Math.ceil((((d.getTime() - new Date(d.getFullYear(), 0, 1).getTime()) / 8.64e7) + 1) / 7);
348 | }
349 | ;
350 | // More date variables
351 | function setDateVariables() {
352 | var today = new Date();
353 | _userDefinedVariables["Date"] = today.getDate();
354 | _userDefinedVariables["UTCDate"] = today.getUTCDate();
355 | _userDefinedVariables["WeekDay"] = Weekdays[today.getDay()];
356 | _userDefinedVariables["UTCWeekDay"] = Weekdays[today.getUTCDay()];
357 | _userDefinedVariables["Hours"] = today.getHours();
358 | _userDefinedVariables["UTCHours"] = today.getUTCHours();
359 | _userDefinedVariables["Month"] = Months[today.getMonth()];
360 | _userDefinedVariables["UTCMonth"] = Months[today.getUTCMonth()];
361 | _userDefinedVariables["MonthNumber"] = today.getMonth() + 1;
362 | _userDefinedVariables["UTCMonthNumber"] = today.getUTCMonth() + 1;
363 | _userDefinedVariables["Year"] = today.getFullYear();
364 | _userDefinedVariables["UTCYear"] = today.getUTCFullYear();
365 | _userDefinedVariables["Week"] = getWeekNumber(today);
366 | var utcDate = new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), today.getUTCHours());
367 | _userDefinedVariables["UTCWeek"] = getWeekNumber(utcDate);
368 | }
369 | function shouldProcessGroup(group) {
370 | if (RunOnWebParts.length === 0)
371 | return true;
372 | if (RunOnWebParts.indexOf(group) != -1)
373 | return true;
374 | if (RunOnWebParts.indexOf(group.toLowerCase()) != -1)
375 | return true;
376 | if (RunOnWebParts.indexOf(group.toUpperCase()) != -1)
377 | return true;
378 | return false;
379 | }
380 | // Function to inject custom variables on page load
381 | function injectCustomQueryVariables() {
382 | var queryGroups = Srch.ScriptApplicationManager.get_current().queryGroups;
383 | for (var group in queryGroups) {
384 | if (queryGroups.hasOwnProperty(group) && shouldProcessGroup(group)) {
385 | if (typeof queryGroups[group].dataProvider !== "undefined" && queryGroups[group].dataProvider !== null) {
386 | var dataProvider = queryGroups[group].dataProvider;
387 | var properties = dataProvider.get_properties();
388 | // add all user variables fetched and stored as spcsrUser.
389 | for (var prop in _userDefinedVariables) {
390 | if (_userDefinedVariables.hasOwnProperty(prop)) {
391 | properties[prop] = _userDefinedVariables[prop];
392 | }
393 | }
394 | // set hook for query time variables which can change
395 | dataProvider.add_queryIssuing(function (sender, e) {
396 | // Process query (remove noise words and add synonyms)
397 | processCustomQuery(e.queryState.k, sender);
398 | // reset the processed IDs
399 | _processedIds = [];
400 | });
401 | _dataProviders.push(dataProvider);
402 | }
403 | }
404 | }
405 | }
406 | // Function to add the synonym highlighting to the highlighted properties
407 | function setSynonymHighlighting(itemId, crntItem, mp) {
408 | var highlightedProp = crntItem[HIGHLIGHTED_PROPERTIES];
409 | var highlightedSummary = crntItem[HIGHLIGHTED_SUMMARY];
410 | // Check if ID is already processed
411 | if (_processedIds.indexOf(itemId) === -1) {
412 | var queryGroups = Srch.ScriptApplicationManager.get_current().queryGroups;
413 | for (var group in queryGroups) {
414 | if (queryGroups.hasOwnProperty(group) && shouldProcessGroup(group)) {
415 | if (typeof queryGroups[group].dataProvider !== "undefined" && queryGroups[group].dataProvider !== null) {
416 | var dataProvider = queryGroups[group].dataProvider;
417 | var properties = dataProvider.get_properties();
418 | // Check synonym custom property exists
419 | if (typeof properties[PROP_SYNONYM] !== 'undefined') {
420 | var crntSynonyms = properties[PROP_SYNONYM];
421 | // Loop over all the synonyms for the current query
422 | for (var i = 0; i < crntSynonyms.length; i++) {
423 | var crntSynonym = crntSynonyms[i];
424 | for (var j = 0; j < crntSynonym.length; j++) {
425 | var synonymVal = crntSynonym[j];
426 | // Remove quotes from the synonym
427 | synonymVal = synonymVal.replace(/['"]+/g, '');
428 | // Highlight synonyms and remove the noise words
429 | highlightedProp = highlightSynonyms(highlightedProp, synonymVal);
430 | highlightedSummary = highlightSynonyms(highlightedSummary, synonymVal);
431 | }
432 | }
433 | }
434 | // Remove the noise words
435 | highlightedProp = removeNoiseHighlightWords(highlightedProp);
436 | highlightedSummary = removeNoiseHighlightWords(highlightedSummary);
437 | _processedIds.push(itemId);
438 | }
439 | }
440 | }
441 | }
442 | crntItem[HIGHLIGHTED_PROPERTIES] = highlightedProp;
443 | crntItem[HIGHLIGHTED_SUMMARY] = highlightedSummary;
444 | // Call the original highlighting function
445 | return _getHighlightedProperty(itemId, crntItem, mp);
446 | }
447 | // Function that finds the synonyms and adds the required highlight tags
448 | function highlightSynonyms(prop, synVal) {
449 | if (!prop) {
450 | return;
451 | }
452 | // Only highlight synonyms when required
453 | if (ShowSynonyms) {
454 | // Remove all tags from the property value
455 | prop = prop.replace(//g, '');
456 | // Add the required tags to the highlighted properties
457 | var occurences_1 = prop.split(new RegExp('\\b' + synVal.toLowerCase() + '\\b', 'ig')).join('{replace}');
458 | if (occurences_1.indexOf('{replace}') !== -1) {
459 | // Retrieve all the matching values, this is important to display the same display value
460 | var matches = prop.match(new RegExp('\\b' + synVal.toLowerCase() + '\\b', 'ig'));
461 | if (matches !== null) {
462 | matches.forEach(function (m, index) {
463 | occurences_1 = occurences_1.replace('{replace}', '' + m + '');
464 | });
465 | prop = occurences_1;
466 | }
467 | }
468 | // Check the plurals of the synonym
469 | var synPlural = pluralize(synVal);
470 | if (synPlural !== synVal) {
471 | prop = highlightSynonyms(prop, synPlural);
472 | }
473 | }
474 | return prop;
475 | }
476 | // Function which finds highlighted noise words and removes the highlight tags
477 | function removeNoiseHighlightWords(prop) {
478 | // Only remove the noise words when required
479 | if (RemoveNoiseWords) {
480 | // Remove noise from highlighting
481 | var regexp = /(.*?)<\/c0>/ig;
482 | var noiseWord;
483 | while ((noiseWord = regexp.exec(prop)) !== null) {
484 | if (noiseWord.index === regexp.lastIndex) {
485 | regexp.lastIndex++;
486 | }
487 | // Check if the noise word exists in the array
488 | if (NOISE_WORDS.indexOf(noiseWord[1].toLowerCase()) !== -1) {
489 | // Replace the highlighting with just the noise word
490 | prop = prop.replace(noiseWord[0], noiseWord[1]);
491 | }
492 | }
493 | }
494 | return prop;
495 | }
496 | // Loader function to hook in client side custom query variables
497 | function hookCustomQueryVariables() {
498 | _origExecuteQuery = Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQuery;
499 | _origExecuteQueries = Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQueries;
500 | // TODO: Check if we have cached data, if so, no need to intercept for async web parts
501 | // Override both executeQuery and executeQueries
502 | Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQuery = function (query) {
503 | loadDataAndSearch();
504 | return new SP.JsonObjectResult();
505 | };
506 | Microsoft.SharePoint.Client.Search.Query.SearchExecutor.prototype.executeQueries = function (queryIds, queries, handleExceptions) {
507 | loadDataAndSearch();
508 | return new SP.JsonObjectResult();
509 | };
510 | // Highlight synonyms and remove noise
511 | Srch.U.getHighlightedProperty = function (itemId, crntItem, mp) {
512 | return setSynonymHighlighting(itemId, crntItem, mp);
513 | };
514 | }
515 | ExecuteOrDelayUntilBodyLoaded(function () {
516 | Sys.Application.add_init(hookCustomQueryVariables);
517 | });
518 | })(VariableInjection = Search.VariableInjection || (Search.VariableInjection = {}));
519 | })(Search = spcsr.Search || (spcsr.Search = {}));
520 | })(spcsr || (spcsr = {}));
521 |
522 |
523 | /***/ }),
524 | /* 1 */
525 | /***/ (function(module, exports, __webpack_require__) {
526 |
527 | /* WEBPACK VAR INJECTION */(function(process, setImmediate) {// vim:ts=4:sts=4:sw=4:
528 | /*!
529 | *
530 | * Copyright 2009-2012 Kris Kowal under the terms of the MIT
531 | * license found at http://github.com/kriskowal/q/raw/master/LICENSE
532 | *
533 | * With parts by Tyler Close
534 | * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
535 | * at http://www.opensource.org/licenses/mit-license.html
536 | * Forked at ref_send.js version: 2009-05-11
537 | *
538 | * With parts by Mark Miller
539 | * Copyright (C) 2011 Google Inc.
540 | *
541 | * Licensed under the Apache License, Version 2.0 (the "License");
542 | * you may not use this file except in compliance with the License.
543 | * You may obtain a copy of the License at
544 | *
545 | * http://www.apache.org/licenses/LICENSE-2.0
546 | *
547 | * Unless required by applicable law or agreed to in writing, software
548 | * distributed under the License is distributed on an "AS IS" BASIS,
549 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
550 | * See the License for the specific language governing permissions and
551 | * limitations under the License.
552 | *
553 | */
554 |
555 | (function (definition) {
556 | "use strict";
557 |
558 | // This file will function properly as a