First Commit

This commit is contained in:
2024-09-05 00:05:20 +05:30
parent 8f85d3435b
commit bdbab37c4b
67 changed files with 12017 additions and 289 deletions

View File

@@ -9,6 +9,5 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

6
lerna.json Normal file
View File

@@ -0,0 +1,6 @@
{
"packages": [
"src/packages/*"
],
"version": "independent"
}

11118
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,11 @@
{
"name": "@armco/react-vite-rtk-template",
"name": "@armco/sampadak",
"private": true,
"version": "0.0.0",
"type": "module",
"workspaces": [
"src/packages/*"
],
"scripts": {
"dev": "vite",
"start": "vite",
@@ -25,16 +28,21 @@
"react-dev-utils": "^12.0.1",
"react-dom": "^18.2.0",
"react-redux": "^8.0.1",
"react-router-dom": "^6.13.0"
"react-router-dom": "^6.13.0",
"uuid": "^10.0.0"
},
"devDependencies": {
"@armco/types": "^0.0.6",
"@testing-library/dom": "^9.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.2.5",
"@types/moment": "^2.13.0",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-table": "^7.7.20",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.0.0",
"eslint-config-react-app": "^7.0.1",
@@ -75,7 +83,7 @@
],
"license": "ISC",
"bugs": {
"url": "https://github.com/ReStruct-Corporate-Advantage/react-vite-rtk-template/issues"
"url": "https://github.com/ReStruct-Corporate-Advantage/sampadak/issues"
},
"homepage": "https://github.com/ReStruct-Corporate-Advantage/react-vite-rtk-template#readme"
"homepage": "https://github.com/ReStruct-Corporate-Advantage/sampadak#readme"
}

7
src/app/Router.js Normal file
View File

@@ -0,0 +1,7 @@
import { useRoutes } from "react-router-dom";
import * as pages from "./pages";
import Helper from "./utils/helper";
import ROUTES from "./routes";
Helper.populateComponentsInRoutes(ROUTES, pages);
var Router = function (props) { return useRoutes(ROUTES); };
export default Router;

View File

@@ -0,0 +1 @@
"use strict";

4
src/app/hooks.js Normal file
View File

@@ -0,0 +1,4 @@
import { useDispatch, useSelector } from "react-redux";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export var useAppDispatch = useDispatch;
export var useAppSelector = useSelector;

View File

@@ -1,3 +0,0 @@
.c-Home {
}

View File

@@ -1,18 +0,0 @@
import { createSlice } from "@reduxjs/toolkit"
export interface HomeState {}
const initialState: HomeState = {}
export const homeSlice = createSlice({
name: "home",
initialState,
reducers: {
increment: (state) => {},
},
extraReducers: (builder) => {},
})
export const { increment } = homeSlice.actions
export default homeSlice.reducer

View File

@@ -1,8 +0,0 @@
import React from "react"
import Home from "./Home"
describe("Home", () => {
it("renders without error", () => {
})
})

View File

@@ -1,14 +0,0 @@
import React from "react"
import "./Home.module.scss"
interface HomeProps {}
const Home = props => {
return (
<div className="c-Home">
In Page Home
</div>
)
}
export default Home

View File

@@ -1,3 +0,0 @@
import Home from "./Home.jsx"
export default Home

2
src/app/pages/index.js Normal file
View File

@@ -0,0 +1,2 @@
/* PLOP_INJECT_IMPORT */
export {};

View File

@@ -1,7 +1,3 @@
/* PLOP_INJECT_IMPORT */
import Home from "./Home"
export {
/* PLOP_INJECT_EXPORT */
Home,
}
export /* PLOP_INJECT_EXPORT */ {}

8
src/app/routes.js Normal file
View File

@@ -0,0 +1,8 @@
var ROUTES = [
{
path: "/",
class: "landing",
element: "Home",
},
];
export default ROUTES;

4
src/app/store.js Normal file
View File

