Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Text expansion shortcut #1057

Merged
merged 7 commits into from
Apr 19, 2017
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# compiled output
/dist
/tmp
/electron-out

# dependencies
/node_modules
Expand Down Expand Up @@ -32,4 +33,4 @@ newrelic_agent.log
newrelic.js

/async-disk-cache
c9-couch.js
c9-couch.js
27 changes: 27 additions & 0 deletions app/admin/textreplace/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Ember from 'ember';
import EmberValidations from 'ember-validations';

export default Ember.Controller.extend(EmberValidations, {
hideCancelButton: true,
updateCapability: 'update_config',

createExpansion: function() {
let newExpansion = this.get('store').createRecord('text-expansion');
this.set('newExpansion', newExpansion);
}.on('init'),

actions: {
cancelExpansion() {
this.createExpansion();
}
},

validations: {
'newExpansion.from': {
presence: true
},
'newExpansion.to': {
presence: true
}
}
});
46 changes: 46 additions & 0 deletions app/admin/textreplace/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import AbstractIndexRoute from 'hospitalrun/routes/abstract-index-route';
import { translationMacro as t } from 'ember-i18n';

export default AbstractIndexRoute.extend({
pageTitle: t('admin.textReplacements.pageTitle'),
hideNewButton: true,

model() {
let store = this.get('store');
return store.findAll('text-expansion').then((result) => {
return result.filter((model) => {
let isNew = model.get('isNew');
console.log(`${model.get('from')} ${isNew}`);
return !isNew;
});
});
},

setupController(controller, model) {
this._super(controller, model);
controller.createExpansion();
},

actions: {
addExpansion(newExpansion) {
newExpansion.save()
.then(() => {
this.refresh();
})
.catch(() => {
this.refresh();
});
},

deleteExpansion(expansion) {
expansion.deleteRecord();
expansion.save()
.then(() => {
this.refresh();
})
.catch(() => {
this.refresh();
});
}
}
});
36 changes: 36 additions & 0 deletions app/admin/textreplace/template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<div class="panel panel-primary">
<div class="panel-body">
<p>{{t 'admin.textReplacements.replDesc'}}</p>
<table class="table">
<tr class="table-header">
<th>{{t 'labels.from'}}</th>
<th>{{t 'labels.to'}}</th>
<th/>
</tr>
<tbody>
{{#each model as |expansion|}}
<tr>
<td>#{{expansion.from}}</td>
<td>{{expansion.to}}</td>
<td>
<button class="pull-right btn button-default on-white" {{action 'deleteExpansion' expansion}}>{{t 'buttons.delete'}}</button>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
<div class="panel">
<div class="panel-body">
<h3>{{t 'admin.textReplacements.createNew'}}</h3>
{{#em-form model=newExpansion action="addExpansion" formLayout="horizontal" showErrorsOnFocusIn="true" submitButton=false}}
{{em-input property="from" label=(t 'labels.from') placeholder=(t 'admin.textReplacements.toReplace')}}
{{em-input property="to" label=(t 'labels.to') placeholder=(t 'admin.textReplacements.replaceWith')}}
{{/em-form}}
</div>
<div class="panel-footer">
<button class="btn button-primary on-white" disabled={{isInvalid}} {{action 'addExpansion' newExpansion}}>{{t 'buttons.add'}}</button>
<button class="btn button-default on-white" {{action 'cancelExpansion'}}>{{t 'buttons.cancel'}}</button>
</div>
</div>
2 changes: 1 addition & 1 deletion app/appointments/edit/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@
}}
</div>
{{/if}}
{{em-text label=(t 'models.appointment.labels.notes') property="notes" rows=3 }}
{{expand-text label=(t 'models.appointment.labels.notes') property="notes" rows=3 }}
{{/em-form}}
{{/edit-panel}}
154 changes: 154 additions & 0 deletions app/components/expand-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import Ember from 'ember';
import textExpansion from '../utils/text-expansion';

export default Ember.Component.extend({
i18n: Ember.inject.service(),
store: Ember.inject.service(),

userText: '',

didInsertElement() {
try {
let feedbackDiv = document.createElement('div');
feedbackDiv.style.position = 'absolute';
// let textarea = this.$()[0].getElementsByTagName('textarea')[0];
let [textarea] = this.$('textarea');
this.set('textarea', textarea);
let textPos = textarea.getBoundingClientRect();
let fbStyle = feedbackDiv.style;
fbStyle.top = `${textPos.bottom}px`;
fbStyle.left = `${textPos.left}px`;
fbStyle.width = `${textarea.offsetWidth}px`;
fbStyle.backgroundColor = 'lightyellow';
fbStyle.borderStyle = 'solid';
fbStyle.borderWidth = '1px';
fbStyle.borderRadius = '3px';
fbStyle.paddingLeft = '5px';
fbStyle.visibility = 'hidden';

this.set('feedbackDiv', feedbackDiv);
this.get('feedbackText');
this.get('activeExpansionSite');

this.get('store')
.findAll('text-expansion')
.then((expansions) => {
return expansions.reduce((prev, curr) => {
// console.log(`curr ${JSON.stringify(prev)}`);
prev[curr.get('from')] = curr.get('to');
return prev;
}, {});
})
.then((expansions) => {
this.set('expansions', expansions);
});

} catch(e) {
// console.log(`didInsert {e}`);
}
},

keyUp(k) {
let textArea = k.target;
let text = textArea.value;
this.set('userText', text);
this.set('cursorLocation', textArea.selectionStart);
},

keyDown(k) {
if (k.keyCode === 13) {
let possibleSwaps = this.get('possibleSwaps');
if (possibleSwaps && possibleSwaps.length === 1) {
let swapTo = possibleSwaps[0].to;
let activeSite = this.get('activeExpansionSite');
let sliceLength = activeSite.match.length;
let currentText = k.target.value;
let modifiedText = currentText.slice(0, activeSite.index) + swapTo + currentText.slice(activeSite.index + sliceLength);
k.target.value = modifiedText;

k.preventDefault();
k.returnValue = false;
k.cancelBubble = true;
return false;
}
}
},

// Find an expandable word that has the cursor within it
activeExpansionSite: Ember.computed('userText', 'cursorLocation', function() {

let userText = this.get('userText');
let textarea = this.get('textarea');
if (!textarea) {
return null;
}
let cursorLoc = textarea.selectionStart;
let subjects = textExpansion.findExpansionSubjects(userText);
let sites = textExpansion.findExpansionSites(userText, subjects);

return sites.find((s) => {
let endIndex = s.index + s.match.length;

return cursorLoc >= s.index && cursorLoc <= endIndex;
});
}),

// If an expansion site is active, which possible swaps could occur there?
possibleSwaps: Ember.computed('activeExpansionSite', 'expansions', function() {
let activeSite = this.get('activeExpansionSite');

if (activeSite) {
let expansions = this.get('expansions');
return Object.keys(expansions)
.filter((ex) => {
return ex.startsWith(activeSite.term);
})
.sort()
.map((from) => {
return {
from,
to: expansions[from]
};
});
}
}),

expansionText: Ember.computed('possibleSwaps', 'activeExpansionSite', 'userText', function() {
let result = '';

let i18n = this.get('i18n');
let possibleSwaps = this.get('possibleSwaps');
if (possibleSwaps) {
let activeSite = this.get('activeExpansionSite');

if (possibleSwaps.length === 1) {
let swapTo = possibleSwaps[0].to;
result = i18n.t('admin.textReplacements.performExpand', { from: activeSite.term, to: swapTo });
} else if (possibleSwaps.length > 1) {
let possible = possibleSwaps
.map((swap) => {
return swap.from;
}).join(', ');
result = i18n.t('admin.textReplacements.possibleExpansions', { possible });
} else {
result = i18n.t('admin.textReplacements.noMatches', { term: activeSite.term });
}
}

return result;
}),

expansionDivStyle: Ember.computed('expansionText', function() {
let expansionText = this.get('expansionText');
let visiblility = expansionText ? 'visible' : 'hidden';
let textArea = this.get('textarea');

let styleString = `visibility: ${visiblility};`;

if (textArea) {
let textPos = textArea.getBoundingClientRect();
styleString += ` top: ${textPos.bottom}px; left: ${textPos.left}px; width: ${textArea.offsetWidth}px;`;
}
return new Ember.Handlebars.SafeString(styleString);
})
});
2 changes: 1 addition & 1 deletion app/imaging/edit/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@
}}
{{em-input property="result" label=(t 'labels.result') class="result-input"}}
{{/if}}
{{em-text property="notes" label=(t 'labels.notes') rows=3 }}
{{expand-text property="notes" label=(t 'labels.notes') rows=3 }}
{{/em-form}}
{{/edit-panel}}
2 changes: 1 addition & 1 deletion app/incident/edit/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
{{/if}}
</div>
<div class="row">
{{em-text label=(t 'incident.labels.description') property="description" class="required col-sm-12 incident-description" rows=3}}
{{expand-text label=(t 'incident.labels.description') property="description" class="required col-sm-12 incident-description" rows=3}}
</div>

