Skip to content

Commit 0c2d669

Browse files
authored
Merge pull request #117 from VSEphpbb/duplicate
Improved duplicate linking
2 parents b916ecc + 8132258 commit 0c2d669

9 files changed

Lines changed: 236 additions & 47 deletions

File tree

config/routing.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ phpbb_ideas_list_controller:
1717
phpbb_ideas_post_controller:
1818
path: /ideas/post
1919
defaults: { _controller: phpbb.ideas.post_controller:post }
20+
21+
phpbb_ideas_livesearch_controller:
22+
path: /ideas/livesearch/title
23+
defaults: { _controller: phpbb.ideas.livesearch_controller:title_search }

config/services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ services:
6262
class: phpbb\ideas\controller\idea_controller
6363
parent: phpbb.ideas.controller.base
6464

65+
phpbb.ideas.livesearch_controller:
66+
class: phpbb\ideas\controller\livesearch_controller
67+
parent: phpbb.ideas.controller.base
68+
6569
phpbb.ideas.ideas:
6670
class: phpbb\ideas\factory\ideas
6771
arguments:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
*
4+
* Ideas extension for the phpBB Forum Software package.
5+
*
6+
* @copyright (c) phpBB Limited <https://www.phpbb.com>
7+
* @license GNU General Public License, version 2 (GPL-2.0)
8+
*
9+
*/
10+
11+
namespace phpbb\ideas\controller;
12+
13+
/**
14+
* Ideas live search controller
15+
*/
16+
class livesearch_controller extends base
17+
{
18+
public function title_search()
19+
{
20+
$title_chars = $this->request->variable('duplicateeditinput', '', true);
21+
22+
$matches = $this->ideas->ideas_title_livesearch($title_chars, 10);
23+
24+
$json_response = new \phpbb\json_response();
25+
$json_response->send([
26+
'keyword' => $title_chars,
27+
'results' => $matches,
28+
]);
29+
}
30+
}

event/listener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public function show_idea($event)
229229
'IDEA_STATUS_ID' => $idea['idea_status'],
230230
'IDEA_STATUS_NAME' => $this->ideas->get_status_from_id($idea['idea_status']),
231231

232-
'IDEA_DUPLICATE' => $idea['duplicate_id'],
232+
'IDEA_DUPLICATE' => $idea['duplicate_id'] ? $this->ideas->get_title($idea['duplicate_id']) : '',
233233
'IDEA_RFC' => $idea['rfc_link'],
234234
'IDEA_TICKET' => $idea['ticket_id'],
235235
'IDEA_IMPLEMENTED' => $idea['implemented_version'],
@@ -252,6 +252,7 @@ public function show_idea($event)
252252
'U_IDEA_VOTE' => $this->link_helper->get_idea_link($idea['idea_id'], 'vote', true),
253253
'U_IDEA_DUPLICATE' => $this->link_helper->get_idea_link($idea['duplicate_id']),
254254
'U_IDEA_STATUS_LINK'=> $this->helper->route('phpbb_ideas_list_controller', array('status' => $idea['idea_status'])),
255+
'U_TITLE_LIVESEARCH'=> $this->helper->route('phpbb_ideas_livesearch_controller'),
255256
));
256257

257258
// Use Ideas breadcrumbs

factory/ideas.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,34 @@ public function get_idea_by_topic_id($id)
265265
return $this->get_idea($idea_id);
266266
}
267267

