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

Commit

Permalink
Text expansion shortcut (#1057)
Browse files Browse the repository at this point in the history
Resolves #130
  • Loading branch information
tangollama authored and jkleinsc committed Apr 19, 2017
1 parent 8761cb7 commit ed9b002
Show file tree
Hide file tree
Showing 29 changed files with 410 additions and 20 deletions.
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

0 comments on commit ed9b002

Please # to comment.