@@ -0,0 +1,4 @@
import { configureStore } from "@reduxjs/toolkit";
export var store = configureStore({
reducer: {},
});

View File

@@ -1,10 +1,7 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
import counterReducer from "../features/counter/counterSlice"
export const store = configureStore({
reducer: {
counter: counterReducer,
},
reducer: {},
})
export type AppDispatch = typeof store.dispatch

17
src/app/utils/helper.js Normal file
View File

@@ -0,0 +1,17 @@
import { jsx as _jsx } from "react/jsx-runtime";
var Helper = /** @class */ (function () {
function Helper() {
}
Helper.populateComponentsInRoutes = function (routes, components) {
routes &&
routes.forEach(function (route) {
var Component = components[route.element];
route.element = _jsx(Component, {});
if (route.children) {
Helper.populateComponentsInRoutes(route.children, components);
}
});
};
return Helper;
}());
export default Helper;

View File

@@ -1,67 +0,0 @@
import { useState } from "react"
import { useAppSelector, useAppDispatch } from "../../app/hooks"
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from "./counterSlice"
export function Counter() {
const count = useAppSelector(selectCount)
const dispatch = useAppDispatch()
const [incrementAmount, setIncrementAmount] = useState("2")
const incrementValue = Number(incrementAmount) || 0
return (
<div>
<div className="row">
<button
className="button"
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span className="value">{count}</span>
<button
className="button"
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className="row">
<input
className="textbox"
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
className="button"
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className="asyncButton"
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className="button"
onClick={() => dispatch(incrementIfOdd(incrementValue))}
>
Add If Odd
</button>
</div>
</div>
)
}

View File

@@ -1,6 +0,0 @@
// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
return new Promise<{ data: number }>((resolve) =>
setTimeout(() => resolve({ data: amount }), 500),
)
}

View File

@@ -1,34 +0,0 @@
import counterReducer, {
CounterState,
increment,
decrement,
incrementByAmount,
} from "./counterSlice"
describe("counter reducer", () => {
const initialState: CounterState = {
value: 3,
status: "idle",
}
it("should handle initial state", () => {
expect(counterReducer(undefined, { type: "unknown" })).toEqual({
value: 0,
status: "idle",
})
})
it("should handle increment", () => {
const actual = counterReducer(initialState, increment())
expect(actual.value).toEqual(4)
})
it("should handle decrement", () => {
const actual = counterReducer(initialState, decrement())
expect(actual.value).toEqual(2)
})
it("should handle incrementByAmount", () => {
const actual = counterReducer(initialState, incrementByAmount(2))
expect(actual.value).toEqual(5)
})
})

View File

@@ -1,84 +0,0 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"
import { RootState, AppThunk } from "../../app/store"
import { fetchCount } from "./counterAPI"
export interface CounterState {
value: number
status: "idle" | "loading" | "failed"
}
const initialState: CounterState = {
value: 0,
status: "idle",
}
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
"counter/fetchCount",
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
},
)
export const counterSlice = createSlice({
name: "counter",
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn"t actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = "loading"
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = "idle"
state.value += action.payload
})
.addCase(incrementAsync.rejected, (state) => {
state.status = "failed"
})
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they"re used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state: RootState) => state.counter.value
// We can also write thunks by hand, which may contain both sync and async logic.
// Here"s an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState())
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount))
}
}
export default counterSlice.reducer

10
src/index.js Normal file
View File

@@ -0,0 +1,10 @@
import { jsx as _jsx } from "react/jsx-runtime";
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "./app/store";
import Router from "./app/Router";
import "./app/static/styles/global.scss";
var root = ReactDOM.createRoot(document.getElementById("root"));
root.render(_jsx(React.StrictMode, { children: _jsx(BrowserRouter, { children: _jsx(Provider, { store: store, children: _jsx(Router, {}) }) }) }));

View File