{{#if canManageIncident}}
Expand Down
2 changes: 1 addition & 1 deletion app/incident/note/edit/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{{date-picker property="dateRecorded" label=(t 'incident.labels.dateRecorded') class="col-sm-6" format="l h:mm A" showTime=true }}
</div>
<div class="row">
{{em-text property="description" label=(t 'incident.labels.note') class="col-sm-12 note-description"}}
{{expand-text property="description" label=(t 'incident.labels.note') class="col-sm-12 note-description"}}
</div>
{{/em-form}}
{{/modal-dialog}}
2 changes: 1 addition & 1 deletion app/inventory/adjust/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
}}
{{number-input property="adjustmentQuantity" label=(t 'labels.quantity') class="col-sm-3 required"}}
</div>
{{em-text label=(t 'inventory.labels.reason') property="reason" rows=3}}
{{expand-text label=(t 'inventory.labels.reason') property="reason" rows=3}}
<div class="row">
{{date-picker property="dateCompleted" label=(t 'inventory.labels.adjustmentDate') class="col-sm-4 required"}}
{{select-or-typeahead property="expenseAccount" label=(t 'inventory.labels.expense') list=expenseAccountList selection=model.expenseAccount class="col-sm-8"}}
Expand Down
2 changes: 1 addition & 1 deletion app/invoices/payment/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
{{number-input property="amount" label=(t 'labels.amount') class="required payment-amount"}}
{{date-picker property="datePaid" label=(t 'labels.datePaid') maxDate="now" class="required"}}
{{select-or-typeahead property="expenseAccount" label=(t 'labels.creditTo') list=expenseAccountList selection=model.expenseAccount }}
{{em-text property="notes" label=(t 'labels.notes')}}
{{expand-text property="notes" label=(t 'labels.notes')}}
{{/em-form}}
{{/modal-dialog}}
2 changes: 1 addition & 1 deletion app/labs/edit/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
{{#if canComplete}}
{{em-input property="result" label=(t 'labels.result') class="test-result-input"}}
{{/if}}
{{em-text property="notes" label=(t 'labels.notes') rows=3 }}
{{expand-text property="notes" label=(t 'labels.notes') rows=3 }}
{{custom-form-manager model=model formType="lab"}}
{{/em-form}}
{{/edit-panel}}
14 changes: 14 additions & 0 deletions app/locales/en/translations.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,17 @@ export default {
messages: { roleSaved: 'The {{roleName}} role has been saved.' },
titles: { roleSaved: 'Role Saved' }
},
textReplacements: {
createNew: 'Create a new shortcode',
existingRepl: 'Existing Shortcodes',
replDesc: 'When entering text, these shortcuts allow you to replace a short sequence of characters with a longer phrase.',
pageTitle: 'Shortcodes',
toReplace: 'Text to replace',
replaceWith: 'Replace with',
performExpand: "Press Enter to replace #{{from}} with '{{to}}'",
possibleExpansions: 'Possible replacements: {{possible}}',
noMatches: "No replacements match '{{term}}'"
},
userRoles: 'User Roles',
users: 'Users',
visitForms: {
Expand Down Expand Up @@ -793,6 +804,7 @@ export default {
fileLoadSuccessful: 'File To Load Successful',
fileName: 'File Name',
fileToLoad: 'File Load',
from: 'From',
fulfill: 'Fulfill',
fulfillRequest: 'Fulfill Request',
fulfillRequestNow: 'Fulfill Request Now',
Expand Down Expand Up @@ -841,6 +853,7 @@ export default {
startTime: 'Start Time',
status: 'Status',
takenBy: 'Taken By',
to: 'To',
total: 'Total',
type: 'Type',
userCanAddNewValue: 'User Can Add New Values',
Expand Down Expand Up @@ -1094,6 +1107,7 @@ export default {
requests: 'Requests',
returnMedication: 'Return Medication',
scheduleSurgery: 'Schedule Surgery',
textReplacements: 'Shortcodes',
theaterSchedule: 'Theater Schedule',
"today'sAppointments": "Today's Appointments",
userRoles: 'User Roles',
Expand Down
2 changes: 1 addition & 1 deletion app/medication/edit/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
{{static-text label=(t 'medication.labels.refills') class="col-xs-3" value=model.refills }}
</div>
{{else}}
{{em-text property="prescription" label=(t 'labels.prescription')rows="3" class=prescriptionClass }}
{{expand-text property="prescription" label=(t 'labels.prescription')rows="3" class=prescriptionClass }}
<div class="row">
{{date-picker property="prescriptionDate" label=(t 'labels.prescriptionDate') class="col-sm-4"}}
</div>
Expand Down
Loading