Merge branch 'feature/mappers-integrated-with-model' into develop

This commit is contained in:
Ken Snyder
2022-01-31 22:11:30 -08:00
725 changed files with 9156 additions and 3736 deletions

20
.gitignore vendored
View File

@@ -1,10 +1,12 @@
.DS_Store
.vite-ssg-dist
.vite-ssg-temp
**/.DS_Store
**/.vite-ssg-dist
**/.vite-ssg-temp
*.local
dist
bin
dist-ssr
node_modules
.idea/
*.log
**/dist
**/bin
**/dist-ssr
**/node_modules
**/.idea/
**/*.log
**/.do-devops.json
**/*.env

View File

@@ -2,17 +2,17 @@ services:
# We are not using the scraper for anything currently but keeping it here
# for now in case we decide to
# scraper:
# image: getmeili/docs-scraper:latest
# container_name: scraper
# command: pipenv run ./docs_scraper config.json -d
# depends_on:
# - search
# environment:
# - MEILISEARCH_HOST_URL=localhost:7700
# - MEILISEARCH_API_KEY=""
# volumes:
# - ./scraper:/data.ms
scraper:
image: getmeili/docs-scraper:latest
container_name: scraper
command: pipenv run ./docs_scraper config.json -d
depends_on:
- search
environment:
- MEILISEARCH_HOST_URL=localhost:7700
- MEILISEARCH_API_KEY=""
volumes:
- ./scraper:/data.ms
search:
image: getmeili/meilisearch:latest

View File

@@ -1,38 +0,0 @@
<script setup lang="ts">
import { useSearch } from "~/modules/search";
const s = useSearch();
const indexPossibilites = ["repo", "prose", "TS API", "RS API"];
</script>
<template>
<div class="flex flex-col rounded-md bg-gray-100/25 dark:bg-gray-900/25">
<div class="flex flex-col space-y-2 rounded-t-md overflow-hidden">
<div class="font-bold text-lg px-4 py-3 bg-gray-900/10 dark:bg-gray-100/10">
ACTIONS
</div>
<div class="p-4 flex flex-col space-y-2">
<div v-for="idx in indexPossibilites" :key="idx" class=" rounded cursor-pointer p-1 flex">
<div class="actions flex flex-row space-x-1 items-center text-gray-100 dark:text-gray-700 transition-colors duration-200">
<carbon:next-filled class="flex hover:text-green-500" />
<carbon:error-filled class="flex hover:text-orange-500" />
</div>
<div class="flex flex-row ml-2">
<span class="font-medium">{{idx}}&nbsp;</span>
<span class="font-light">pipeline</span>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="css" scoped>
.btn {
@apply text-xs ;
}
</style>

View File

@@ -1,66 +0,0 @@
<script setup lang="ts">
import { PropType } from "vue";
import { GenericDoc } from "~/types/meilisearch";
const props = defineProps({
document: {
type: Object as PropType<GenericDoc>, required: true}
});
const doc = computed(() => props.document);
const showDetails = ref(false);
const apiKind = computed(() => {
if(doc.value._idx !== "api") {
return "";
}
switch(doc.value.kind) {
case "Function":
return "fn";
case "Namespace":
return "module";
case "Interface":
return "interface";
default:
return doc.value?.kind ? String(doc.value?.kind).toLowerCase() : "";
}
});
const details = () => {
showDetails.value = !showDetails.value;
};
</script>
<template>
<div class="flex flex-col border-1 rounded px-2 py-1 border-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 ">
<div class="flex flex-row space-x-1 items-center place-content-center cursor-pointer" @click="details">
<mdi:github v-if="doc._idx === 'repo'" class="flex flex-shrink-0" />
<vscode-icons:file-type-typescript-official v-if="doc._idx === 'api' && doc.language === 'typescript'" class="flex flex-shrink-0" />
<vscode-icons:file-type-rust v-if="doc._idx === 'api' && doc.language === 'rust'" class="flex" />
<ant-design:file-markdown-outlined v-if="doc._idx === 'prose'" class="flex" />
<div class="flex flex-shrink-0">{{doc.name}}</div>
<div class="flex flex-grow ml-1 font-light text-xs italic truncate">
{{doc.description}}
</div>
<div v-if="doc._idx === 'api'" class="flex text-xs font-medium px-1 py-0.5 bg-blue-500 dark:bg-blue-600 text-gray-50 rounded">
{{apiKind}}
</div>
<div class="flex px-1 hover:text-green-600 hover:font-bold ">
<a :href="doc.url" target="_new">
<ph:link-light class="flex" />
</a>
</div>
</div>
<div v-if="showDetails" class="items-start">
<div v-for="key in Object.keys(doc)" :key="key" class="flex flex-row">
<span class="flex font-bold">{{key}}:</span>
<span class="flex ml-1">{{doc[key]}}</span>
</div>
</div>
</div>
</template>

View File

@@ -1,42 +0,0 @@
<script setup lang="ts">
import { useSearch } from "~/modules/search";
const s = useSearch();
</script>
<template>
<div class="search-stats flex flex-col rounded-md bg-gray-100/25 dark:bg-gray-800/25">
<div class="rounded-t-md overflow-hidden">
<div class="font-bold text-lg rounded-t-md bg-gray-900/10 dark:bg-gray-100/10 py-3 px-4">
INDEXES
</div>
</div>
<div v-if="s.$state.health" class="text-xs mt-2" >
<div class="text-green-500">MeiliSearch Service Healthy!</div>
<div class="db-size">db size: {{s.dbSize}}</div>
</div>
<span v-else class="text-red-500 ">
Meilisearch is not available locally!
</span>
<div class="p-4">
<div
v-for="idx in s.$state.indexes"
:key="idx.uid"
class="index flex flex-row w-full py-2 border-1 rounded border-gray-500 p-4 mt-4"
>
<span class="index-name flex flex-grow font-medium">{{idx.name}}</span>
<v-tooltip >
<span class="index-name font-light cursor-pointer">
{{s.indexInfo(idx.name)?.numberOfDocuments}}
</span>
<template #popper>
<span class="dark:text-gray-300">
# of Docs
</span>
</template>
</v-tooltip>
</div>
</div>
</div>
</template>

View File

@@ -1,30 +0,0 @@
// register vue composition api globally
import { ViteSSG } from 'vite-ssg'
import generatedRoutes from 'virtual:generated-pages'
import { setupLayouts } from 'virtual:generated-layouts'
import FloatingVue from 'floating-vue'
import 'floating-vue/dist/style.css'
import App from './App.vue'
// windicss layers
import 'virtual:windi-base.css'
import 'virtual:windi-components.css'
// your custom styles here
import './styles/main.css'
// windicss utilities should be the last style import
import 'virtual:windi-utilities.css'
// windicss devtools support (dev only)
import 'virtual:windi-devtools'
const routes = setupLayouts(generatedRoutes)
// https://github.com/antfu/vite-ssg
export const createApp = ViteSSG(
App,
{ routes, base: import.meta.env.BASE_URL },
(ctx) => {
// install all modules under `modules/`
Object.values(import.meta.globEager('./modules/*.ts')).map(i => i.install?.(ctx))
ctx.app.use(FloatingVue)
},
)

View File

@@ -1,13 +0,0 @@
import NProgress from 'nprogress'
import type { UserModule } from '~/types'
export const install: UserModule = ({ isClient, router }) => {
if (isClient) {
router.beforeEach(() => {
NProgress.start()
})
router.afterEach(() => {
NProgress.done()
})
}
}

View File

@@ -1,62 +0,0 @@
# Getting Started
+++ Install Dependencies and Start Docs Site (_looks like you already did_)
```bash
# 1. installs deps for both CLI and Docs
# 2. starts Docs server (in dev mode with HMR), opens in browser
# 3. starts Meilisearch server in Docker
pnpm run start
```
> A browser window should now have opened to [`http://localhost:3333`](http://localhost:3333).
You are now up and running with the documentation site -- and assuming you have Docker installed -- a local [search server](./meilisearch) which you can interact with.
### Already installed Deps?
If you've already installed all the deps and want more granular control you can choose from the various script targets or just choose _watch_ to look at docs with active editing capability:
```bash
# turn on watcher mode for both CLI and Docs
pnpm run watch
```
+++
+++ Ways to Consume this library
- **Search Development** - if you are updating docs, index definitions, etc. you'll run this in _watch_ mode (aka., `pnpm run start` (first time) or `pnpm run watch`)
- **Deployment** - When an _upstream_ dependency is updated this repo should be trigged by a Netlify build hook. For instance:
- `tauri` has a new release to production branch, as a `postbuild` step in Netlify build process, it will call Netlify's API and ask for a rebuild of this repo.
- we care about picking up the two AST files to build the API docs (`ts-docs.json`, `rust.json`)
- `tauri-docs` releases new docs, again a `postbuild` hook on Netlify is called to it requests a rebuild from this repo
- here we need to pickup the directly or MD files
- `NPM Dependency` - the `Models` you've defined along with all of the _types_ defined are available as an NPM dependency
```ts
import { ProseModel } from "tauri-search";
import type { MeiliSearchResponse } from "tauri-search";
```
+++
## Sitemap
Use the Nav bar at the top to navigation to the various sections:
- MeiliSearch Info
- Search Bar Info
- Docker Info
Then we move into the core content types:
- Typescript API Content
- Rust API Content
- Prose Content
## External Resources
- General Documentation
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [MeiliSearch Website Docs](https://docs.meilisearch.com/learn/what_is_meilisearch/)
- API Docs
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [Open API for MeiliSearch](https://bump.sh/doc/meilisearch)
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [API Docs from MeiliSearch Website](https://docs.meilisearch.com/reference/api/)
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [Postman Collection of MeiliSearch API](https://docs.meilisearch.com/postman/meilisearch-collection.json)
- Interactive
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [MeiliSearch Dashboard](http://localhost:7700/)

347
fx
View File

@@ -1,347 +0,0 @@
[
{
id: 'module::app::app.ts',
language: 'typescript',
kind: 'Namespace',
module: 'app',
name: 'app',
fileName: 'app.ts',
comments: 'This package is also accessible with `window.__TAURI__.app` when `tauri.conf.json > build > withGlobalTauri` is set to true.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/app'
},
{
id: 'module::cli::cli.ts',
language: 'typescript',
kind: 'Namespace',
module: 'cli',
name: 'cli',
fileName: 'cli.ts',
comments: 'This package is also accessible with `window.__TAURI__.cli` when `tauri.conf.json > build > withGlobalTauri` is set to true.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/cli'
},
{
id: 'module::clipboard::clipboard.ts',
language: 'typescript',
kind: 'Namespace',
module: 'clipboard',
name: 'clipboard',
fileName: 'clipboard.ts',
comments: 'This package is also accessible with `window.__TAURI__.clipboard` when `tauri.conf.json > build > withGlobalTauri` is set to true.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/clipboard'
},
{
id: 'module::dialog::dialog.ts',
language: 'typescript',
kind: 'Namespace',
module: 'dialog',
name: 'dialog',
fileName: 'dialog.ts',
comments: 'This package is also accessible with `window.__TAURI__.dialog` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "dialog": {\n' +
' "all": true, // enable all dialog APIs\n' +
' "open": true, // enable file open API\n' +
' "save": true // enable file save API\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/dialog'
},
{
id: 'module::event::event.ts',
language: 'typescript',
kind: 'Namespace',
module: 'event',
name: 'event',
fileName: 'event.ts',
comments: 'This package is also accessible with `window.__TAURI__.event` when `tauri.conf.json > build > withGlobalTauri` is set to true.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/event'
},
{
id: 'module::fs::fs.ts',
language: 'typescript',
kind: 'Namespace',
module: 'fs',
name: 'fs',
fileName: 'fs.ts',
comments: 'This package is also accessible with `window.__TAURI__.fs` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "fs": {\n' +
' "all": true, // enable all FS APIs\n' +
' "readTextFile": true,\n' +
' "readBinaryFile": true,\n' +
' "writeFile": true,\n' +
' "writeBinaryFile": true,\n' +
' "readDir": true,\n' +
' "copyFile": true,\n' +
' "createDir": true,\n' +
' "removeDir": true,\n' +
' "removeFile": true,\n' +
' "renameFile": true\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/fs'
},
{
id: 'module::globalShortcut::globalShortcut.ts',
language: 'typescript',
kind: 'Namespace',
module: 'globalShortcut',
name: 'globalShortcut',
fileName: 'globalShortcut.ts',
comments: 'This package is also accessible with `window.__TAURI__.globalShortcut` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "globalShortcut": {\n' +
' "all": true // enable all global shortcut APIs\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/globalShortcut'
},
{
id: 'module::http::http.ts',
language: 'typescript',
kind: 'Namespace',
module: 'http',
name: 'http',
fileName: 'http.ts',
comments: 'This package is also accessible with `window.__TAURI__.http` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "http": {\n' +
' "all": true, // enable all http APIs\n' +
' "request": true // enable HTTP request API\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/http'
},
{
id: 'module::notification::notification.ts',
language: 'typescript',
kind: 'Namespace',
module: 'notification',
name: 'notification',
fileName: 'notification.ts',
comments: 'This package is also accessible with `window.__TAURI__.notification` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "notification": {\n' +
' "all": true // enable all notification APIs\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/notification'
},
{
id: 'module::os::os.ts',
language: 'typescript',
kind: 'Namespace',
module: 'os',
name: 'os',
fileName: 'os.ts',
comments: 'This package is also accessible with `window.__TAURI__.fs` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "os": {\n' +
' "all": true, // enable all Os APIs\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/os'
},
{
id: 'module::path::path.ts',
language: 'typescript',
kind: 'Namespace',
module: 'path',
name: 'path',
fileName: 'path.ts',
comments: undefined,
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/path'
},
{
id: 'module::process::process.ts',
language: 'typescript',
kind: 'Namespace',
module: 'process',
name: 'process',
fileName: 'process.ts',
comments: undefined,
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/process'
},
{
id: 'module::shell::shell.ts',
language: 'typescript',
kind: 'Namespace',
module: 'shell',
name: 'shell',
fileName: 'shell.ts',
comments: undefined,
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/shell'
},
{
id: 'module::tauri::tauri.ts',
language: 'typescript',
kind: 'Namespace',
module: 'tauri',
name: 'tauri',
fileName: 'tauri.ts',
comments: 'This package is also accessible with `window.__TAURI__.tauri` when `tauri.conf.json > build > withGlobalTauri` is set to true.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/tauri'
},
{
id: 'module::updater::updater.ts',
language: 'typescript',
kind: 'Namespace',
module: 'updater',
name: 'updater',
fileName: 'updater.ts',
comments: 'This package is also accessible with `window.__TAURI__.updater` when `tauri.conf.json > build > withGlobalTauri` is set to true.',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/updater'
},
{
id: 'module::window::window.ts',
language: 'typescript',
kind: 'Namespace',
module: 'window',
name: 'window',
fileName: 'window.ts',
comments: 'This package is also accessible with `window.__TAURI__.window` when `tauri.conf.json > build > withGlobalTauri` is set to true.\n' +
'\n' +
'The APIs must be allowlisted on `tauri.conf.json`:\n' +
'```json\n' +
'{\n' +
' "tauri": {\n' +
' "allowlist": {\n' +
' "window": {\n' +
' "all": true, // enable all window APIs\n' +
' "create": true // enable window creation\n' +
' }\n' +
' }\n' +
' }\n' +
'}\n' +
'```\n' +
'It is recommended to allowlist only the APIs you use for optimal bundle size and security.\n' +
'\n' +
'# Window events\n' +
'\n' +
'Events can be listened using `appWindow.listen`:\n' +
'```typescript\n' +
"import { appWindow } from '@tauri-apps/api/window'\n" +
"appWindow.listen('tauri://move', ({ event, payload }) => {\n" +
' const { x, y } = payload // payload here is a `PhysicalPosition`\n' +
'})\n' +
'```\n' +
'\n' +
'Window-specific events emitted by the backend:\n' +
'\n' +
"#### 'tauri://resize'\n" +
'Emitted when the size of the window has changed.\n' +
'*EventPayload*:\n' +
'```typescript\n' +
'type ResizePayload = PhysicalSize\n' +
'```\n' +
'\n' +
"#### 'tauri://move'\n" +
'Emitted when the position of the window has changed.\n' +
'*EventPayload*:\n' +
'```typescript\n' +
'type MovePayload = PhysicalPosition\n' +
'```\n' +
'\n' +
"#### 'tauri://close-requested'\n" +
'Emitted when the user requests the window to be closed.\n' +
"If a listener is registered for this event, Tauri won't close the window so you must call `appWindow.close()` manually.\n" +
'\n' +
"#### 'tauri://focus'\n" +
'Emitted when the window gains focus.\n' +
'\n' +
"#### 'tauri://blur'\n" +
'Emitted when the window loses focus.\n' +
'\n' +
"#### 'tauri://scale-change'\n" +
"Emitted when the window's scale factor has changed.\n" +
'The following user actions can cause DPI changes:\n' +
"- Changing the display's resolution.\n" +
"- Changing the display's scale factor (e.g. in Control Panel on Windows).\n" +
'- Moving the window to a display with a different scale factor.\n' +
'*Event payload*:\n' +
'```typescript\n' +
'interface ScaleFactorChanged {\n' +
' scaleFactor: number\n' +
' size: PhysicalSize\n' +
'}\n' +
'```\n' +
'\n' +
"#### 'tauri://menu'\n" +
'Emitted when a menu item is clicked.\n' +
'*EventPayload*:\n' +
'```typescript\n' +
'type MenuClicked = string\n' +
'```\n',
tags: undefined,
url: 'https://tauri.studio/docs/api/js/modules/window'
}
]

View File

@@ -1,20 +0,0 @@
/* eslint-disable unicorn/import-style */
import type { Config } from "@jest/types";
import { resolve } from "path";
const config: Config.InitialOptions = {
verbose: true,
// roots: ["tests", "src"],
transform: {
"^.+\\.ts$": "ts-jest",
},
testPathIgnorePatterns: ["/node_modules/", "/docs/"],
moduleNameMapper: {
"^[/]{0,1}~/(.*)$": resolve(process.cwd(), "src", "$1"),
},
testMatch: ["**/?(*[-.])+(spec|test).ts"],
setupFilesAfterEnv: ["jest-extended"],
testEnvironment: "node",
};
export default config;