@@ -0,0 +1,12 @@
{
"name": "@sampadak/collaboration",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

View File

@@ -0,0 +1,16 @@
{
"name": "@sampadak/core",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
export * from "./utils";

View File

@@ -0,0 +1 @@
export * from "./utils"

View File

@@ -0,0 +1,21 @@
export function debounce(func, wait, immediate) {
if (immediate === void 0) { immediate = false; }
var timeout;
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var later = function () {
timeout = undefined;
if (!immediate) {
func.apply(void 0, args);
}
};
clearTimeout(timeout);
if (immediate && !timeout) {
func.apply(void 0, args);
}
timeout = setTimeout(later, wait || 1000);
};
}

View File

@@ -0,0 +1,25 @@
export function debounce<T extends (...args: any[]) => void>(
func: T,
wait?: number,
immediate: boolean = false,
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout
return function (...args: Parameters<T>): void {
const later = () => {
timeout = undefined!
if (!immediate) {
func(...args)
}
}
clearTimeout(timeout)
if (immediate && !timeout) {
func(...args)
}
timeout = setTimeout(later, wait || 1000)
}
}

View File

@@ -0,0 +1 @@
export * from "./helper";

View File

@@ -0,0 +1 @@
export * from "./helper"

View File

@@ -0,0 +1,12 @@
{
"name": "@sampadak/event-manager",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

View File

@@ -0,0 +1,17 @@
{
"name": "@sampadak/formatter",
"version": "1.0.0",
"main": "build/index.js",
"scripts": {
"build": "rm -rf build && tsc",
"test": "jest"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"jest": "^26.0.0"
},
"files": ["build"]
}

View File

@@ -0,0 +1,93 @@
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { v4 as uuid } from "uuid";
export var findOverlappedChunksInSingleText = function (chunks, start, end) {
return Object.values(chunks).filter(function (chunk) { return chunk.end > start && chunk.start < end; });
};
export var findOverlappedChunks = function (contentRegistry, selectionInfo) {
var selectedNodes = selectionInfo.selectedNodes, startParentId = selectionInfo.startParentId, endParentId = selectionInfo.endParentId, startOffset = selectionInfo.startOffset, endOffset = selectionInfo.endOffset;
return Array.from(selectedNodes).reduce(function (acc, id) {
var _a;
var currentConfig = contentRegistry[id];
var start = id !== startParentId ? 0 : startOffset;
var end = id !== endParentId
? (((_a = currentConfig.text) === null || _a === void 0 ? void 0 : _a.length) || 0) - 1
: endOffset;
acc = acc.concat(findOverlappedChunksInSingleText(currentConfig.chunks, start, end));
return acc;
}, []);
};
export var isEntireSelectionFormatted = function (format, selectionInfo, contentRegistry) {
// Flatten all chunks from selected nodes
var allSelectedChunks = findOverlappedChunks(contentRegistry, selectionInfo);
// Check if all chunks in the selection already have the format
var entireSelectionFormatted = allSelectedChunks.length > 0 &&
allSelectedChunks.every(function (chunk) { return format in chunk.formats; });
return entireSelectionFormatted;
};
export var applyTool = function (format, formatValue, selectionInfo, contentState, callback) {
if (selectionInfo) {
var contentStateClone_1 = __assign({}, contentState);
var selectedNodes = selectionInfo.selectedNodes, startParentId_1 = selectionInfo.startParentId, endParentId_1 = selectionInfo.endParentId, startOffset_1 = selectionInfo.startOffset, endOffset_1 = selectionInfo.endOffset;
var entireSelectionFormatted_1 = isEntireSelectionFormatted(format, selectionInfo, contentStateClone_1.contentRegistry);
contentStateClone_1.entireSelectionFormatted = entireSelectionFormatted_1;
selectedNodes === null || selectedNodes === void 0 ? void 0 : selectedNodes.forEach(function (id) {
var _a;
var currentConfig = contentStateClone_1.contentRegistry[id];
handleNewChunk(currentConfig.chunks, [format, true], // TODO: Retain value as true, this will change for color, backgroundColor and fontSize
startParentId_1 === id ? startOffset_1 : 0, endParentId_1 === id
? endOffset_1
: ((_a = currentConfig.text) === null || _a === void 0 ? void 0 : _a.length) - 1, entireSelectionFormatted_1, currentConfig.text);
});
Array.from(selectedNodes).length > 0 &&
callback &&
callback(contentStateClone_1);
}
};
var handleNewChunk = function (chunksMap, formatDetails, start, end, entireSelectionFormatted, text) {
var format = formatDetails[0], value = formatDetails[1];
// Find all overlapped chunks, chunk out first and/or last chunks, splitting based on selection start and end and whether applied format is same or different
var overlappedChunks = findOverlappedChunksInSingleText(chunksMap, start, end);
overlappedChunks.sort(function (a, b) { return a.start - b.start; });
function createRestChunk(chunk, start, end, isLeftOverlap) {
var chunkId = uuid();
var restChunk = {
id: chunkId,
start: isLeftOverlap ? chunk.start : end,
end: isLeftOverlap ? start : chunk.end,
text: text.substring(isLeftOverlap ? chunk.start : end, isLeftOverlap ? start : chunk.end),
formats: __assign({}, chunk.formats),
parent: chunk.parent,
};
chunksMap[chunkId] = restChunk;
if (isLeftOverlap) {
chunk.start = start;
chunk.text = chunk.text.substring(start);
}
else {
chunk.end = end;
}
}
// entireSelectionFormatted && remove format from overlapped chunk, shrink the before/after chunks
// !entireSelectionFormatted && add format to all the chunks, if a chunk ends up becoming similar to previous one, extend the previous chunk, ignore current one
overlappedChunks.forEach(function (c) {
if (format in c.formats === entireSelectionFormatted) {
c.start < start && createRestChunk(c, c.start, start, true);
c.end > end && createRestChunk(c, end, c.end, false);
chunksMap[c.id] = c;
// Full contained or shrunk to be fully contained
entireSelectionFormatted
? delete c.formats[format]
: (c.formats[format] = value !== undefined ? value : true);
}
});
};

View File

@@ -0,0 +1,164 @@
import { v4 as uuid } from "uuid"
import {
ChunkInfo,
FlexContentType,
FunctionType,
SelectionInfo,
TextFormat,
} from "@armco/types"
export const findOverlappedChunksInSingleText = (
chunks: { [key: string]: ChunkInfo },
start: number,
end: number,
) => {
return Object.values(chunks).filter(
(chunk) => chunk.end > start && chunk.start < end,
)
}
export const findOverlappedChunks = (
contentRegistry: FlexContentType,
selectionInfo: SelectionInfo,
) => {
const { selectedNodes, startParentId, endParentId, startOffset, endOffset } =
selectionInfo
return Array.from(selectedNodes).reduce(
(acc: Array<ChunkInfo>, id: string) => {
const currentConfig = contentRegistry[id]
const start = id !== startParentId ? 0 : startOffset
const end =
id !== endParentId
? ((currentConfig.text as string)?.length || 0) - 1
: endOffset
acc = acc.concat(
findOverlappedChunksInSingleText(currentConfig.chunks, start, end),
)
return acc
},
[],
)
}
export const isEntireSelectionFormatted = (
format: keyof TextFormat,
selectionInfo: SelectionInfo,
contentRegistry: FlexContentType,
) => {
// Flatten all chunks from selected nodes
const allSelectedChunks: Array<ChunkInfo> = findOverlappedChunks(
contentRegistry,
selectionInfo,
)
// Check if all chunks in the selection already have the format
const entireSelectionFormatted =
allSelectedChunks.length > 0 &&
allSelectedChunks.every((chunk) => format in chunk.formats)
return entireSelectionFormatted
}
export const applyTool = (
format: keyof TextFormat,
formatValue: string | boolean | number | undefined,
selectionInfo: SelectionInfo,
contentState: {
entireSelectionFormatted?: boolean
contentRegistry: FlexContentType
},
callback: FunctionType,
) => {
if (selectionInfo) {
const contentStateClone = { ...contentState }
const {
selectedNodes,
startParentId,
endParentId,
startOffset,
endOffset,
} = selectionInfo
const entireSelectionFormatted = isEntireSelectionFormatted(
format,
selectionInfo,
contentStateClone.contentRegistry,
)
contentStateClone.entireSelectionFormatted = entireSelectionFormatted
selectedNodes?.forEach((id: string) => {
const currentConfig = contentStateClone.contentRegistry[id]
handleNewChunk(
currentConfig.chunks,
[format, true], // TODO: Retain value as true, this will change for color, backgroundColor and fontSize
startParentId === id ? startOffset : 0,
endParentId === id
? endOffset
: (currentConfig.text as string)?.length - 1,
entireSelectionFormatted,
currentConfig.text,
)
})
Array.from(selectedNodes).length > 0 &&
callback &&
callback(contentStateClone)
}
}
const handleNewChunk = (
chunksMap: { [key: string]: ChunkInfo },
formatDetails: [keyof TextFormat, string | boolean | number],
start: number,
end: number,
entireSelectionFormatted: boolean,
text: string,
) => {
const [format, value] = formatDetails
// Find all overlapped chunks, chunk out first and/or last chunks, splitting based on selection start and end and whether applied format is same or different
const overlappedChunks = findOverlappedChunksInSingleText(
chunksMap,
start,
end,
)
overlappedChunks.sort((a, b) => a.start - b.start)
function createRestChunk(
chunk: any,
start: number,
end: number,
isLeftOverlap: boolean,
) {
const chunkId = uuid()
const restChunk = {
id: chunkId,
start: isLeftOverlap ? chunk.start : end,
end: isLeftOverlap ? start : chunk.end,
text: text.substring(
isLeftOverlap ? chunk.start : end,
isLeftOverlap ? start : chunk.end,
),
formats: { ...chunk.formats },
parent: chunk.parent,
}
chunksMap[chunkId] = restChunk
if (isLeftOverlap) {
chunk.start = start
chunk.text = chunk.text.substring(start)
} else {
chunk.end = end
}
}
// entireSelectionFormatted && remove format from overlapped chunk, shrink the before/after chunks
// !entireSelectionFormatted && add format to all the chunks, if a chunk ends up becoming similar to previous one, extend the previous chunk, ignore current one
overlappedChunks.forEach((c) => {
if (format in c.formats === entireSelectionFormatted) {
c.start < start && createRestChunk(c, c.start, start, true)
c.end > end && createRestChunk(c, end, c.end, false)
chunksMap[c.id as string] = c
// Full contained or shrunk to be fully contained
entireSelectionFormatted
? delete c.formats[format]
: ((c.formats as any)[format] = value !== undefined ? value : true)
}
})
}

View File

@@ -0,0 +1,9 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"noEmit": false,
"outDir": "build"
},
"include": ["src"]
}

