//Bottom of this doc sets up page structure and references components created above //Global variable for moro_click database var global_id_to_morpheme_definition = []; var global_id_to_row = {}; var global_whole_data; var firstLoad = true; //These are imports from ReactRouter o.13.x //docs: https://github.com/rackt/react-router/blob/0.13.x/docs/guides/overview.md var Link = ReactRouter.Link; var RouteHandler = ReactRouter.RouteHandler; var Route = ReactRouter.Route; // These are endpoints to load data from. // Loaded from static files in the repository rather than from lingsync. // Static file with sentences. var sentence_url = 'sentences.json'; // Static file with stories. var story_url = 'stories.json'; // Promise that is resolved once the sentence data is loaded var raw_data_promise = new Promise(function(resolve, reject) { $.ajax({ url: sentence_url, dataType: 'json', success: function(d) { resolve(d); }, error: function(xhr, status, err) { console.error(sentence_url, status, err.toString()); reject(err);} }) }); // Promise that is resolved once stories are loaded var story_data_promise = new Promise(function(resolve, reject) { $.ajax({ url: story_url, dataType: 'json', success: function(d) { resolve(d); }, error: function(xhr, status, err) { console.error(sentence_url, status, err.toString()); reject(err);} }) }); var sentence_data_promise = Promise.all([raw_data_promise, story_data_promise]).then( function(x) { var sentence_data = x[0]; var story_data = x[1]; var stories = _.reduce(story_data.rows, function(acc, x) { acc[x.key] = 1; return acc; }, {}) return _.filter(sentence_data.rows, function(x) {return stories[x.key[0]] == 1;}) } ); //===========================================Dictionary Code=========================================== //get id of all occurrences of the morpheme and definition pair from the global_id_to_morpheme_definition function get_occurrence_ids(morpheme_click, definition_click) { var results = []; for (var i = 0; i < global_id_to_morpheme_definition.length; i++) { var morpheme_definition_pair = global_id_to_morpheme_definition[i]["morpheme_definition"]; var match_found = false; for (var j = 0; j < morpheme_definition_pair.length; j++) { if (morpheme_definition_pair[j]["moroword"] == morpheme_click && morpheme_definition_pair[j]["definition"] == definition_click) { match_found = true; break; } } if (match_found) { results = results.concat (global_id_to_morpheme_definition[i]["id"]); //{sentence_id:dirtydata.rows[i].id, utterance_match:sentence.utterance, morphemes_match:sentence.morphemes, gloss_match:sentence.gloss, translation_match:sentence.translation}); } } //console.log(results); return results; } function get_rows(list_of_id) { var results = []; for (var i = 0; i rootindex) { gloss = '-'+gloss; morpheme = '-'+morpheme; results.push({moroword:[{word:morpheme, count:1}], definition:gloss}); click_database_result.push({moroword:morpheme, definition:gloss}); } else { results.push({moroword:[{word:morpheme, count:1}], definition:gloss}); click_database_result.push({moroword:morpheme, definition:gloss}); } } } return [results, click_database_result]; } //merge two arrays and de-duplicate items function arrayUnique(array) { var a = array.concat(); for(var i=0; i {comma} {pair.morpheme} ; }) return (

{rendered_morphemes}

