Skip to content

Commit

Permalink
Hashtags Feature applied to Proposals (#3959)
Browse files Browse the repository at this point in the history
* initial commit working on hashtags system

* applying hashtags on proposals

* applied requested changes

* fix specs

* change eslint in package json

* fix input_hashtags.js

* change versions package.json

* fix rubocop offenses

* Don't leave trailing whitespace

* remove hashtaggable on meeting

* add changelog lines
  • Loading branch information
isaacmg410 authored and mrcasals committed Aug 30, 2018
1 parent 9b4cfa8 commit 006a084
Show file tree
Hide file tree
Showing 59 changed files with 734 additions and 130 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
**Added**:
- **decidim-proposals**: Apply hashtags to Proposals. [\#3959](https://github.com/decidim/decidim/pull/3959)
- **decidim-core**: Add the functionality of hashtags. [\#3959](https://github.com/decidim/decidim/pull/3959)
- **decidim-assemblies**: Add the posibility to select the parent assembly when the assembly is created or edited [\#4022](https://github.com/decidim/decidim/pull/4022)
- **decidim-admin**:Add link to user profile and link to conversation from admin space. [\#3995](https://github.com/decidim/decidim/pull/3995)
- **decidim-core**:Add compression settings to image uploader [\#3984](https://github.com/decidim/decidim/pull/3984)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
FactoryBot.define do
factory :accountability_component, parent: :component do
name { Decidim::Components::Namer.new(participatory_space.organization.available_locales, :accountability).i18n_name }
manifest_name :accountability
manifest_name { :accountability }
participatory_space { create(:participatory_process, :with_steps, organization: organization) }
settings do
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// = require ./field_dependent_inputs.component
// = require ./bundle
// = require ./draggable-list
// = require decidim/input_tags
// = require decidim/input_hashtags
// = require_self

window.Decidim = window.Decidim || {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@
@import "dropdown";
@import "agenda";
@import "draggable-list";

// mentions__container
@import "decidim/modules/tags";
@import "decidim/modules/input-tags";
@import "decidim/modules/input-mentions";
@import "decidim/modules/input-hashtags";
2 changes: 1 addition & 1 deletion decidim-blogs/lib/decidim/blogs/test/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
FactoryBot.define do
factory :post_component, parent: :component do
name { Decidim::Components::Namer.new(participatory_space.organization.available_locales, :blogs).i18n_name }
manifest_name :blogs
manifest_name { :blogs }
participatory_space { create(:participatory_process, :with_steps, organization: organization) }
end

Expand Down
6 changes: 3 additions & 3 deletions decidim-budgets/lib/decidim/budgets/test/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
FactoryBot.define do
factory :budget_component, parent: :component do
name { Decidim::Components::Namer.new(participatory_space.organization.available_locales, :budgets).i18n_name }
manifest_name :budgets
manifest_name { :budgets }
participatory_space { create(:participatory_process, :with_steps, organization: organization) }

trait :with_total_budget_and_vote_threshold_percent do
transient do
total_budget 100_000_000
vote_threshold_percent 70
total_budget { 100_000_000 }
vote_threshold_percent { 70 }
end

settings do
Expand Down
4 changes: 2 additions & 2 deletions decidim-comments/lib/decidim/comments/test/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
weight { [-1, 1].sample }

trait :up_vote do
weight 1
weight { 1 }
end

trait :down_vote do
weight(-1)
weight { -1 }
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@
published_at { Time.current }
start_voting_date { Time.zone.today }
end_voting_date { Time.zone.today + 1.month }
introductory_video_url "https://www.youtube.com/embed/zhMMW0TENNA"
introductory_video_url { "https://www.youtube.com/embed/zhMMW0TENNA" }
decidim_highlighted_scope_id { create(:scope, organization: organization).id }
results_published_at nil
results_published_at { nil }

trait :unpublished do
published_at nil
published_at { nil }
end

trait :published do
published_at { Time.current }
end

trait :unpublished_results do
results_published_at nil
results_published_at { nil }
end

trait :published_results do
Expand Down Expand Up @@ -72,22 +72,22 @@
published_at { Time.current }
hero_image { Decidim::Dev.test_file("city.jpeg", "image/jpeg") }
banner_image { Decidim::Dev.test_file("city2.jpeg", "image/jpeg") }
external_voting false
external_voting { false }
sequence :order do |n|
n
end

trait :unpublished do
published_at nil
published_at { nil }
end

trait :published do
published_at { Time.current }
end

trait :external_voting do
external_voting true
i_frame_url "http://example.org"
external_voting { true }
i_frame_url { "http://example.org" }
end
end

Expand Down
2 changes: 1 addition & 1 deletion decidim-core/app/assets/javascripts/decidim.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// = require decidim/append_redirect_url_to_modals
// = require decidim/editor
// = require decidim/input_tags
// = require decidim/input_mentions
// = require decidim/input_hashtags
// = require decidim/ajax_modals
// = require decidim/conferences
// = require_tree ./decidim/vizzs
Expand Down
115 changes: 115 additions & 0 deletions decidim-core/app/assets/javascripts/decidim/input_hashtags.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// = require tribute

$(() => {
const $hashtagContainer = $(".js-hashtags");
const nodatafound = $hashtagContainer.attr("data-noresults");

// Listener for the event triggered by quilljs
let cursor = "";
$hashtagContainer.on("quill-position", function(event) {
if (event.detail !== null) {
cursor = event.detail.index;
}
});

/* eslint no-use-before-define: ["error", { "variables": false }]*/
let remoteSearch = function(text, cb) {
$.post("/api", {query: `{hashtags(name:${text}) {name}}`}).

then((response) => {
let data = response.data.hashtags || {};
cb(data)
}).fail(function() {
cb([])
}).always(() => {
// This function runs Tribute every single time you type something
// So we must evalute DOM properties after each
const $parent = $(tribute.current.element).parent()
$parent.addClass("is-active")

// We need to move the container to the wrapper selected
const $tribute = $parent.find(".tribute-container");
// Remove the inline styles, relative to absolute positioning
$tribute.removeAttr("style");
})
};

// tribute.js docs - http://github.com/zurb/tribute
/* global Tribute*/
let tribute = new Tribute({
trigger: "#",
values: function (text, cb) {
remoteSearch(text, (hashtags) => cb(hashtags));
},
positionMenu: true,
menuContainer: null,
fillAttr: "name",
noMatchTemplate: () => `<li>${nodatafound}</li>`,
lookup: (item) => item.name,
selectTemplate: function(item) {
if (typeof item === "undefined") {
return null;
}
if (this.range.isContentEditable(this.current.element)) {
// Check quill.js
if ($(this.current.element).hasClass("editor-container")) {
let quill = this.current.element.__quill;
quill.insertText(cursor - 1, `#${item.original.name} `, Quill.sources.API);
// cursor position + hashtag length + "#" sign + space
let position = cursor + item.original.name.length + 2;

let next = 0;
if (quill.getLength() > position) {
next = position
} else {
next = quill.getLength() - 1
}
// Workaround https://github.com/quilljs/quill/issues/731
setTimeout(function () {
quill.setSelection(next, 0);
}, 500);

return ""
}
return `<span contenteditable="false">#${item.original.name}</span>`;
}
return `#${item.original.name}`;
},
menuItemTemplate: function(item) {
let tpl = `<strong>${item.original.name}</strong>`;
return tpl;
}
});

tribute.attach($hashtagContainer);

// DOM manipulation
$hashtagContainer.on("focusin", (event) => {
// Set the parent container relative to the current element

tribute.menuContainer = event.target.parentNode;
});
$hashtagContainer.on("focusout", (event) => {
let $parent = $(event.target).parent();

if ($parent.hasClass("is-active")) {
$parent.removeClass("is-active");
}
});
$hashtagContainer.on("input", (event) => {
let $parent = $(event.target).parent();
$parent.removeAttr("style");

if (tribute.isActive) {
// We need to move the container to the wrapper selected
let $tribute = $(".tribute-container");
$tribute.removeAttr("style");
$tribute.appendTo($parent);
// Remove the inline styles, relative to absolute positioning
// Parent adaptation
$parent.addClass("is-active");
} else {
$parent.removeClass("is-active");
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ $(() => {
// EXAMPLE DATA
// tag & name properties are mandatory
//
// sources = [{
// const sources = [{
// "tag": "barrera",
// "name": "Collins Franklin",
// },
// {
// "tag": "woods",
// "name": "Nadine Buck",
// },
// ...]
// }]

// Listener for the event triggered by quilljs
let cursor = "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
@import "tribute";

.hashtags__container{
width: 100%;

span{
color: $secondary;
}

[type=text]{
white-space: nowrap;
overflow: hidden;

br{
display: none;
}

*{
display: inline;
white-space: nowrap;
}
}

[contenteditable=true]:empty::before{
color: $input-placeholder-color;
content: attr(placeholder);
display: block; // firefox
}

&.hashtags__container--textarea{
[contenteditable=true]{
-webkit-appearance: textfield;
-moz-appearance: textfield-multiline;
appearance: textfield;

// foundation defaults
@include form-element();

max-width: 100%;
// Min size, 3 times input textfield
min-height: 3 * (($input-font-size * unitless-calc($input-line-height)) + (get-side($input-padding, 'top') + get-side($input-padding, 'bottom')));
height: auto;

// Placeholder text
&::placeholder{
color: $input-placeholder-color;
}

// Disabled/readonly state
&:disabled,
&[readonly]{
background-color: $input-background-disabled;
cursor: $input-cursor-disabled;
}
}
}

// Override defaults
.tribute-container{
position: relative;
min-width: 100%;
top: -1rem - rem-calc(1);

ul{
//reset list
margin: 0;
list-style: none;
// input defaults
outline: none;
border: $input-border-focus;
border-radius: $input-radius;
background-color: $input-background-focus;
box-shadow: $input-shadow-focus;

@if has-value($input-transition){
transition: $input-transition;
}

li{
padding: $input-padding;
border-top: $border;
}
}

.highlight{
background-color: $input-prefix-background;
}
}

&.is-active{
[contenteditable=true],
input,
textarea{
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}

.tribute-container{
ul{
border-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}

// Quill.js
&.editor{
> :first-child{
border-top-left-radius: $input-radius;
border-top-right-radius: $input-radius;
border-top: $input-border-focus;
border-right: $input-border-focus;
border-left: $input-border-focus;
}

.editor-container{
border-right: $input-border-focus;
border-left: $input-border-focus;
margin-bottom: 1rem;
outline: none;
}
}
}
}
Loading

0 comments on commit 006a084

Please sign in to comment.