View File

@@ -1,86 +1,49 @@
{
"name": "tauri-search",
"version": "0.1.0",
"description": "Search Engine for Tauri website",
"name": "tauri-search-monorepo",
"private": true,
"license": "MIT",
"author": "Ken Snyder<ken@ken.net>",
"bin": {
"ts-ast": "./bin/ts-ast.cjs",
"ts-ast-overview": "./bin/ts-ast-overview.cjs"
},
"scripts": {
"dev": "pnpm -C docs dev",
"clean": "pnpm run --filter ./packages run clean",
"start": "pnpm -r install && pnpm run start:tauri-search && pnpm run start:docs && pnpm run up",
"start:tauri-search": "pnpm -C ./packages/tauri-search run watch",
"start:docs": "pnpm -C ./packages/docs run watch",
"build:cli": "pnpm -C ./packages/tauri-search run build:cli",
"build:npm": "pnpm -C ./packages/tauri-search run build:npm",
"watch": "run-s watch:prep watch:rest",
"watch:prep": "pnpm -C ./packages/tauri-search run build:npm",
"watch:rest": "run-p watch:tauri-search watch:docs",
"watch:tauri-search": "pnpm -C ./packages/tauri-search run watch",
"watch:docs": "pnpm -C ./packages/docs run watch",
"test": "pnpm -C ./packages/docs run test && pnpm -C ./packages/tauri-search run test",
"test:watch": "pnpm -C ./packages/tauri-search run test:watch",
"current-indexes": "pnpm -C ./packages/tauri-search run current-indexes",
"create-indexes": "pnpm -C ./packages/tauri-search run create-indexes",
"refresh-prose": "pnpm -C ./packages/tauri-search run refresh-prose",
"push-prose": "pnpm -C ./packages/tauri-search run push-prose",
"up": "docker compose up -d",
"down": "docker compose down",
"into:scraper": "docker exec -it scraper bash",
"into:search": "docker exec -it search bash",
"lint": "eslint src --ext ts,js,tsx,jsx --fix --no-error-on-unmatched-pattern",
"ping": "http GET localhost:7700/health",
"prep": "run-p start:*",
"lint": "pnpm -r eslint src --ext ts,js,tsx,jsx --fix --no-error-on-unmatched-pattern",
"lint:docs": "pnpm -C docs run lint",
"ping": "npx http GET localhost:7700/health --timeout 2",
"prune": "docker system prune",
"restart": "docker compose restart",
"start": "run-s prep watch",
"start:cli": "pnpm install",
"start:docker": "run-s up",
"start:docs": "pnpm -C docs start",
"test": "echo \"Error: no test specified\" && exit 1",
"ts-ast": "node ./bin/ts-ast.js",
"ts-ast-overview": "node ./bin/ts-ast-overview.js",
"up": "docker compose up -d",
"vol:create": "run-s vol:create:*",
"vol:create:scraper": "docker volume create scraper",
"vol:create:search": "docker volume create search_db",
"vol:inspect": "run-s vol:inspect:*",
"vol:inspect:scraper": "docker volume inspect scraper",
"vol:inspect:search": "docker volume inspect search_db",
"watch": "run-p watch:*",
"watch:cli": "tsup src/cli/*.ts --format=esm,cjs --clean --sourcemap -d bin --watch",
"watch:npm": "tsup src/models/*.ts src/type/*.ts --format=esm,cjs --clean --sourcemap -d dist --dts --watch",
"watch:docs": "pnpm -C docs dev"
},
"dependencies": {
"cheerio": "^1.0.0-rc.10",
"gray-matter": "^4.0.3",
"inferred-types": "^0.18.4",
"native-dash": "^1.21.5",
"node-fetch": "^3.2.0",
"simple-markdown": "^0.7.3",
"ts-morph": "^13.0.3",
"xxhash-wasm": "^1.0.1"
"docs": "pnpm -C ./packages/docs run dev",
"npm": "pnpm -C ./packages/tauri-search run watch:npm",
"npm:test": "pnpm -C ./packages/tauri-search run test"
},
"devDependencies": {
"@jest/types": "^27.4.2",
"@octokit/types": "^6.34.0",
"@types/jest": "^27.4.0",
"@types/markdown-it": "^12.2.3",
"@types/node": "^14.18.9",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"changeset": "^0.2.6",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^25.7.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^6.0.0",
"fx": "^20.0.2",
"husky": "^7.0.4",
"jest": "^27.4.7",
"jest-extended": "^1.2.0",
"markdown-it": "^12.3.2",
"markdown-it-anchor": "^8.4.1",
"markdown-it-attrs": "^4.1.3",
"markdown-it-video": "^0.6.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-node": "^10.4.0",
"tsup": "^5.11.11",
"typescript": "^4.6.0-dev.20220124"
},
"engines": {
"node": ">=14",
"pnpm": ">=3"
"npm-run-all": "^4.1.5"
}
}

