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:

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; })();