OO-PG0016
Plug-In: Read Outline Rows
This plug-in will read the text of the selected outline rows.
The plug-in includes these optional preferences:
- Include the recitation of each row’s note (if any).
- Include the recitation of each row’s position identifier, ex: 2.2.1
- Read the entire outline, instead of just the selected rows (default).
During the execution of the plug-in, an alert dialog will be presented. Click the alert button at any time to halt the text-to-speech process.
NOTE: By default, the plug-in uses the built-in “Alex” voice on macOS, iOS, and iPadOS. Make sure this voice is installed and activated on the devices hosting this plug-in.
On iPadOS/iOS: Settings > Accessibility > Spoken Content > Voices > English > Alex
On macOS: System Settings > Accessibility > Spoken Content > System Voice > Manage Voices… (Customize)
Video: Read Outline Rows |
This plug-in will read the text of the selected outline rows. |
|
Return to: OmniOutliner Plug-In Collection
Read Outline Rows
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.read-selected-rows",
"version": "1.3",
"description": "This plug-in will read the text of the selected rows. There is an option to automatically include the recitation of the row’s note.",
"label": "Read Outline Rows",
"shortLabel": "Read Outline Rows",
"paletteLabel": "Read Outline Rows",
"image": "person.wave.2.fill"
}*/
(() => {
var preferences = new Preferences() // NO ID = PLUG-IN ID
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
function genPositionID(item){
nums = item.ancestors.map(x => 1 + x.index)
nums.reverse().push(item.index + 1)
return nums.join(".")
}
const action = new PlugIn.Action(async function(selection, sender){
try {
var synthesizer = new Speech.Synthesizer()
shouldIdentifyRow = preferences.readBoolean("shouldIdentifyRow")
if(!shouldIdentifyRow){shouldIdentifyRow = false}
console.log("shouldIdentifyRow", shouldIdentifyRow)
shouldReadNotes = preferences.readBoolean("shouldReadNotes")
if(!shouldReadNotes){shouldReadNotes = false}
console.log("shouldReadNotes", shouldReadNotes)
shouldReadOutline = preferences.readBoolean("shouldReadOutline")
if(!shouldReadOutline){shouldReadOutline = false}
console.log("shouldReadOutline", shouldReadOutline)
checkSwitchFieldA = new Form.Field.Checkbox(
"shouldIdentifyRow",
"Speak each row’s positional identifier",
shouldIdentifyRow
)
checkSwitchFieldB = new Form.Field.Checkbox(
"shouldReadNotes",
"Automatically read a row’s notes",
shouldReadNotes
)
checkSwitchFieldC = new Form.Field.Checkbox(
"shouldReadOutline",
"Read entire outline",
shouldReadOutline
)
inputForm = new Form()
inputForm.addField(checkSwitchFieldA)
inputForm.addField(checkSwitchFieldB)
inputForm.addField(checkSwitchFieldC)
inputForm.validate = function(formObject){
return true
}
formPrompt = (shouldReadOutline) ? "Read Outline Rows":"Read Selected Rows"
spokenPrompt = (shouldReadOutline) ? "Reed Outline Rows":"Reed Selected Rows"
buttonTitle = "Continue"
utterance = createUtterance(spokenPrompt)
synthesizer.speakUtterance(utterance)
formObject = await inputForm.show(formPrompt, buttonTitle)
shouldIdentifyRow = formObject.values['shouldIdentifyRow']
console.log('shouldIdentifyRow', shouldIdentifyRow)
preferences.write("shouldIdentifyRow", shouldIdentifyRow)
shouldReadNotes = formObject.values['shouldReadNotes']
console.log('shouldReadNotes', shouldReadNotes)
preferences.write("shouldReadNotes", shouldReadNotes)
shouldReadOutline = formObject.values['shouldReadOutline']
console.log('shouldReadOutline', shouldReadOutline)
preferences.write("shouldReadOutline", shouldReadOutline)
var itemsToRead = new Array()
if(shouldReadOutline){
itemsToRead = rootItem.descendants
} else {
itemsToRead = selection.items
}
if(itemsToRead.length === 0){
throw {
name: "Selection Issue",
message: "No items have been selected to be read."
}
}
rowCount = itemsToRead.length
rowOrRows = (rowCount === 1) ? "row":"rows"
var textSegments = new Array()
itemsToRead.forEach(item => {
if(shouldIdentifyRow){
itemID = genPositionID(item)
spokenID = itemID.replaceAll('.', ' point ')
identifyingPhrase = `Row ${spokenID}`
textSegments.push(identifyingPhrase)
}
rowText = item.topic
if(rowText.length === 0){rowText = "Empty row."}
textSegments.push(rowText)
if(shouldReadNotes){
noteText = item.note
if(noteText.length > 0){
textSegments.push("Note for row.")
textSegments.push(noteText)
}
}
})
addedPhrase = (shouldReadNotes) ? "and notes": ""
openingUtterance = createUtterance(`Reading ${rowCount} ${rowOrRows} ${addedPhrase}`)
openingUtterance.postUtteranceDelay = 0.75
var utterances = [openingUtterance]
textSegments.forEach(segment => {
console.log(segment)
utterance = createUtterance(segment)
utterance.postUtteranceDelay = 0.5
utterances.push(utterance)
})
utterances.forEach(utterance => {
synthesizer.speakUtterance(utterance)
})
utterance = createUtterance("Done!")
synthesizer.speakUtterance(utterance)
alertTitle = (shouldReadOutline) ? "Reading Outline Rows":"Reading Selected Rows"
alert = new Alert(alertTitle, "Click “Done” button to stop speaking.")
alert.addOption("Done")
index = await alert.show()
synthesizer.stopSpeaking(Speech.Boundary.Word)
}
catch(err){
if(!err.causedByUserCancelling){
utterance = createUtterance(err.message)
synthesizer.speakUtterance(utterance)
//new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
return true
};
return action;
})();