3
packages/docs/.eslintrc Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc"
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -16,3 +16,8 @@ home:
search: Suche
desc: Spielplatz für die Tauri-Suche
whats-your-name: Suchen Sie nach etwas
ask-for-search: Suchen Sie nach etwas
nav:
indexing: Indizierung
toggle_langs: Sprachen ändern
toggle_dark: Dunkelmodus umschalten

View File

@@ -15,3 +15,9 @@ not-found: No se ha encontrado
home:
search: Búsqueda
whats-your-name: buscar algo
ask-for-search: buscar algo
desc: Documentación y Patio Interactivo
nav:
indexing: Indexación
toggle_dark: Alternar modo oscuro
toggle_langs: Cambiar idiomas

View File

@@ -16,3 +16,8 @@ home:
desc: Aire de jeux pour Tauri Search
search: Chercher
whats-your-name: Rechercher quelque chose
ask-for-search: Rechercher quelque chose
nav:
indexing: Indexage
toggle_dark: Basculer en mode sombre
toggle_langs: Changer de langue

View File

@@ -1,40 +1,48 @@
{
"private": true,
"scripts": {
"start": "pnpm install",
"clean": "rimraf dist/*",
"start": "pnpm run dev",
"build": "vite-ssg build",
"dev": "vite --port 3333 --open",
"lint": "eslint \"**/*.{vue,ts,js}\"",
"lint": "run-s lint:*",
"lint:eslint": "eslint \"**/*.{vue,ts,js}\"",
"lint:tsc": "vue-tsc --noEmit",
"preview": "vite preview",
"preview-https": "serve dist",
"test": "vitest",
"test": "vitest run",
"test:e2e": "cypress open",
"test:unit": "vitest",
"typecheck": "vue-tsc --noEmit"
"test:dev": "vitest dev",
"test:watch": "vitest watch",
"watch": "pnpm run dev"
},
"dependencies": {
"@vueuse/core": "^7.5.4",
"@vueuse/core": "^7.5.5",
"@vueuse/head": "^0.7.5",
"date-fns": "^2.28.0",
"floating-vue": "^2.0.0-beta.3",
"nprogress": "^0.2.0",
"pinia": "^2.0.9",
"pinia": "^2.0.11",
"prism-theme-vars": "^0.2.2",
"tauri-search": "workspace:*",
"ts-morph": "^13.0.3",
"vue": "^3.2.28",
"vue": "^3.2.29",
"vue-demi": "^0.12.1",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@antfu/eslint-config": "^0.16.0",
"@antfu/eslint-config": "^0.16.1",
"@iconify-json/ant-design": "^1.0.2",
"@iconify-json/bx": "^1.0.3",
"@iconify-json/carbon": "^1.0.14",
"@iconify-json/cib": "^1.0.1",
"@iconify-json/fluent": "^1.0.15",
"@iconify-json/fluent": "^1.0.16",
"@iconify-json/ic": "^1.0.8",
"@iconify-json/mdi": "^1.0.12",
"@iconify-json/iconoir": "^1.0.5",
"@iconify-json/mdi": "^1.0.15",
"@iconify-json/octicon": "^1.0.9",
"@iconify-json/ph": "^1.0.4",
"@iconify-json/tabler": "^1.0.12",
"@iconify-json/teenyicons": "^1.0.1",
@@ -43,32 +51,32 @@
"@types/markdown-it-link-attributes": "^3.0.1",
"@types/nprogress": "^0.2.0",
"@vitejs/plugin-vue": "^2.1.0",
"@vue/compiler-sfc": "^3.2.28",
"@vue/server-renderer": "^3.2.28",
"@vue/compiler-sfc": "^3.2.29",
"@vue/server-renderer": "^3.2.29",
"@vue/test-utils": "^2.0.0-rc.18",
"critters": "^0.0.16",
"cross-env": "^7.0.3",
"cypress": "^9.3.1",
"eslint": "^8.7.0",
"eslint": "^8.8.0",
"eslint-plugin-cypress": "^2.12.1",
"https-localhost": "^4.7.0",
"jsdom": "^19.0.0",
"markdown-it-collapsible": "^1.0.0",
"markdown-it-link-attributes": "^4.0.0",
"markdown-it-prism": "^2.2.2",
"typescript": "^4.5.5",
"unplugin-auto-import": "^0.5.11",
"unplugin-icons": "^0.13.0",
"unplugin-vue-components": "^0.17.14",
"unplugin-vue-components": "^0.17.15",
"vite": "^2.7.13",
"vite-plugin-inspect": "^0.3.13",
"vite-plugin-md": "^0.11.7",
"vite-plugin-pages": "^0.20.0",
"vite-plugin-pages": "^0.20.1",
"vite-plugin-pwa": "^0.11.13",
"vite-plugin-vue-layouts": "^0.5.0",
"vite-plugin-windicss": "^1.6.3",
"vite-ssg": "^0.17.6",
"vite-ssg": "^0.17.9",
"vitepress": "^0.21.6",
"vitest": "^0.1.27",
"vitest": "^0.2.5",
"vue-tsc": "^0.31.1"
}
}