View File

@@ -0,0 +1,12 @@
{
"name": "@sampadak/history",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

View File

@@ -0,0 +1,12 @@
{
"name": "@sampadak/persistence",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

View File

@@ -0,0 +1,16 @@
{
"name": "@sampadak/plugin-manager",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,16 @@
{
"name": "@sampadak/selection-manager",
"version": "1.0.0",
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
"build": "rm -rf build && tsc && cp src/types.d.ts build",
"test": "jest"
},
"devDependencies": {
"jest": "^26.0.0"
},
"files": [
"build"
]
}

View File

@@ -0,0 +1,36 @@
import { processCaret, processRange } from "./util";
var SelectionManager = /** @class */ (function () {
function SelectionManager() {
}
SelectionManager.getSelection = function (contentRegistry, doc, win, atomicContentSelector) {
var docObj = doc || document;
var selection = (win || window).getSelection();
atomicContentSelector = atomicContentSelector || "ar-Text";
if (selection) {
if (selection.type === "Range") {
return processRange(selection, docObj, contentRegistry, atomicContentSelector);
}
else if (selection.type === "Caret" && contentRegistry) {
return processCaret(selection, contentRegistry, atomicContentSelector);
}
}
return null;
};
SelectionManager.setSelection = function (range) {
throw new Error("Method not implemented.");
};
SelectionManager.clearSelection = function () {
throw new Error("Method not implemented.");
};
SelectionManager.selectAll = function () {
throw new Error("Method not implemented.");
};
SelectionManager.getSelectedText = function () {
throw new Error("Method not implemented.");
};
SelectionManager.hasSelection = function () {
throw new Error("Method not implemented.");
};
return SelectionManager;
}());
export default SelectionManager;

View File

@@ -0,0 +1,46 @@
import { FlexContentType, SelectionInfo } from "@armco/types"
import { ISelectionManager } from "./types"
import { processCaret, processRange } from "./util"
class SelectionManager {
static getSelection(
contentRegistry: FlexContentType,
doc?: Document,
win?: Window,
atomicContentSelector?: string,
): SelectionInfo | null {
const docObj = doc || document
const selection = (win || window).getSelection()
atomicContentSelector = atomicContentSelector || "ar-Text"
if (selection) {
if (selection.type === "Range") {
return processRange(
selection,
docObj,
contentRegistry,
atomicContentSelector,
)
} else if (selection.type === "Caret" && contentRegistry) {
return processCaret(selection, contentRegistry, atomicContentSelector)
}
}
return null
}
static setSelection(range: Range): void {
throw new Error("Method not implemented.")
}
static clearSelection(): void {
throw new Error("Method not implemented.")
}
static selectAll(): void {
throw new Error("Method not implemented.")
}
static getSelectedText(): string {
throw new Error("Method not implemented.")
}
static hasSelection(): boolean {
throw new Error("Method not implemented.")
}
}
export default SelectionManager as ISelectionManager

View File

@@ -0,0 +1,15 @@
import { FlexContentType, SelectionInfo } from "@armco/types"
export interface ISelectionManager {
getSelection(
contentRegistry: FlexContentType,
doc?: Document,
win?: Window,
atomicContentSelector?: string,
): SelectionInfo
setSelection(range: Range): void
clearSelection(): void
selectAll(): void
getSelectedText(): string
hasSelection(): boolean
}

View File

@@ -0,0 +1,104 @@
export var getTextContainerId = function (node, atomicContentSelector) {
var _a;
var textElementNode = node.nodeType === Node.TEXT_NODE
? (_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.closest(atomicContentSelector)
: node;
return textElementNode === null || textElementNode === void 0 ? void 0 : textElementNode.id;
};
export var processRange = function (selection, docObj, contentRegistry, atomicContentSelector) {
var _a, _b, _c;
var selectionInfo = {
startOffset: -1,
endOffset: -1,
startParentId: "",
endParentId: "",
startChunkId: "",
endChunkId: "",
selectedNodes: new Set(),
raw: selection,
};
var range = selection.getRangeAt(0);
var content = range.cloneContents();
if (content.childNodes.length === 1 &&
((_a = content.firstChild) === null || _a === void 0 ? void 0 : _a.nodeType) === Node.TEXT_NODE) {
var parent_1 = (_c = (_b = range.startContainer.parentElement) === null || _b === void 0 ? void 0 : _b.closest(atomicContentSelector)) === null || _c === void 0 ? void 0 : _c.cloneNode(true);
parent_1 && content.replaceChild(parent_1, content.firstChild);
}
var startParentId = getTextContainerId(range.startContainer, atomicContentSelector);
var endParentId = getTextContainerId(range.endContainer, atomicContentSelector);
if (startParentId && endParentId) {
var startOffset = calculateOffset(range, range.startContainer, range.startOffset);
var endOffset = calculateOffset(range, range.endContainer, range.endOffset);
// Update selectionInfo properties
selectionInfo.startParentId = startParentId;
selectionInfo.endParentId = endParentId;
selectionInfo.startOffset = startOffset;
selectionInfo.endOffset = endOffset;
var walker = docObj.createTreeWalker(content, NodeFilter.SHOW_ELEMENT);
while (walker.nextNode()) {
var currNode = walker.currentNode;
var node = ((currNode === null || currNode === void 0 ? void 0 : currNode.closest(atomicContentSelector)) ||
range.commonAncestorContainer);
if (node) {
selectionInfo.selectedNodes.add(node.id);
}
}
}
return selectionInfo;
};
export var processCaret = function (selection, contentRegistry, atomicContentSelector) {
if (selection.anchorNode) {
var selectionInfo = {
startOffset: -1,
endOffset: -1,
startParentId: "",
endParentId: "",
startChunkId: "",
endChunkId: "",
selectedNodes: new Set(),
raw: selection,
};
var anchorNode = selection.anchorNode;
if (anchorNode.nodeType === Node.ELEMENT_NODE &&
anchorNode.classList.contains("ar-FlexContent")) {
// Implies there's no text yet, so we get first text config as
// at least 1 is always expected to be present
anchorNode = anchorNode.querySelector(atomicContentSelector);
}
var normalizedCursorPosition_1 = anchorNode &&
calculateOffset(selection.getRangeAt(0), anchorNode, selection.anchorOffset);
var activeText = anchorNode &&
contentRegistry[getTextContainerId(anchorNode, atomicContentSelector)];
var activeChunk = (activeText === null || activeText === void 0 ? void 0 : activeText.chunks) && normalizedCursorPosition_1 !== null
? Object.values(activeText.chunks).find(function (chunk) {
return chunk.start <= normalizedCursorPosition_1 &&
chunk.end >= normalizedCursorPosition_1;
})
: undefined;
if (normalizedCursorPosition_1) {
selectionInfo.startOffset = normalizedCursorPosition_1;
selectionInfo.endOffset = normalizedCursorPosition_1;
}
if (activeText && activeChunk) {
selectionInfo.startParentId = activeText.id;
selectionInfo.endParentId = activeText.id;
selectionInfo.startChunkId = activeChunk.id;
selectionInfo.endChunkId = activeChunk.id;
}
return selectionInfo;
}
return null;
};
export function calculateOffset(range, container, offset) {
var _a;
var relativeTo = container.nodeType === Node.TEXT_NODE
? (_a = container.parentElement) === null || _a === void 0 ? void 0 : _a.closest(".ar-Text")
: container.closest(".ar-Text");
if (relativeTo) {
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(relativeTo);
preCaretRange.setEnd(container, offset);
return preCaretRange.toString().length;
}
return -1;
}

View File

@@ -0,0 +1,162 @@
import { FlexContentType, SelectionInfo, TextInfo } from "@armco/types"
export const getTextContainerId = (
node: Node | HTMLElement,
atomicContentSelector: string,
) => {
const textElementNode =
node.nodeType === Node.TEXT_NODE
? node.parentElement?.closest(atomicContentSelector)
: node
return (textElementNode as HTMLElement)?.id
}
export const processRange = (
selection: Selection,
docObj: Document,
contentRegistry: FlexContentType,
atomicContentSelector: string,
): SelectionInfo => {
const selectionInfo: SelectionInfo = {
startOffset: -1,
endOffset: -1,
startParentId: "",
endParentId: "",
startChunkId: "",
endChunkId: "",
selectedNodes: new Set(),
raw: selection,
}
const range = selection.getRangeAt(0)
const content = range.cloneContents()
if (
content.childNodes.length === 1 &&
content.firstChild?.nodeType === Node.TEXT_NODE
) {
const parent = range.startContainer.parentElement
?.closest(atomicContentSelector)
?.cloneNode(true)
parent && content.replaceChild(parent, content.firstChild)
}
const startParentId = getTextContainerId(
range.startContainer,
atomicContentSelector,
)
const endParentId = getTextContainerId(
range.endContainer,
atomicContentSelector,
)
if (startParentId && endParentId) {
const startOffset = calculateOffset(
range,
range.startContainer,
range.startOffset,
)
const endOffset = calculateOffset(
range,
range.endContainer,
range.endOffset,
)
// Update selectionInfo properties
selectionInfo.startParentId = startParentId
selectionInfo.endParentId = endParentId
selectionInfo.startOffset = startOffset
selectionInfo.endOffset = endOffset
const walker = docObj.createTreeWalker(content, NodeFilter.SHOW_ELEMENT)
while (walker.nextNode()) {
const currNode = walker.currentNode as HTMLElement
const node = (currNode?.closest(atomicContentSelector) ||
range.commonAncestorContainer) as HTMLElement
if (node) {
selectionInfo.selectedNodes.add(node.id)
}
}
}
return selectionInfo
}
export const processCaret = (
selection: Selection,
contentRegistry: FlexContentType,
atomicContentSelector: string, // Eg. .ar-Text
): SelectionInfo | null => {
if (selection.anchorNode) {
const selectionInfo: SelectionInfo = {
startOffset: -1,
endOffset: -1,
startParentId: "",
endParentId: "",
startChunkId: "",
endChunkId: "",
selectedNodes: new Set(),
raw: selection,
}
let anchorNode: Node | HTMLElement | null = selection.anchorNode
if (
anchorNode.nodeType === Node.ELEMENT_NODE &&
(anchorNode as HTMLElement).classList.contains("ar-FlexContent")
) {
// Implies there's no text yet, so we get first text config as
// at least 1 is always expected to be present
anchorNode = (anchorNode as HTMLElement).querySelector(
atomicContentSelector,
)
}
const normalizedCursorPosition =
anchorNode &&
calculateOffset(
selection.getRangeAt(0),
anchorNode,
selection.anchorOffset,
)
const activeText =
anchorNode &&
contentRegistry[getTextContainerId(anchorNode, atomicContentSelector)]
const activeChunk =
activeText?.chunks && normalizedCursorPosition !== null
? Object.values((activeText as TextInfo).chunks).find(
(chunk) =>
chunk.start <= normalizedCursorPosition &&
chunk.end >= normalizedCursorPosition,
)
: undefined
if (normalizedCursorPosition) {
selectionInfo.startOffset = normalizedCursorPosition
selectionInfo.endOffset = normalizedCursorPosition
}
if (activeText && activeChunk) {
selectionInfo.startParentId = activeText.id
selectionInfo.endParentId = activeText.id
selectionInfo.startChunkId = activeChunk.id
selectionInfo.endChunkId = activeChunk.id
}
return selectionInfo
}
return null
}
export function calculateOffset(
range: Range,
container: Node,
offset: number,
): number {
const relativeTo =
container.nodeType === Node.TEXT_NODE
? container.parentElement?.closest(".ar-Text")
: (container as HTMLElement).closest(".ar-Text")
if (relativeTo) {
const preCaretRange = range.cloneRange()
preCaretRange.selectNodeContents(relativeTo)
preCaretRange.setEnd(container, offset)
return preCaretRange.toString().length
}
return -1
}

View File

@@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "build"
},
"include": ["src"],
}

View File

@@ -0,0 +1,12 @@
{
"name": "@sampadak/syntax-highlighter",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,16 @@
{
"name": "@sampadak/toolbar",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

View File

@@ -0,0 +1,16 @@
{
"name": "@sampadak/ui",
"version": "1.0.0",
"main": "src/index.tsx",
"scripts": {
"build": "tsc",
"test": "jest"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"jest": "^26.0.0"
}
}

View File

@@ -0,0 +1 @@
"use strict";

View File

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

View File

@@ -17,10 +17,9 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"noEmit": false,
"jsx": "react-jsx"
},
"include": [
"src"
]
"include": ["src"],
"exclude": ["**/build"]
}