268+
/**
269+
* Do a live search on idea titles. Return any matches based on a given search query.
270+
*
271+
* @param string $search The string of characters to search using LIKE
272+
* @param int $limit The number of results to return
273+
* @return array An array of matching idea id/key and title/values
274+
*/
275+
public function ideas_title_livesearch($search, $limit = 10)
276+
{
277+
$results = [];
278+
$sql = 'SELECT idea_title, idea_id
279+
FROM ' . $this->table_ideas . '
280+
WHERE idea_title ' . $this->db->sql_like_expression($search . $this->db->get_any_char());
281+
$result = $this->db->sql_query_limit($sql, $limit);
282+
while ($row = $this->db->sql_fetchrow($result))
283+
{
284+
$results[] = [
285+
'idea_id' => $row['idea_id'],
286+
'result' => $row['idea_id'],
287+
'clean_title' => $row['idea_title'],
288+
'display' => "<span>{$row['idea_title']}</span>", // spans are expected in phpBB's live search JS
289+
];
290+
}
291+
$this->db->sql_freeresult($result);
292+
293+
return $results;
294+
}
295+
268296
/**
269297
* Returns the status name from the status ID specified.
270298
*
@@ -416,6 +444,25 @@ public function set_title($idea_id, $title)
416444
return true;
417445
}
418446

447+
/**
448+
* Get the title of an idea.
449+
*
450+
* @param int $id ID of an idea
451+
*
452+
* @return string The idea's title
453+
*/
454+
public function get_title($id)
455+
{
456+
$sql = 'SELECT idea_title
457+
FROM ' . $this->table_ideas . '
458+
WHERE idea_id = ' . (int) $id;
459+
$result = $this->db->sql_query_limit($sql, 1);
460+
$idea_title = $this->db->sql_fetchfield('idea_title');
461+
$this->db->sql_freeresult($result);
462+
463+
return $idea_title;
464+
}
465+
419466
/**
420467
* Submits a vote on an idea.
421468
*

language/en/common.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
'DATE' => 'Date',
3131
'DELETE_IDEA' => 'Delete idea',
3232
'DUPLICATE' => 'Duplicate',
33+
'DUPLICATE_PLACEHOLDER' => 'Start typing a title',
3334

3435
'EDIT' => 'Edit',
3536
'ENABLE_JS' => 'Please enable JavaScript in your browser to use phpBB Ideas effectively.',
@@ -38,7 +39,6 @@
3839
'IDEA_DELETED' => 'Idea successfully deleted.',
3940
'IDEA_ID' => 'Idea ID',
4041
'IDEA_LIST' => 'Idea List',
41-
'IDEA_NUM' => 'Idea #',
4242
'IDEA_NOT_FOUND' => 'Idea not found',
4343
'IDEA_STORED_MOD' => 'Your idea has been submitted successfully, but it will need to be approved by a moderator before it is publicly viewable. You will be notified when your idea has been approved.<br /><br /><a href="%s">Return to Ideas</a>.',
4444
'IDEAS_TITLE' => 'phpBB Ideas',
@@ -78,7 +78,7 @@
7878

7979
'TICKET' => 'Ticket',
8080
'TICKET_ERROR' => 'Ticket ID must be of the format “PHPBB3-#####”.',
81-
'TICKET_ERROR_DUP' => 'Please use the Idea ID number.',
81+
'TICKET_ERROR_DUP' => 'You must click on an idea title from the live search results. To delete the duplicate, clear the field and press ENTER. To exit this field press ESC.',
8282
'TITLE' => 'Title',
8383
'TITLE_TOO_LONG' => 'Subject must be under %d characters long.',
8484
'TITLE_TOO_SHORT' => 'You must specify a subject when posting a new idea.',
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
*
4+
* Ideas extension for the phpBB Forum Software package.
5+
*
6+
* @copyright (c) phpBB Limited <https://www.phpbb.com>
7+
* @license GNU General Public License, version 2 (GPL-2.0)
8+
*
9+
*/
10+
11+
namespace phpbb\ideas\migrations;
12+
13+
class m10_update_idea_schema extends \phpbb\db\migration\migration
14+
{
15+
/**
16+
* {@inheritDoc}
17+
*/
18+
static public function depends_on()
19+
{
20+
return [
21+
'\phpbb\ideas\migrations\m1_initial_schema',
22+
'\phpbb\ideas\migrations\m6_migrate_old_tables',
23+
'\phpbb\ideas\migrations\m7_drop_old_tables',
24+
'\phpbb\ideas\migrations\m8_implemented_version',
25+
'\phpbb\ideas\migrations\m9_remove_idea_bot',
26+
];
27+
}
28+
29+
/**
30+
* {@inheritDoc}
31+
*
32+
* Convert ideas title column to sortable text (same as topic titles)
33+
* to allow for case-insensitive SQL LIKE searches.
34+
*/
35+
public function update_schema()
36+
{
37+
return [
38+
'change_columns' => [
39+
$this->table_prefix . 'ideas_ideas' => [
40+
'idea_title' => ['STEXT_UNI', '', 'true_sort'],
41+
],
42+
],
43+
];
44+
}
45+
46+
/**
47+
* {@inheritDoc}
48+
*/
49+
public function revert_schema()
50+
{
51+
}
52+
}