View File

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -183,6 +183,7 @@ declare global {
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']

View File

@@ -4,26 +4,40 @@
declare module 'vue' {
export interface GlobalComponents {
'AntDesign:fileMarkdownOutlined': typeof import('~icons/ant-design/file-markdown-outlined')['default']
'Bx:bxCheckbox': typeof import('~icons/bx/bx-checkbox')['default']
'Bx:bxCheckboxChecked': typeof import('~icons/bx/bx-checkbox-checked')['default']
'Bx:bxCheckboxMinus': typeof import('~icons/bx/bx-checkbox-minus')['default']
'Bx:bxSearchAlt': typeof import('~icons/bx/bx-search-alt')['default']
'Carbon:document': typeof import('~icons/carbon/document')['default']
'Carbon:listDropdown': typeof import('~icons/carbon/list-dropdown')['default']
CarbonLanguage: typeof import('~icons/carbon/language')['default']
CarbonMoon: typeof import('~icons/carbon/moon')['default']
CarbonSun: typeof import('~icons/carbon/sun')['default']
Checkbox: typeof import('./components/Checkbox.vue')['default']
Counter: typeof import('./components/Counter.vue')['default']
CurrentIndex: typeof import('./components/CurrentIndex.vue')['default']
'Fluent:databaseSearch24Regular': typeof import('~icons/fluent/database-search24-regular')['default']
Footer: typeof import('./components/Footer.vue')['default']
'Iconoir:download': typeof import('~icons/iconoir/download')['default']
'Mdi:folderHome': typeof import('~icons/mdi/folder-home')['default']
'Mdi:github': typeof import('~icons/mdi/github')['default']
'Mdi:languageRust': typeof import('~icons/mdi/language-rust')['default']
'Mdi:languageTypescript': typeof import('~icons/mdi/language-typescript')['default']
MissingIndex: typeof import('./components/MissingIndex.vue')['default']
'Octicon:trash16': typeof import('~icons/octicon/trash16')['default']
'Ph:info': typeof import('~icons/ph/info')['default']
'Ph:linkLight': typeof import('~icons/ph/link-light')['default']
README: typeof import('./components/README.md')['default']
SearchActions: typeof import('./components/SearchActions.vue')['default']
SearchHit: typeof import('./components/SearchHit.vue')['default']
SearchIndexes: typeof import('./components/SearchIndexes.vue')['default']
SearchResults: typeof import('./components/SearchResults.vue')['default']
SearchStats: typeof import('./components/SearchStats.vue')['default']
SimpleCard: typeof import('./components/SimpleCard.vue')['default']
'Tabler:databaseImport': typeof import('~icons/tabler/database-import')['default']
'Teenyicons:dockerOutline': typeof import('~icons/teenyicons/docker-outline')['default']
'Teenyicons:textDocumentSolid': typeof import('~icons/teenyicons/text-document-solid')['default']
'VscodeIcons:fileTypeRust': typeof import('~icons/vscode-icons/file-type-rust')['default']
'VscodeIcons:fileTypeTypescriptOfficial': typeof import('~icons/vscode-icons/file-type-typescript-official')['default']
}
}

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">import { PropType } from "vue";
const props = defineProps({
value: {type: Boolean as PropType<boolean | undefined>, default: undefined, required: true}
});
const toggle = () => {
};
</script>
<template>
<div class="checkbox text-xl mr-1">
<bx:bx-checkbox-minus v-if="props.value === undefined" class="flex cursor-default text-gray-500/50" />
<bx:bx-checkbox v-if="props.value=== false" class="flex cursor-pointer text-gray-500/75" @click="toggle" />
<bx:bx-checkbox-checked v-if="props.value===true" class="flex cursor-pointer text-gray-800/95 dark:text-gray-100/90" @click="toggle" />
</div>
</template>

View File

@@ -0,0 +1,112 @@
<script setup lang="ts">
/* eslint-disable no-console */
import { useSearch } from "~/modules/search";
import { format } from "date-fns";
import { PropType } from "vue";
import { MeiliSearchInterface } from "~/types/meilisearch";
import { ApiModel } from "tauri-search";
const s = useSearch();
const props = defineProps({
idx: {type: Object as PropType<MeiliSearchInterface>, required: true},
unknown: {type: Boolean, default: false, required: false}
});
const indexName = computed(() => props.idx.name as "api" | "repo" | "prose");
const remove = async () => {
// just using ApiModel at random, as we can state which index to remove
await ApiModel.query.deleteIndex(indexName.value);
};
const pushCache = async() => {
// console.group(`downloading cached docs for "${indexName.value}"`);
// const docs = await getCache(indexName.value);
// console.info(`${docs.length} docs downloaded, now calling Meilisearch API to add each doc`);
// const tasks = await pushDocs(docs);
// console.info(`all documents have been sent to the MeiliSearch API where they will be processed in a task queu`);
// console.log(`The tasks in queue are:`, tasks);
// console.groupEnd();
};
const usedInSearch = computed(() => s.indexIsUsed(indexName.value));
// async function pushCache() {
// const docs = await
// }
</script>
<template>
<div
class="index flex flex-row w-full py-2 px-3 border-1 rounded border-gray-500 mt-4 cursor-default items-center justify-center"
:class="props.unknown ? 'unknown' : ''"
>
<checkbox :value="usedInSearch" @click="s.toggleUseOfIndex(indexName)" />
<span class="index-name flex flex-grow font-medium">{{indexName}}</span>
<div class="flex flex-row items-center">
<!-- Right Content -->
<v-tooltip v-if="s.indexInfo(indexName)?.numberOfDocuments === 0 && !props.unknown" class="cursor-pointer text-sm flex flex-grow-0 mr-3 text-gray-500 hover:text-green-800 dark:hover:text-green-500">
<iconoir:download class="" @click="pushCache" />
<template #popper>Push documents in cache into<br/>local MeiliSearch server</template>
</v-tooltip>
<v-tooltip>
<octicon:trash-16
class="cursor-pointer text-sm flex flex-grow-0 mr-3 text-gray-500 hover:text-red-800 dark:hover:text-red-500"
@click="remove"
/>
<template #popper>remove "{{indexName}}" index<span v-if="s.indexInfo(indexName)?.numberOfDocuments || 0 > 0">; including all documents.</span></template>
</v-tooltip>
<span class="index-count flex flex-grow-0 font-light items-baseline ">
<span class="flex">{{s.indexInfo(indexName)?.numberOfDocuments}}</span>
&nbsp;
<span class="flex italic font-light text-xs">docs</span>
</span>
<v-tooltip placement="right" >
<ph:info
class=" flex flex-grow-0 ml-2 dark:text-gray-500/75 hover:text-opacity-100 "
/>
<template #popper>
<div class="w-56 text-sm">
<div class="font-bold text-lg">
{{indexName}}
&nbsp;
<span class="font-light">index</span>
</div>
<table width="100%">
<tr>
<td class="font-normal">created:</td>
<td class="font-light" align="right">{{format(new Date(props.idx.createdAt), "do MMM, yyyy")}}</td>
</tr>
<tr>
<td class="font-normal">updated:</td>
<td class="font-light justify-end" align="right" >{{format(new Date(props.idx.updatedAt), "do MMM, yyyy")}}</td>
</tr>
<tr>
<td>stop words</td>
<td>[]</td>
</tr>
<tr v-if="s.indexInfo(indexName)?.numberOfDocuments || 0 > 0" class="text-xs">
<td>Prop<br/>Distribution</td>
<td>
<table width="100%">
<tr v-for="k in Object.keys(s.$state.stats?.indexes[indexName].fieldDistribution|| {}) " :key="k">
<td>{{ k }}</td>
<td>{{ s.$state.stats?.indexes[indexName].fieldDistribution[k]}}</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</template>
</v-tooltip>
</div>
</div>
</template>
<style lang="css" scoped>
.unknown {
@apply border-orange-500/50
}
</style>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
import { ApiModel, ConsolidatedModel, ProseModel, RepoModel } from "tauri-search";
import { PropType } from "vue";
import { useSearch } from "~/modules/search";
const s = useSearch();
const props = defineProps({
idx: {type: String as PropType<"api" | "prose" | "repo" | "consolidated">, required: true}
});
const models = {
api: ApiModel,
prose: ProseModel,
repo: RepoModel,
consolidated: ConsolidatedModel
};
async function addIndex() {
const model = models[props.idx];
await model.query.createIndex();
}
</script>
<template>
<v-tooltip class="w-full" placement="right" :delay="8000">
<div
class="index flex flex-row w-full py-2 px-3 border-1 rounded border-dashed border-gray-300 dark:border-gray-700 mt-4 cursor-default"
>
<span class="index-name flex flex-grow font-medium">{{idx}}</span>
<span class="index-count flex flex-grow-0 font-light cursor-pointer items-baseline">
<v-tooltip :delay="750" placement="right">
<button class="text-xs italic px-2 py-1 bg-gray-300/75 hover:bg-green-300/75 dark:hover:bg-green-700/75 dark:bg-gray-700/75 rounded-md" @click="addIndex">add index</button>
<template #popper>will create an empty index for "{{idx}}"</template>
</v-tooltip>
</span>
</div>
<template #popper>
The <span class="tag">{{idx}}</span> index is defined as a MODEL but doesn't yet exist in MeiliSearch as an index.
</template>
</v-tooltip>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { PropType } from "vue";
import { GenericDoc } from "~/types/meilisearch";
const props = defineProps({
document: {
type: Object as PropType<GenericDoc>, required: true}
});
const doc = computed(() => props.document);
const showDetails = ref(false);
const apiKind = computed(() => {
if(doc.value._idx !== "api") {
return "";
}
switch(doc.value.kind) {
case "Function":
return "fn";
case "Namespace":
return "module";
case "Interface":
return "interface";
default:
return doc.value?.kind ? String(doc.value?.kind).toLowerCase() : "";
}
});
const details = () => {
showDetails.value = !showDetails.value;
};
</script>
<template>
<div class="flex flex-col border-1 rounded px-2 py-1 border-gray-500 hover:bg-gray-100/50 dark:hover:bg-gray-700/50 ">
<div class="flex flex-row space-x-1 items-center place-content-center cursor-pointer" @click="details">
<div v-if="doc._idx === 'repo'" class="flex flex-row flex-grow space-x-1 items-center place-items-center">
<mdi:github class="flex flex-shrink-0" />
<div class="name font-semibold flex-shrink-0 pr-2">{{doc.name}}</div>
<div class="description flex flex-grow font-light text-sm truncate text-gray-500">{{doc.description}}</div>
</div>
<div v-if="doc._idx === 'api'" class="flex flex-row flex-grow space-x-2 place-items-center items-center">
<vscode-icons:file-type-typescript-official v-if="doc.language === 'typescript'" class="flex flex-shrink-0" />
<vscode-icons:file-type-rust v-if="doc.language === 'rust'" class="flex" />
<span class="flex">{{ doc.name }}</span>
</div>
<div v-if="doc._idx === 'prose'" class="flex flex-row flex-grow space-x-2 place-items-center items-center">
<ant-design:file-markdown-outlined class="flex" />
<div class="name">{{doc.title}}</div>
</div>
<div v-if="doc._idx === 'consolidated'" class="w-full">
<div v-if="doc.from === 'api'" class="flex flex-row flex-grow space-x-2 place-items-center items-center">
<vscode-icons:file-type-rust v-if="doc.language === 'rust'" class="flex" />
<vscode-icons:file-type-typescript-official v-else class="flex" />
<div class="symbolName font-semibold">{{doc.lvl0}}</div>
<div class="text-sm font-light">{{doc.symbol}}</div>
</div>
<!-- PROSE -->
<div v-if="doc.from === 'prose'" class="flex flex-row flex-grow space-x-2 place-items-center items-center">
<teenyicons:text-document-solid class="flex" />
<div class="title font-semibold flex-shrink-0">{{doc.lvl0}}</div>
<div class="title font-light truncate text-gray-500">{{doc.lvl1}}</div>
</div>
<!-- REPOs -->
<div v-if="doc.from === 'repo'" class="flex flex-row flex-grow space-x-2 place-items-center items-center">
<mdi:github class="flex flex-shrink-0" />
<div class="name font-semibold flex-shrink-0">{{doc.lvl0}}</div>
<div class="description truncate flex-shrink text-gray-500 font-light">{{doc.lvl1}}</div>
</div>
</div>
<div v-if="doc._idx === 'api'" class="flex text-xs font-medium px-1 py-0.5 bg-blue-500 dark:bg-blue-600 text-gray-50 rounded">
{{apiKind}}
</div>
<div class="flex px-1 hover:text-green-600 hover:font-bold ">
<a :href="doc.url" target="_new">
<ph:link-light class="flex" />
</a>
</div>
</div>
<div v-if="showDetails" class="items-start">
<div v-for="key in Object.keys(doc)" :key="key" class="flex flex-row">
<span class="flex font-bold">{{key}}:</span>
<span class="flex ml-1">{{doc[key]}}</span>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { useSearch } from "~/modules/search";
const search = useSearch();
/**
* this allows all expected indexes to be shown and in a sensible order
*/
const knownIndexes = ["api", "prose", "repo", "consolidated"];
/** the name of the indexes current known by MeiliSearch */
const currentIndexes = computed(() => search.$state.indexes.map(i => i.name));
/**
* we do want to see ANY index that exists though too
*/
const unknownIndexes = computed(() => {
return search.$state.indexes.filter(i => !knownIndexes.includes(i.name));
});
</script>
<template>
<div class="search-indexes flex flex-col flex-grow rounded-md bg-gray-100/25 dark:bg-gray-800/25">
<div class="rounded-t-md overflow-hidden">
<div class="font-bold text-lg rounded-t-md bg-gray-900/10 dark:bg-gray-100/10 py-3 px-4">
INDEXES
</div>
</div>
<div v-if="search.$state.health" class="text-xs mt-2" >
<div class="text-green-500">MeiliSearch Service Healthy!</div>
<div class="db-size">db size: {{search.dbSize}}</div>
</div>
<span v-else class="text-red-500 ">
Meilisearch is not available locally!
</span>
<div class="index-list p-4 w-full flex flex-col ">
<template v-for="idx in knownIndexes" :key="idx">
<current-index v-if="currentIndexes.includes(idx)" :idx="search.$state.indexes.find(i => i.name === idx)"/>
<missing-index v-else :idx="idx" />
</template>
<template v-for="idx in unknownIndexes" :key="idx">
<current-index :idx="idx" :unknown="true" />
</template>
</div>
</div>
</template>

View File

@@ -1,17 +1,25 @@
<script setup lang="ts">
import { useSearch } from "~/modules/search";
const props = defineProps({
query: {type: String, default: ""}
});
import { useSearch } from "~/modules/search";
const s = useSearch();
</script>
<template>
<div class="search-stats flex flex-col rounded-md h-full">
<div class="search-stats flex flex-col rounded-md h-full min-h-128">
<div class="rounded-t-md overflow-hidden">
<div class="font-bold text-lg rounded-t-md bg-gray-900/10 dark:bg-gray-100/10 py-3 px-4">
Results
<div class="grid grid-cols-3 rounded-t-md bg-gray-900/10 dark:bg-gray-100/10 py-3 px-4 ">
<div class="block text-sm font-light place-self-start self-center">
{{s.$state.searchUsing.length}} <span class="italic">index(s) searched</span>
</div>
<div class="block font-bold text-lg place-self-center self-center">
Results
</div>
<div class="block text-sm font-light place-self-end self-center">
{{s.$state.searchResults.length}} <span class="italic">docs found</span>
</div>
</div>
</div>

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
const { t } = useI18n()
const { t } = useI18n();
</script>
<template>
<main class="text-center text-gray-700 dark:text-gray-200 flex items-center justify-center flex-col ">
<main class="text-center text-gray-700 dark:text-gray-200 flex flex-col items-center justify-center flex-col min-h-full ">
<!-- HEADER -->
<div class="flex flex-col justify-center bg-blue-900 text-gray-100 dark:bg-blue-800/50 w-full space-y-4 align-middle items-center justify-center py-2">
<div class="flex flex-col ">
@@ -18,8 +19,8 @@ const { t } = useI18n()
<Footer />
</div>
<!-- MAIN CONTENt -->
<div class="flex max-w-4xl h-auto min-h-full dark:bg-black/25 p-4 rounded-lg w-4xl">
<!-- MAIN CONTENT -->
<div class="flex flex-grow max-w-6xl h-auto min-h-full dark:bg-black/25 p-4 rounded-lg w-6xl">
<router-view />
</div>
</main>

33
packages/docs/src/main.ts Normal file
View File

@@ -0,0 +1,33 @@
// register vue composition api globally
import { ViteSSG } from "vite-ssg";
import generatedRoutes from "virtual:generated-pages";
import { setupLayouts } from "virtual:generated-layouts";
import FloatingVue from "floating-vue";
import "floating-vue/dist/style.css";
import App from "./App.vue";
// windicss layers
import "virtual:windi-base.css";
import "virtual:windi-components.css";
// your custom styles here
import "./styles/main.css";
// windicss utilities should be the last style import
import "virtual:windi-utilities.css";
// windicss devtools support (dev only)
import "virtual:windi-devtools";
const routes = setupLayouts(generatedRoutes);
// https://github.com/antfu/vite-ssg
export const createApp = ViteSSG(
App,
{ routes, base: import.meta.env.BASE_URL },
(ctx) => {
// install all modules under `modules/`
Object.values(import.meta.globEager("./modules/*.ts")).map(i => i.install?.(ctx));
ctx.app.use(FloatingVue);
},
);

View File

@@ -1,23 +1,23 @@
import { createI18n } from 'vue-i18n'
import type { UserModule } from '~/types'
import { createI18n } from "vue-i18n";
import type { UserModule } from "~/types";
// Import i18n resources
// https://vitejs.dev/guide/features.html#glob-import
//
// Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite
const messages = Object.fromEntries(
Object.entries(import.meta.globEager('../../locales/*.y(a)?ml')).map(([key, value]) => {
const yaml = key.endsWith('.yaml')
return [key.slice(14, yaml ? -5 : -4), value.default]
Object.entries(import.meta.globEager("../../locales/*.y(a)?ml")).map(([key, value]) => {
const yaml = key.endsWith(".yaml");
return [key.slice(14, yaml ? -5 : -4), value.default];
}),
)
);
export const install: UserModule = ({ app }) => {
const i18n = createI18n({
legacy: false,
locale: 'en',
locale: "en",
messages,
})
});
app.use(i18n)
}
app.use(i18n);
};

