Skip to content

Latest commit

 

History

History
195 lines (150 loc) · 6.33 KB

README.md

File metadata and controls

195 lines (150 loc) · 6.33 KB

Reminisce

Live Site!

Background and Overview

Reminisce is essentially a "GitHub for Writers" built using the MERN stack -- MongoDb, Express.js, React, and Node.js. We created our own version control system and text editor and combined them into a single application that enables users to write, commit their progress, and toggle between multiple drafts (e.g. for alternate endings).

We enabled writers to execute complex version control actions by recreating the committing and branching workflows natively and simplifying processes by removing the use of terminal and using custom React modals in its place. We used Myers' diff algorithm to identify the text differences between commits and branches.

Contents

Technologies Used

Backend

  • Backend Framework: Node/Express (v8.11.1/v4.16.13)
  • Database: MongoDB (v3.0.6)
  • User Authentication: Google OAuth

Frontend

  • Frontend Framework: React/Redux (v16.3.2/v4.0.0)
  • Notable React Library: DraftJs
  • Styling: HTML5/CSS3

Features

Splash Page

Users must log in in order to access the site. Users can log in via Google OAuth. Splash Page

Projects Dashboard

Once logged in, user will be presented with all projects that they've created or have been added as a collaborator on. Projects Dashboard

Project Show Page

List of documents that pertain to the selected project. On this page, users can:

  • Toggle between different drafts (branches)
  • Click a specific document to edit
  • Go to the save history page for a history of saves for the project.
  • Save the current project draft (commit)

Project Show Page

Document Editor

Editing a specific document. Users will be spending the bulk of their time on this page. They can save this page directly, or toggle between past drafts as well.

Document Editor Page

Save History Modal

Log of past saves (commits). When clicking on a specific save, a modal will pop up with the differentials between the current save and the save immediately prior. There will be a mini map with green and red highlights where users can jump to the sections of the document that changed.

Save History Modal with Diffs

Resolve Conflicts from Merging Drafts

Once the user decides to combine two drafts, if there are any merge conflicts (same paragraph was changed in both drafts), the conflicting paragraph will be displayed for the user to decide which version to keep. In the resolve conflicts modal, paragarphs before and after the conflict will be included for context.

Resolve Conflicts Modal

Code Highlights

Applying Myers' Diff Algorithm for text diffing

To find and display the diffing for the user, we used Myers' algorithm to find the "shortest edit" from the one commit version to another.

const shortestEdit = (original, target) => {
  const [n, m] = [original.length, target.length];
  const max = n + m;
  const trace = [[0]];
  let originalIdx, targetIdx;

  for (let edits = 0; edits <= max; edits++) {
    const v = [];
    const prev = trace[trace.length - 1];

    for (let deletes = 0; deletes <= edits; deletes++) {
      if (
        deletes === 0 ||
        (deletes !== edits && prev[deletes - 1] < prev[deletes])
      ) {
        originalIdx = prev[deletes];
      } else {
        originalIdx = prev[deletes - 1] + 1;
      }

      targetIdx = originalIdx - (2 * deletes - edits);

      while (
        originalIdx < n &&
        targetIdx < m &&
        original[originalIdx] === target[targetIdx]
      ) {
        originalIdx++;
        targetIdx++;
      }

      v.push(originalIdx);

      if (originalIdx >= n && targetIdx >= m) {
        return trace;
      }
    }
    trace.push(v);
  }
};

const backtrack = (original, target) => {
  const result = [];
  const trace = shortestEdit(original, target);
  let [originalIdx, targetIdx] = [original.length, target.length];

  for (let edits = trace.length - 1; edits >= 0; edits--) {
    const row = trace[edits];
    const deletes = (originalIdx - targetIdx + edits) / 2;
    let prevDeletes;
    if (
      deletes === 0 ||
      (deletes !== edits && row[deletes - 1] < row[deletes])
    ) {
      prevDeletes = deletes;
    } else {
      prevDeletes = deletes - 1;
    }

    const prevOriginalIdx = row[prevDeletes];
    const prevTargetIdx = prevOriginalIdx - (2 * prevDeletes - edits + 1);

    while (originalIdx > prevOriginalIdx && targetIdx > prevTargetIdx) {
      result.push([originalIdx - 1, targetIdx - 1, originalIdx, targetIdx]);
      originalIdx--;
      targetIdx--;
    }
    if (edits > 0) {
      result.push([prevOriginalIdx, prevTargetIdx, originalIdx, targetIdx]);
    }
    [originalIdx, targetIdx] = [prevOriginalIdx, prevTargetIdx];
  }
  return result;
};

const diffRevisions = (original, target) => {
  const result = [];

  for (const [
    prevOriginalIdx,
    prevTargetIdx,
    originalIdx,
    targetIdx
  ] of backtrack(original, target)) {
    if (originalIdx === prevOriginalIdx) {
      result.push({
        type: 'insert',
        data: target[prevTargetIdx],
        origIdx: originalIdx - 1,
        targetIdx: prevTargetIdx + 1
      });
    } else if (targetIdx === prevTargetIdx) {
      result.push({
        type: 'delete',
        data: original[prevOriginalIdx],
        origIdx: prevOriginalIdx + 1,
        targetIdx: targetIdx - 1
      });
    } else {
      result.push({
        type: 'none',
        data: original[prevOriginalIdx],
        origIdx: prevOriginalIdx + 1,
        targetIdx: prevTargetIdx + 1
      });
    }
  }

  result.reverse();
  return result;
};

Planned Work

  • User Show Page with statistics
  • Add collaborators
  • Enable comments