{this.props.definition}
); } }); // ReactClass for rendering many definitions var DictList = React.createClass({ render: function() { var definitions=this.props.data.map(function(def) { return ( ) }); return (
{definitions}
); } }); //SEARCH CODE //matchSearchFunc for definition to searchTerm (EngPlain) function matchSearchFuncEngPlain (searchTerm) { return function(element) { if (element.definition == searchTerm) { return true; } else { return false; } } } //matchSearchFunc for definition to searchTerm (EngRegex) function matchSearchFuncEngRegex (searchTerm) { return function(element) { var re = ".*" + searchTerm + ".*"; if (element.definition.match(re)) { return true; } else { return false; } } } //matchSearchFunc for moroword to searchTerm (MoroPlain) function matchSearchFuncMoroPlain (searchTerm) { return function(element) { return findMoroWordInArrayMoroPlain(element.moroword, searchTerm) } } //matchSearchFunc healper for moroword to searchTerm (without regrex) function findMoroWordInArrayMoroPlain (categories, moroword) { var found = false; for (i = 0; i < categories.length && !found; i++) { if (categories[i] === moroword) { found = true; } } return found } //matchSearchFunc for moroword to searchTerm (MoroRegex) function matchSearchFuncMoroRegex (searchTerm) { return function(element) { return findMoroWordInArrayMoroRegex(element.moroword, searchTerm) } } //matchSearchFunc healper for moroword to searchTerm (with regrex) function findMoroWordInArrayMoroRegex (categories, moroword) { var found = false; for (i = 0; i < categories.length && !found; i++) { // if (categories[i] === moroword) { var re = ".*" + moroword + ".*"; if (categories[i].match(re)) { found = true; } } return found } // React container for rendering 1 page of dictionary entries, with a // header and footer for page navigation. var DictPage = React.createClass({ render: function() { if (firstLoad == true) { global_whole_data = this.props.data; firstLoad = false; } var data = this.props.data; var search = this.props.search; if (search == "") { data = global_whole_data; } else { var filter; if (this.props.search_language == 'eng') { if(this.props.regex) { filter = matchSearchFuncEngRegex; } else { filter = matchSearchFuncEngPlain; } } else { if(this.props.regex) { filter = matchSearchFuncMoroRegex; } else { filter = matchSearchFuncMoroPlain; } } data = data.filter(filter(search)); } // TODO: We might have to compute the alphabet on-demand here, since // our skips are going to be wrong. var skip = this.props.skip; var pagesize = this.props.limit; var length = data.length; if (length == 0) { return
No Results Found
} else { pc = GetPaginationControls(skip, length, pagesize); return
{pc.page_controls} {pc.page_controls}
} } }); // React container that will show a loading dimmer until the dictionary data is available; then renders definitions var DictBox = React.createClass({ getInitialState: function() { return { data: [], loaded: false, }; }, clearSkip : function() { UpdateQuery({'skip': 0}); }, componentDidMount: function() { dictionary_data_promise.then(function(dictdata) { // Find the first index of each letter, grouping numbers. var alphabet = {} _.forEach(dictdata, function consider_word(word, index) { var c = _.get(word, ["moroword", 0, 0], ""); if (c == "-") { c = _.get(word, ["moroword", 0, 1], ""); } c = "" + c; if (c.match(/[0-9]/)) { c = '0-9'; } if (c) { if (alphabet[c] == undefined) { alphabet[c] = index; } } }); this.setState( { data: dictdata, alphabet: alphabet, loaded: true }, function() { $(this.refs.right_half.getDOMNode()).sticky({}); }.bind(this)); }.bind(this)); }, render: function() { if (this.state.loaded) { var alphabet = this.state.alphabet; var alphabet_buttons = _.map(_.toPairs(alphabet), function(pair) { var letter = pair[0]; var skip = pair[1]; return {letter} ; }); var data = this.state.data; return (

Concordance:

{alphabet_buttons}
); } return
Loading
} }); // Dictionary view with concordance. var ConcordanceView = React.createClass({ render: function() { var morpheme = this.props.params.morpheme var definition = this.props.params.definition var list_of_occurrence = get_occurrence_ids(morpheme, definition); var list_of_four_sentences = get_rows(list_of_occurrence) var sentences = _.map(list_of_four_sentences, function(x) { return }); return
Definition for: {this.props.params.morpheme} is {this.props.params.definition}
Occurred at:
{sentences}
} }); var DictView = React.createClass({ render: function() { return
} }); //===================================================Text Page================================== // React Class that renders list of stories with links to story content pages (w/loading dimmer) var TextBox = React.createClass({ getInitialState: function() { return {data: [], loaded: false}; }, componentDidMount: function() { story_data_promise.then(function(rawdata){ this.setState({data: rawdata, loaded: true}); }.bind(this)) }, render: function() { if (this.state.loaded){ var results = this.state.data.rows.map(function (x) { return
  • {x.value.name} by {x.value.author}
  • }); return
      {results}
    ; } else { return
    Loading
    } } }); // A component to render a single sentence. var Sentence = React.createClass({ render: function() { var gloss = ''; var sentence = this.props.sentence; if (this.props.only_utterance) { return
    {sentence.utterance}
    ; } if (this.props.only_translation) { return
    {sentence.translation}
    ; } // interlinear gloss alignment if (this.props.show_gloss) { var morphemes = sentence.morphemes.split(' '); var glosses = sentence.gloss.split(' '); var pairs = _.zip(morphemes, glosses); // render one inline block div containing morpheme and gloss per word var glosses = _(pairs).map(function(x, i){ var morpheme = x[0]; var gloss = x[1]; return
    {morpheme}
    {gloss}
    }.bind(this)).value(); gloss = {glosses}
    ; } // render utterance and translation return
    {sentence.utterance}
    {gloss} {sentence.translation}
    } }); //React Class for a single story view var StoryView = React.createClass({ //React object state // //sentence: loaded flag and sentence data //story: loaded flag and story data //show_gloss: flag true if we show interlinear gloss lines getInitialState: function() { return {sentence: {data: [], loaded: false}, story: {data: [], loaded: false}, show_gloss: false, story_view: false, }; }, //queue uploading of story and sentence data when this component is mounted componentDidMount: function() { story_data_promise.then(function(rawdata){ this.setState({story:{data: rawdata.rows, loaded: true}}); }.bind(this)); sentence_data_promise.then(function(sentences){ this.setState({sentence:{data: sentences, loaded: true}}); }.bind(this)); }, //only ready to display story when story and sentence data have loaded loaded: function() { return this.state.story.loaded && this.state.sentence.loaded; }, // Get the story object getStory: function() { var arr = this.state.story.data; for (var i = 0; i < arr.length; i++) { var o = arr[i]; if (o.key == this.props.params.key) { return o.value; } } return {}; }, //return name of story by searching story data for this story's id getStoryName: function() { return _.get(this.getStory(), 'name', ""); }, //return author of story by searching story data for this story's id getStoryAuthor: function() { return _.get(this.getStory(), 'author', ""); }, //toggles interlinear gloss or not toggleGloss: function() { var new_show_gloss = !this.state.show_gloss; var new_story_view = this.state.story_view; if(new_show_gloss) { new_story_view = false; } this.setState({show_gloss: new_show_gloss, story_view: new_story_view}); }, //toggles story view toggleStoryView: function() { var new_show_gloss = this.state.show_gloss; var new_story_view = !this.state.story_view; if(new_story_view) { new_show_gloss = false; } this.setState({show_gloss: new_show_gloss, story_view: new_story_view}); }, //renders component render: function() { // If we haven't loaded yet, just render the dimmer. if (!this.loaded()) { return
    Loading
    ; } // process sentence data to render alignment of morphemes/glosses and show one clause per line // lodash chaining: https://lodash.com/docs#_ var sentences; var story_sentences = _(this.state.sentence.data).filter( // render sentences from this story function(x){ return x.value.story == this.props.params.key; }.bind(this) ); if (this.state.story_view) { var sentence_rows = story_sentences.map( function(x) { return [ (
    ), (
    ) ]; }.bind(this) ).value(); sentences = (

    Moro

    English

    {sentence_rows}
    ); } else { sentences = story_sentences.map( // how to render a sentence function(x){ return ; }.bind(this) ).value(); } // render story content page with title and checkbox to toggle interlinear gloss display return (

    {this.getStoryName()}

    by {this.getStoryAuthor()}
    {sentences}
    ); } }); var Homepage = React.createClass( {render: function() { //=========================HOMEPAGE=============================== return

    Moro Story Corpus

    This website contains a collection of texts and stories in the Moro language. Moro is a language of the Nuba Mountains of Sudan, where the Moro people have lived since ancient times. Moro is a Kordofanian language, a subgroup of the Niger-Congo language family. Through the texts on this page you can learn more about the Moro, their culture, and their traditional stories.

    This page is intended as a resource for Moro people as well as researchers who are interested in learning more about the Moro language. Written Moro is a mixture of dialects, but often closely resembles the Wërria dialect. As in all written Moro, tone is not marked in these stories.

    The website, including the concordance, is generated from a json file from a single script developed by Hannah Sande, Marcus Ewert, and Maytas Monsereenusorn, available on Github. The development of this corpus was supported by a grant from the Hellman Fellows Fund.

    Project members

    Angelo Naser (Author, Editor)

    Born and raised in the village of Karkaria in the Nuba Mountains, Angelo now lives and works in Khartoum. Angelo is a member of the Moro Language Committee and is involved in the Moro literacy program. He works at the United Bible Society in Khartoum.

    Peter Jenks (Editor)

    Peter has studied the Moro language since 2005, much of his work in collaboration with the Moro Language Project. He is an Assistant Professor at UC Berkeley.

    Hannah Sande (Editor)

    Hannah is a PhD candidate in the UC Berkeley linguistics department. In addition to her work on Moro, Hannah has worked extensively on Guebie, an endangered Kru language spoken in the Ivory Coast.

    Marcus Ewert

    Marcus is a software developer in the Bay Area who has provided assistance with several linguistic documentation projects.

    Juwon Kim

    UC Berkeley Class of 2018, Juwon is a double major in linguistics and computer science.

    Maytas Monsereenusorn

    UC Berkeley Class of 2016, Maytas is a double major in computer science and economics.

    } } ) var Glosspage = React.createClass( {render: function() { //=========================GLOSS PAGE=============================== return
    The abbreviations below are used for glossing in the Moro texts and Moro concordance.

    Abbreviations

    1sg
    1st person singular subject agreement
    1du
    1st person dual inclusive subject agreement
    1in
    1st person plural inclusive subject agreement
    1ex
    1st person plural exclusive subject agreement
    2sg
    2nd person singular subject agreement
    2sg.inf
    Infinitive 2nd person singular subject agreement
    2pl
    2nd person plural subject agreement
    3sg.inf
    Infinitive 3rd person singular subject agreement
    acc
    Accusative case
    ap
    Antipassive
    appl
    Applicative
    assoc.pl
    Associative plural
    be.rt
    Predicative copula
    be.loc
    Locative copula
    be.1d
    Deictic copula: be here
    be.2d
    Deictic copula: be there
    be.3d
    Deictic copula: be yonder
    be.eq
    Equative copula
    appl
    Applicative
    caus
    Causative
    clg
    Noun class agreement/concord: class g (sg/pl)
    cll
    Noun class agreement/concord: class l (sg/pl)
    cly
    Noun class agreement/concord: class n (pl)
    clŋ
    Noun class agreement/concord: class ŋ (sg/pl)
    clɲ
    Noun class agreement/concord: class ɲ (pl)
    clr
    Noun class agreement/concord: class r (pl)
    clð
    Noun class agreement/concord: class ð (sg)
    clð.nom
    Nominalization prefix: class ð
    cly
    Noun class agreement/concord: class y (sg/pl)
    cmp
    Comparative
    comp1
    Finite complementizer
    comp1b
    Consecutive imperfective complementizer
    comp2
    Infinitive and consecutive perfective complementizer
    cmp
    Comparative
    d.ipfv
    Distal imperfective
    d.inf1
    Distal infinitive 1
    d.inf2
    Distal infinitive 2
    d.imp
    Distal imperative
    dpc
    Finite dependent clause vowel
    foc
    Focus cleft
    fut.aux
    Future auxiliary
    imp
    Imperative
    indef
    Indefinite
    inf1
    Infinitive 1
    inf2
    Infinitive 2
    inst
    Instrumental clitic
    ipfv
    Imperfective
    iter
    Iterative/durative aspect
    juss
    Jussive
    loc
    Locative
    loc.appl
    Locative applicative
    not.aux
    Negative auxiliary
    om
    Object marker (object pronouns)
    pass
    Passive
    past.aux
    Past tense auxiliary
    pfv
    Perfective
    plz
    Politeness marker
    poss
    Possessive/genitive
    pro
    Pronoun
    prog
    Progressive
    prog.aux
    Progressive auxiliary
    rtc
    Finite root clause vowel
    sclg
    Strong (definite) noun class concord: class g (sg/pl)
    scll
    Strong (definite) noun class concord: class l (sg/pl)
    scly
    Strong (definite) noun class concord: class n (pl)
    sclŋ
    Strong (definite) noun class concord: class ŋ (sg/pl)
    sclɲ
    Strong (definite) noun class concord: class ɲ (pl)
    sclr
    Strong (definite) noun class concord: class r (pl)
    sclð
    Strong (definite) noun class concord: class ð (sg)
    scly
    Strong (definite) noun class concord: class y (sg/pl)
    way
    Manner clitic suffix
    ynq
    Yes/no question marker
    } } ) //=========================Search Page=============================== var SearchPane = React.createClass({ getInitialState: function() { return { sentence: {data: [], loaded: false}, story: {data: [], loaded: false} }; }, //queue uploading of story and sentence data when this component is mounted componentDidMount: function() { story_data_promise.then(function(rawdata){ this.setState({story:{data: rawdata.rows, loaded: true}}); }.bind(this)); sentence_data_promise.then(function(sentences){ this.setState({sentence:{data: sentences, loaded: true}}); }.bind(this)); }, //only ready to display story when story and sentence data have loaded loaded: function() { return this.state.story.loaded && this.state.sentence.loaded; }, render_results: function(stories, results) { var results_per_story = _.reduce(results, function(acc, x) { var new_list = _.get(acc, x.key[0], []); new_list.push(x); acc[x.key[0]] = new_list; return acc; }, {}) var rendered_results = _(_.toPairs(results_per_story)).map( function(x) { var story = x[0]; var sentences = x[1]; var rendered_sentences = _.map(sentences, function(x) { return }); var storyname = _.get(_.filter(stories, function(x) { return x.key == story; }), '[0].value.name', 'UNKNOWN STORY'); return
    Results from {storyname} : {rendered_sentences}
    ; } ).value(); return rendered_results; }, clearSkip : function() { UpdateQuery({'skip': 0}); }, render: function() { function matchesAnySentence(x) { return ( x.value.sentence.translation.search(search_regex) != -1 || x.value.sentence.gloss.search(search_regex) != -1 || x.value.sentence.utterance.search(search_regex) != -1 || x.value.sentence.morphemes.search(search_regex) != -1 ) } if (!this.loaded()) { return
    Loading
    ; } var search_regex = new RegExp(this.props.search); var results = _.filter(this.state.sentence.data, matchesAnySentence); var skip = this.props.skip; var pagesize = this.props.limit; var length = results.length; pc = GetPaginationControls(skip, length, pagesize); results = _.take(_.drop(results, pc.skip), pagesize); var rendered_results; if (results.length > 0) { var rr = this.render_results(this.state.story.data, results); rendered_results =
    {pc.page_controls} {rr} {pc.page_controls}
    } else { rendered_results =
    No Matches Found.
    } return (

    {rendered_results}
    ); } }); var SearchPage = React.createClass({ render: function() { return ( ); } }); //render page template using ReactRouter: https://github.com/rackt/react-router/blob/0.13.x/docs/guides/overview.md var App = React.createClass({ componentDidMount: function() { $(React.findDOMNode(this.refs.glossingPopupActivator)).popup({ hoverable: true, inline: true, position: 'bottom right', }); }, render: function() { return
    About Texts Concordance Search Glossing
    click for complete list
    appl
    Applicative
    clX
    Noun class X agreement
    d
    Distal
    comp
    Complementizer
    cons
    Consecutive
    dpc
    Dependent clause vowel
    ipfv
    Imperfective
    inf
    Infinitive
    pass
    Passive
    pfv
    Perfective
    rtc
    Finite root clause vowel
    About Texts Concordance
    } }); // set up routes for ReactRouter: https://github.com/rackt/react-router/blob/0.13.x/docs/guides/overview.md // enables the single-page web app design var routes = ; ReactRouter.run( routes, function(Handler) { React.render(, document.getElementById('content')) } );