styles/prosilver/template/idea_body.html

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,33 @@
9090
{% if IDEA_DUPLICATE or S_IS_MOD %}
9191
<dt class="duplicatetoggle idealabel"{% if IDEA_STATUS_ID != STATUS_ARY.DUPLICATE %} style="display:none"{% endif %}>{{ lang('DUPLICATE') ~ lang('COLON') }}</dt>
9292
<dd class="duplicatetoggle" {% if IDEA_STATUS_ID != STATUS_ARY.DUPLICATE %}style="display:none"{% endif %}>
93-
<a id="duplicatelink" data-link="{{ U_IDEA_DUPLICATE }}" data-l-msg="{{ lang('IDEA_NUM') }}" {% if IDEA_DUPLICATE %}href="{{ U_IDEA_DUPLICATE }}">{{ lang('IDEA_NUM') ~ IDEA_DUPLICATE }}{% else %}style="display:none">{% endif %}</a>
93+
<a id="duplicatelink" data-link="{{ U_IDEA_DUPLICATE }}" {% if IDEA_DUPLICATE %}href="{{ U_IDEA_DUPLICATE }}">{{ IDEA_DUPLICATE }}{% else %}style="display:none">{% endif %}</a>
9494
{% if S_IS_MOD %}
9595
<a href="{{ U_EDIT_DUPLICATE }}" id="duplicateedit" data-l-add="{{ lang('ADD') }}" data-l-edit="{{ lang('EDIT') }}">{% if IDEA_DUPLICATE %}<i class="icon fa-fw fa-pencil"></i>{{ lang('EDIT') }}{% else %}<i class="icon fa-fw fa-plus-circle"></i>{{ lang('ADD') }}{% endif %}</a>
96-
<input type="text" id="duplicateeditinput" class="ideainput"{% if IDEA_DUPLICATE %} value="{{ IDEA_DUPLICATE }}"{% endif %} placeholder="###" data-l-err="{{ lang('ERROR') }}" data-l-msg="{{ lang('TICKET_ERROR_DUP') }}" />
96+
<div class="dropdown-container dropdown-{{ S_CONTENT_FLOW_END }}">
97+
<input
98+
type="text"
99+
name="duplicateeditinput"
100+
id="duplicateeditinput"
101+
value="{{ IDEA_DUPLICATE ? IDEA_DUPLICATE }}"
102+
placeholder="{{ lang('DUPLICATE_PLACEHOLDER') }}"
103+
class="ideainput"
104+
autocomplete="off"
105+
data-filter="phpbb.search.filter"
106+
data-ajax="idea_search"
107+
data-min-length="3"
108+
data-url="{{ U_TITLE_LIVESEARCH }}"
109+
data-results="#live-search"
110+
data-l-err="{{ lang('ERROR') }}"
111+
data-l-msg="{{ lang('TICKET_ERROR_DUP') }}"
112+
/>
113+
<div class="dropdown live-search hidden" id="live-search">
114+
<div class="pointer"><div class="pointer-inner"></div></div>
115+
<ul class="dropdown-contents search-results">
116+
<li class="search-result-tpl"><span class="search-result"></span></li>
117+
</ul>
118+
</div>
119+
</div>
97120
{% endif %}
98121
</dd>
99122
{% endif %}

styles/prosilver/template/ideas.js

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -254,53 +254,81 @@
254254
$obj.duplicateEditInput.show().trigger('focus');
255255
});
256256

