Desktop app for developing a legal dictionary and managing legal terminology.
Built with TypeScript/Node, Electron, Firebase and MongoDB.
Desktop app for:
- parsing entries from two giant legal dictionaries in DOCX →
Parser
- displaying, editing and exploring interconnected entries →
Client
The app is intended to facilitate the development of a bilingual legal dictionary.
🚧 Work in progress: The data conversion section of the app is complete, while the interface is under construction.
Features:
- User interface built with Tailwind CSS (forthcoming!)
- TypeScript/Electron client and IPC channels
- Authentication via Google Sign In + Firebase
- Embedded web content from third-party sources (forthcoming!)
- Storage of user preferences and search history (forthcoming!)
- Snappy DOCX-to-JSON parser in TypeScript/Node
- Completely tested and documented conversion process
.
├── client # desktop app
├── config # credentials management
├── db # DB managers, models, JSON/DOCX data
├── docs # documentation materials
├── services # parsing logic and helpers
├── utils
└── tests
- Install Node v10.15.1
- Clone repo and install dependencies.
- Set up Firestore and Firebase Auth: See below
- Set up MongoDB: See below
- Go to the Firebase console and
Add project
- Select
Database
from the left nav andCreate database
- At the Firebase Console, go to
Project Overview
→+ Add app
→Web
- Name the web app and note down its
apiKey
,authDomain
andprojectId
.
TODO: Write instructions for getting Google OAuth credentials.
- Create the following
.env
file at the/config
dir.
# DOCX filepaths
DOCX_PATH_ENGLISH="db/data/docx/sample_eng.docx"
DOCX_PATH_SPANISH="db/data/docx/sample_spa.docx"
# Firebase credentials
FIREBASE_API_KEY="yourApiKey"
FIREBASE_AUTH_DOMAIN="your.auth.domain"
FIREBASE_PROJECT_ID="yourProjectId"
# Google OAuth credentials
GOOGLE_OAUTH_CLIENT_ID="yourOAuthClientId"
GOOGLE_OAUTH_CLIENT_SECRET="yourOAuthClientSecret"
GOOGLE_OAUTH_REDIRECT_URI="yourOAuthRedirectUri"
GOOGLE_OAUTH_AUTHORIZE_URL="yourAuthorizeUrl"
GOOGLE_OAUTH_RESPONSE_TYPE="token"
GOOGLE_OAUTH_SCOPE="yourOauthScopes"
- Install MongoDB Community Server, including MongoDB compass.
- In MongoDB compass, connect to
mongodb://localhost:27017/
. - Create a db called
normative
and, inside it, create two collections:EnglishEntries
andSpanishEntries
- Index both collections on the
term
field.
$ npm run [script]
Script | Action |
---|---|
client |
Run the Electron client. |
dev |
Run the Electron client while hot-reloading/recompiling any changes. |
test |
Run ~30 unit tests for the parser. |
css |
Optimize ./client/css/tailwind.css with PostCSS plugins, only in production. |
Script | Action |
---|---|
conv:eng |
Convert the source English DOCX into a single JSON file and a summary file. |
conv:spa |
Convert the source Spanish DOCX into a single JSON file and a summary file. |
del:json:eng |
Delete all JSON files in the ./conversion/json/English directory. |
del:json:spa |
Delete all JSON files in the ./conversion/json/Spanish directory. |
log:entry:eng [term] |
Retrieve an English entry from JSON and log it to the console in pretty colors. |
log:entry:spa [term] |
Retrieve a Spanish entry from JSON and log it to the console in pretty colors. |
Script | Action |
---|---|
imp:fire:eng |
Import the single English JSON file and summary into the EnglishEntries Firestore collection. |
imp:fire:spa |
Import the single Spanish JSON file and summary into the SpanishEntries Firestore collection. |
del:fire:eng |
Delete all English entries from the EnglishEntries Firestore collection. |
del:fire:spa |
Delete all Spanish entries from the SpanishEntries Firestore collection. |
Script | Action |
---|---|
imp:mongo:eng |
Import the single English JSON file and summary into the EnglishEntries MongoDB collection. |
imp:mongo:spa |
Import the single Spanish JSON file and summary into the SpanishEntries MongoDB collection. |
del:mongo:eng |
Delete all English entries from the EnglishEntries MongoDB collection. |
del:mongo:spa |
Delete all Spanish entries from the SpanishEntries MongoDB collection. |
Designed for efficiently parsing two giant English-Spanish and Spanish-English legal dictionaries in DOCX format, each containing over 90,000 entries with richly formatted fields such as term
, translation
, definition
, note
, classifiedUnder
, classifiedInto
, tantamountTo
, differentFrom
, derivedFrom
, derivedInto
and reference
. Since the original DOCX files are proprietary, small samples of these files are included in the ./conversion/docx
directory.
The conversion process maps MS Word styles to custom tags for four fields, transforms all entries into HTML, extracts the text of custom tagged and non-custom-tagged (i.e. "loose") fields, parses each entry into an Entry
object, and saves them all as one or multiple JSON files. Italics and superscript are retained!
The Client
class controls the main process, spawns renderer processes and registers IPC channels for communication with the renderer processes. For now, the main process has three IPC channels registered for receiving and responding to requests from renderer processes: one for retrieving entries, one for retrieving summaries, and one for user login.
export default class Client {
window: BrowserWindow | null;
db: DB;
constructor() {
app.on("ready", this.createWindow);
app.on("window-all-closed", this.onWindowAllClosed);
app.allowRendererProcessReuse = true;
this.db = new MongoDB("English");
this.db.init();
this.registerIpcChannels();
}
/**Sets up all the channels for handling events from the renderer process.*/
registerIpcChannels() {
const ipcChannels: IpcChannel[] = [
new EntryChannel(this.db),
new SummaryChannel(this.db),
new AuthChannel()
];
ipcChannels.forEach(channel =>
ipcMain.on(channel.name, (event, argument?: string) =>
channel.handle(event, argument)
)
);
}
private createWindow() {
// renderer process
this.window = new BrowserWindow({
width: 800,
height: 600,
resizable: false,
webPreferences: { nodeIntegration: true }
});
this.window.loadURL("file://" + process.cwd() + "/client/index.html");
this.window.webContents.openDevTools();
this.window.on("closed", () => {
this.window = null; // ensure destruction!
});
}
private onWindowAllClosed = () => {
if (process.platform === "darwin") return; // replicate macOS
this.db.disconnect();
app.quit();
};
}
IPC requests originating in the view are encapsulated in the Requester
class used inside the renderer process. This class forwards the requests to the Client
and promisifies (i.e. returns inside a Promise
) the responses it receives.
export default class Requester {
public request<T>(channel: string, term?: string): Promise<T> {
ipcRenderer.send(channel, term);
return new Promise(resolve => {
ipcRenderer.on(channel, (event, response) => {
resolve(response);
});
});
}
}
© 2020 Iván Ovejero
Distributed under the MIT License. See LICENSE.md