View File

@@ -0,0 +1,13 @@
import NProgress from "nprogress";
import type { UserModule } from "~/types";
export const install: UserModule = ({ isClient, router }) => {
if (isClient) {
router.beforeEach(() => {
NProgress.start();
});
router.afterEach(() => {
NProgress.done();
});
}
};

View File

@@ -1,14 +1,14 @@
import { createPinia } from 'pinia'
import type { UserModule } from '~/types'
import { createPinia } from "pinia";
import type { UserModule } from "~/types";
// Setup Pinia
// https://pinia.esm.dev/
export const install: UserModule = ({ isClient, initialState, app }) => {
const pinia = createPinia()
app.use(pinia)
const pinia = createPinia();
app.use(pinia);
// Refer to
// https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization
// for other serialization strategies.
if (isClient) pinia.state.value = initialState.pinia || {}
else initialState.pinia = pinia.state.value
}
if (isClient) pinia.state.value = initialState.pinia || {};
else initialState.pinia = pinia.state.value;
};

View File

@@ -1,11 +1,11 @@
import type { UserModule } from '~/types'
import type { UserModule } from "~/types";
// https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
export const install: UserModule = ({ isClient, router }) => {
if (!isClient) return
if (!isClient) return;
router.isReady().then(async() => {
const { registerSW } = await import('virtual:pwa-register')
registerSW({ immediate: true })
})
}
const { registerSW } = await import("virtual:pwa-register");
registerSW({ immediate: true });
});
};