257-
$obj.duplicateEditInput.on('keydown', function(e) {
258-
if (e.keyCode === keymap.ENTER) {
259-
e.preventDefault();
260-
e.stopPropagation();
257+
/**
258+
* This callback handles live idea title searches for duplicate ideas.
259+
*/
260+
phpbb.addAjaxCallback('idea_search', function(res) {
261+
phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setDuplicateOnEvent'));
262+
});
261263

262-
var $this = $(this),
263-
url = $obj.duplicateEdit.attr('href'),
264-
value = $this.val();
264+
/**
265+
* This performs actions on each result from the live idea title search for duplicate ideas.
266+
*
267+
* @param {jQuery|JQuery} $input Search input|textarea.
268+
* @param {object} value Result object.
269+
* @param {jQuery|JQuery} $row Result element.
270+
* @param {jQuery|JQuery} $container jQuery object for the search container.
271+
*/
272+
phpbb.search.setDuplicateOnEvent = function($input, value, $row, $container) {
273+
$row.on('click', function() {
274+
setDuplicate($input, value);
275+
phpbb.search.closeResults($input, $container);
276+
});
277+
};
265278

266-
if (value && isNaN(Number(value))) {
267-
phpbb.alert($this.attr('data-l-err'), $this.attr('data-l-msg'));
268-
return;
279+
/**
280+
* Assign a duplicate idea identifier to a given idea.
281+
*
282+
* @param {jQuery|JQuery} $input Search input|textarea.
283+
* @param {object} value Result object.
284+
*/
285+
function setDuplicate($input, value) {
286+
if (value.result && isNaN(Number(value.result))) {
287+
phpbb.alert($input.attr('data-l-err'), $input.attr('data-l-msg'));
288+
return;
289+
}
290+
$input.val(value.clean_title);
291+
showLoadingIndicator();
292+
$.get($obj.duplicateEdit.attr('href'), {duplicate: Number(value.result)}, function(res) {
293+
if (res) {
294+
if (value.result) {
295+
$obj.duplicateLink
296+
.text(value.clean_title)
297+
.attr('href', $obj.duplicateLink.attr('data-link').replace(/^(.*\/)(\d+)$/, '$1') + value.result)
298+
.show();
299+
} else {
300+
$obj.duplicateLink
301+
.empty()
302+
.removeAttr('href');
303+
}
304+
$input.hide();
305+
$obj.duplicateEdit.toggleAddEdit(value.result);
269306
}
307+
}).always(hideLoadingIndicator);
308+
}
270309

271-
showLoadingIndicator();
272-
$.get(url, {duplicate: Number(value)}, function(res) {
273-
if (res) {
274-
if (value) {
275-
var msg = $obj.duplicateLink.attr('data-l-msg');
276-
var link = $obj.duplicateLink.attr('data-link').replace(/^(.*\/)(\d+)$/, '$1');
277-
278-
$obj.duplicateLink
279-
.text(msg + value)
280-
.attr('href', link + value)
281-
.show();
282-
} else {
283-
$obj.duplicateLink
284-
.text(value)
285-
.attr('href', value);
286-
}
287-
288-
$this.hide();
289-
290-
$obj.duplicateEdit.toggleAddEdit(value);
310+
/**
311+
* Handling of the duplicate idea input field.
312+
* ENTER: When the input field is empty clear any existing duplicate entry. Otherwise just show an alert message.
313+
* ESC: Will clear and close the input field (if it isn't cleared, live search may unexpectedly run).
314+
*/
315+
$obj.duplicateEditInput.on('keydown.duplicate', function(e) {
316+
var $this = $(this),
317+
key = e.keyCode || e.which;
318+
switch (key) {
319+
case keymap.ESC:
320+
$this.val('').hide();
321+
$obj.duplicateEdit.show();
322+
$obj.duplicateLink.toggle($obj.duplicateLink.html().length !== 0);
323+
break;
324+
case keymap.ENTER:
325+
if ($this.val().length === 0) {
326+
setDuplicate($this, {'result': '', 'clean_title': ''});
327+
} else {
328+
e.stopPropagation();
329+
phpbb.alert($this.attr('data-l-err'), $this.attr('data-l-msg'));
291330
}
292-
}).always(hideLoadingIndicator);
293-
} else if (e.keyCode === keymap.ESC) {
294-
e.preventDefault();
295-
296-
var $link = $obj.duplicateLink;
297-
298-
$(this).hide();
299-
$obj.duplicateEdit.show();
300-
301-
if ($link.html()) {
302-
$link.show();
303-
}
331+
break;
304332
}
305333
});
306334

0 commit comments

Comments
 (0)