View File

@@ -3,6 +3,8 @@
import { acceptHMRUpdate, defineStore } from "pinia";
import type { UserModule } from "~/types";
import { MeiliSearchHealth, MeiliSearchIndex, MeiliSearchInterface, MeiliSearchResponse, MeiliSearchStats } from "~/types/meilisearch";
import { IMeilisearchIndexSettings } from "tauri-search";
//#region STORE
export interface SearchState {
@@ -12,9 +14,15 @@ export interface SearchState {
/** the indexes which are defined on MeiliSearch */
indexes: MeiliSearchInterface[];
/** indexes to use when searching */
searchUsing: string[];
/** database stats for MeiliSearch */
stats?: MeiliSearchStats;
/** index settings */
indexSettings: Record<string, IMeilisearchIndexSettings<any>>;
searchStatus: "ready" | "searching" | "error" | "not-ready";
searchResults: {id: string; _idx: string; [key: string]: unknown}[];
@@ -24,19 +32,19 @@ export const useSearch = defineStore("search", ({
state: () => ({
health: "initializing",
indexes: [],
indexSettings: {},
searchUsing: ["consolidated"],
stats: undefined,
searchStatus: "not-ready",
searchResults: [],
}) as SearchState,
actions: {
async search(text: string, indexes?: string[]) {
async search(text: string) {
if(text.trim()==="") {
this.$state.searchResults = [];
return;
}
if(!indexes) {
indexes = this.$state.indexes.map(i => i.uid);
}
const indexes = this.$state.searchUsing;
console.group(`searching for: "${text}"`);
console.info(`search on ${indexes.length} indexes`, indexes);
console.time("search");
@@ -71,6 +79,13 @@ export const useSearch = defineStore("search", ({
return results;
},
toggleUseOfIndex(idx: string) {
if(this.$state.searchUsing.includes(idx)) {
this.$state.searchUsing = this.$state.searchUsing.filter(i => i !== idx);
} else {
this.$state.searchUsing = [...this.$state.searchUsing, idx];
}
},
statsUpdate(s: MeiliSearchStats) {
this.$state.stats = s;
},
@@ -78,10 +93,17 @@ export const useSearch = defineStore("search", ({
this.$state.health = h;
},
indexUpdate(idx: MeiliSearchInterface[]) {
const current = this.$state.indexes.map(i => i.name);
const newList = idx.map(i => i.name);
if(current.length === newList.length && newList.every(i => current.includes(i))) {
return;
}
this.$state.indexes = idx;
}
},
getters: {
indexIsUsed: (state) => (idx: string) => state.searchUsing.includes(idx),
dbSize: (state) => state.stats?.databaseSize || 0,
indexInfo: (state) => (idx: string): MeiliSearchIndex | undefined => state.stats?.indexes[idx]
}
@@ -118,8 +140,11 @@ export const install: UserModule = ({ isClient }) => {
s.healthUpdate(h);
s.$state.searchStatus = !h ? "not-ready": "ready";
if(h === true && !isActive) resume();
if(h === false && isActive) pause();
if(h === true && !isActive.value) {
createIndexes();
resume();
};
if(h === false && isActive.value) pause();
}, 1000, {immediate: true});
}
@@ -157,10 +182,14 @@ function api(base: string = "http://localhost:7700") {
}
async function health(): Promise<boolean> {
const response = await (
await fetch(api().health)
).json() as MeiliSearchHealth;
return response.status === "available";
try {
const response = await (
await fetch(api().health)
).json() as MeiliSearchHealth;
return response.status === "available";
} catch {
return false;
}
}
async function indexes() {

View File

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -0,0 +1,103 @@
# Getting Started
+++ Install Dependencies and Start Docs Site (_looks like you already did_)
```bash
# 1. installs deps for both CLI and Docs
# 2. starts Docs server (in dev mode with HMR), opens in browser
# 3. starts Meilisearch server in Docker
pnpm run start
```
> A browser window should now have opened to [`http://localhost:3333`](http://localhost:3333).
You are now up and running with the documentation site -- and assuming you have Docker installed -- a local [search server](./meilisearch) which you can interact with.
### Already installed Deps?
If you've already installed all the deps and want more granular control you can choose from the various script targets or just choose _watch_ to look at docs with active editing capability:
```bash
# turn on watcher mode for both CLI and Docs
pnpm run watch
```
+++
+++ Ways to Consume this library
- **Search Development** - if you are updating docs, index definitions, etc. you'll run this in _watch_ mode (aka., `pnpm run start` (first time) or `pnpm run watch`)
- **Deployment** - When an _upstream_ dependency is updated this repo should be trigged by a Netlify build hook. For instance:
- `tauri` has a new release to production branch, as a `postbuild` step in Netlify build process, it will call Netlify's API and ask for a rebuild of this repo.
- we care about picking up the two AST files to build the API docs (`ts-docs.json`, `rust.json`)
- `tauri-docs` releases new docs, again a `postbuild` hook on Netlify is called to it requests a rebuild from this repo
- here we need to pickup the directly or MD files
- **NPM Dependency** - the `Models` you've defined along with all of the _types_ defined are available as an NPM dependency
```ts
import { ProseModel } from "tauri-search";
import type { MeiliSearchResponse } from "tauri-search";
```
+++
## Models
Central to using this library to build and refresh your search indexes is understanding the concept of `Model`.
- A Model has a `1:1` relationship with search indexes (or at least _potential_ indexes)
- A Model is intended to represent:
- the **document structure** that will be used for docs in the index
- allows for **configuring the index** itself (e.g., stop words, synonyms, etc.)
- allows you to embed data mappers which map from one document structure to another
+++ Take a look at the examples here to get a better bearing:
- +++ define the model:
```ts
/** structure for documents in the Prose index */
export interface IProse {
title: string; section: string;
lastUpdated: number; url: string;
}
/** structure for the input data structure */
export interface IMarkdownAst {
h1: string; h2: string; h3: string;
url: string;
}
/** create the Prose model */
const Prose = createModel("prose", c => c //
.synonomys({ js: ["javascript"], javascript: ["js"]})
.addMapper("markdown").mapDefn<IMarkdownAst>(i => {
title: i.h1,
section: i.h2,
lastUpdated: i.lastUpdated,
url: i.url
})
)
```
- +++ use the model to call the MeiliSearch API
```ts
import { Prose } from "./models";
// create an index
await Prose.api.createIndex();
// get index stats
await Prose.api.stats();
// search on index
await Prose.api.search("foobar");
```
- +++ leverage mappers embedded in the model
```ts
import { Prose } from "./models";
// map from Input structure to expected document structure
const doc: IProse = Prose.mapWith("markdown", data);
// using mapping to perform an update on the index
await Prose.updateWith("markdown", data);
```
> note: in the examples we're supposing `data` to be a single Node/Record but
> you can actually pass in either a single record or a list and it will manage
> both
## External Resources
- General Documentation
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [MeiliSearch Website Docs](https://docs.meilisearch.com/learn/what_is_meilisearch/)
- API Docs
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [Open API for MeiliSearch](https://bump.sh/doc/meilisearch)
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [API Docs from MeiliSearch Website](https://docs.meilisearch.com/reference/api/)
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [Postman Collection of MeiliSearch API](https://docs.meilisearch.com/postman/meilisearch-collection.json)
- Interactive
- <span class="bg-green-500 rounded px-2 py-1 text-white">GET</span> - [MeiliSearch Dashboard](http://localhost:7700/)

View File

@@ -24,7 +24,7 @@ onStartTyping(() => {
<template>
<div class="py-3 w-full h-full">
<h1 class="text-xl mb-4">
Meilisearch Playground
MeiliSearch Playground
</h1>
<input
@@ -44,8 +44,7 @@ onStartTyping(() => {
<div class="results-area grid grid-cols-3 gap-4 h-full mt-8 mx-4">
<div class="flex flex-col col-span-1 space-y-4">
<search-actions />
<search-stats class="mt-4" />
<search-indexes class="" />
</div>
<div class="col-span-2 rounded-md bg-gray-100/25 dark:bg-gray-900/25">

View File

@@ -23,5 +23,5 @@
}
},
"include": ["src", "test", "vite.config.ts"],
"exclude": ["dist", "node_modules"]
"exclude": ["dist", "node_modules",]
}

View File

@@ -165,7 +165,7 @@ export default defineConfig({
include: ["test/**/*.test.ts"],
environment: "jsdom",
api: {
port: 5555,
port: 4444,
host: "0.0.0.0",
},

View File

@@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc"
}

Some files were not shown because too many files have changed in this diff Show More