Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/cap-js/docs
Browse files Browse the repository at this point in the history
  • Loading branch information
renejeglinsky committed Dec 17, 2024
2 parents 9499bfb + 503870d commit fd9a986
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 71 deletions.
7 changes: 7 additions & 0 deletions .vitepress/theme/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import ImplVariants from './components/implvariants/ImplVariants.vue'
import NavScreenMenuItem from './components/implvariants/NavScreenMenuItem.vue'
import Ribbon from './components/Ribbon.vue'
import ScrollToTop from './components/ScrollToTopSimple.vue'
import WasThisHelpful from './components/WasThisHelpful.vue'
import { useRoute } from 'vitepress'
const isPreview = !!import.meta.env.VITE_CAPIRE_PREVIEW
const { Layout } = DefaultTheme
const { frontmatter } = useData()
const route = useRoute()
</script>

<template>
Expand All @@ -24,6 +28,9 @@ const { frontmatter } = useData()
<template #doc-top>
<slot name="doc-top" />
</template>
<template #doc-after>
<WasThisHelpful :key="route.path" />
</template>
<template #not-found>
<slot name="not-found" />
</template>
Expand Down
4 changes: 3 additions & 1 deletion .vitepress/theme/components/ShortcutsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ watch(visible, isVisible => {
})
function onKeyDown(event) {
const { tagName, isContentEditable } = document.activeElement
if (tagName === 'INPUT' || tagName === 'TEXTAREA' || isContentEditable) return
if (document.activeElement === document.querySelectorAll(querySelectorSearchInput)[0]) return // search is active
if (event.altKey || event.ctrlKey || event.metaKey) return // only simple keys for now
if (event.key === 'Shift' && visible.value) {
Expand All @@ -92,7 +94,7 @@ function onKeyDown(event) {
}
const cmd = commands.value.find(cmd => !!cmd.keys.find(k => k.value === event.key))
const enabled = cmd && cmd.run && ('enabled' in cmd ? cmd.enabled() : true)
if (enabled) {
if (enabled) {
event.preventDefault()
cmd.run(event)
}
Expand Down
216 changes: 216 additions & 0 deletions .vitepress/theme/components/WasThisHelpful.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<template>
<div class="helpful-container">
<p v-if="!feedbackSent">Was this page helpful?</p>
<div v-if="!feedbackSent" class="button-row">
<button
:class="{ selected: selection === 'Positive' }"
@click="handlePositive"
title="This page was helpful"
>
👍
</button>
<button
:class="{ selected: selection === 'Negative' }"
@click="handleNegative"
title="This page was not helpful"
>
👎
</button>
</div>

<p v-if="feedbackSent" class="thank-you">Thank you for your feedback!</p>

<div class="feedback-section" v-if="feedbackSelected && !feedbackSent">
<div>
<p>Thank you for your feedback!</p>
<p><br>Feel free to add an optional comment below.<br>Make sure not to include any personal information.</p>
</div>
<div class="textarea-container">
<textarea
ref="feedbackInput"
v-model="feedbackText"
:placeholder="placeholderText"
:maxlength="charLimit"
class="feedback-textarea"
></textarea>

<p class="char-count">{{ feedbackText.length }}/140</p>
</div>

<div class="send-feedback">
<button class="feedback-button" @click="sendFeedback" :disabled="!feedbackText.trim()">Send</button>
</div>
</div>

<p class="more-feedback" v-if="feedbackSelected">
More to say?
<a href="https://github.com/cap-js/docs/issues" target="_blank">
Report an issue.
</a>
</p>
</div>
</template>

<script setup>
import { ref, computed } from 'vue'
const charLimit = 140
const feedbackText = ref('')
const feedbackSelected = ref(false)
const feedbackSent = ref(false)
const selection = ref(null)
const handlePositive = () => {
if (selection.value === 'Positive') return
if (typeof window !== 'undefined' && window._paq) {
const path = new URL(window.location.href).pathname
window._paq.push(['trackEvent', path, 'Positive'])
}
feedbackSelected.value = true
selection.value = 'Positive'
}
const handleNegative = () => {
if (selection.value === 'Negative') return
if (typeof window !== 'undefined' && window._paq) {
const path = new URL(window.location.href).pathname
window._paq.push(['trackEvent', path, 'Negative'])
}
feedbackSelected.value = true
selection.value = 'Negative'
}
const sendFeedback = () => {
if (typeof window !== 'undefined' && window._paq) {
const path = new URL(window.location.href).pathname
const name = feedbackText.value.trim()
window._paq.push(['trackEvent', path, selection.value, name])
feedbackSent.value = true
}
}
const placeholderText = computed(() => {
if (selection.value === 'Positive') return 'What did you like about the page?'
if (selection.value === 'Negative') return 'What did you miss on this page?'
return ''
})
</script>

<style scoped>
.helpful-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 30px;
}
p {
margin: 0;
}
.button-row {
display: flex;
gap: 1rem;
margin-top: 0.5rem;
justify-content: center;
}
button {
cursor: pointer;
border: 1px solid var(--vp-c-divider);
padding: 0.5rem 1rem;
border-radius: 4px;
background: none;
}
button:hover {
border-color: var(--vp-c-brand-1);
}
button.selected {
border-color: var(--vp-c-brand-1);
font-weight: bold;
}
.feedback-button {
background-color: #3b82f7;
border: none;
font-weight: 600;
transition: opacity 0.2s;
min-width: 100px;
}
.feedback-button:hover, .feedback-button.selected {
border: none;
opacity: 0.8;
}
.feedback-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.feedback-section {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 500px;
gap: 0.5rem;
margin-top: 1rem;
position: relative;
text-align: center;
}
.textarea-container {
position: relative;
width: 100%;
}
.feedback-textarea {
font-family: var(--vp-font-family);
font-size: 14px;
font-weight: 500;
width: 100%;
height: 90px;
padding: 0.5rem;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
resize: none;
}
.char-count {
position: absolute;
bottom: 0.5rem;
right: 0.5rem;
font-size: 0.9rem;
color: #666;
padding: 0 0.3rem;
border-radius: 3px;
}
.send-feedback {
color: white;
text-align: center;
}
.thank-you {
margin-top: 1rem;
font-size: 1rem;
color: var(--vp-c-success);
}
.more-feedback {
margin-top: 2rem;
text-align: center;
}
.more-feedback a {
color: var(--vp-c-brand-1);
text-decoration: none;
}
.more-feedback a:hover {
text-decoration: underline;
}
</style>
65 changes: 41 additions & 24 deletions cds/cqn.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class SELECT { SELECT: {
columns? : column[]
where? : xo[]
having? : xo[]
search? : xo[]
groupBy? : expr[]
orderBy? : order[]
limit? : { rows: val, offset: val }
Expand All @@ -82,8 +81,7 @@ which is equivalent to SQL's `SELECT * from ...`.


### `.from`

{#source}
###### source

Property `from` specifies the source of the query, which can be a table, a view, or a subquery.
It is specified with type `source` as follows:
Expand Down Expand Up @@ -112,9 +110,11 @@ type source = ref &as | SELECT | {
### `.columns`
{#column}
###### column
###### as
###### cast
###### expand
{#as}
Property `columns` specifies the columns to be selected, projected, or aggregated, and is specified as an array of `column`s:
Expand All @@ -128,14 +128,16 @@ type column = '*' | expr &as &cast | ref &as &(
{ expand?: column[] } |
{ inline?: column[] }
) &infix
```
```tsx
interface as { as?: name }
interface cast { cast?: {type:name} }
```
> A `cast` is essentially a CSN [type definition](./csn#type-definitions).
>
> Using:
> [`expr`](#expr),
> [`name`](#name)
> [`name`](#name),
> [`ref`](#ref),
> [`infix`](#infix)
>
Expand All @@ -159,6 +161,7 @@ class SELECT { SELECT: {


### `.orderBy`
###### order

```tsx
class SELECT { SELECT: { //...
Expand Down Expand Up @@ -247,6 +250,8 @@ let q = {INSERT:{ into: { ref: ['Authors'] }, entries: [

### `.values`

{#scalar}

Allows input data to be specified as an single array of values, as in SQL.

```js
Expand Down Expand Up @@ -307,8 +312,7 @@ interface data { [element:name]: scalar | data | data[] }

### `.with`

{#changes}
###### changes

Property `with` specifies the changes to be applied to the data, very similar to property [`data`](#data) with the difference to also allow [expressions](#expressions) as values.

Expand Down Expand Up @@ -337,22 +341,21 @@ class DELETE { DELETE: {

## Expressions

{#expr}

{#ref}

{#val}

{#xpr}

{#list}

{#func}

{#param}

Following are the axiomatic building blocks used in CQN expressions:
###### expr
###### ref
###### val
###### xpr
###### list
###### func
###### param
###### infix
###### xo
###### name
###### scalar

Expressions can be entity or element references, query parameters,
literal values, lists of all the former, function calls, sub selects,
or compound expressions.

```tsx
type expr = ref | val | xpr | list | func | param | SELECT
Expand All @@ -366,5 +369,19 @@ type func = { func: string, args: expr[] }
type param = { ref: [ '?' | number | string ], param: true }
```
```tsx
type xo = expr | keyword | operator
type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
type keyword = 'in' | 'like' | 'and' | 'or' | 'not'
type scalar = number | string | boolean | null
type name = string
```
>[!note]
> CQN by intent does not _understand_ expressions and therefore
> keywords and operators are just represented as plain strings in flat
> `xo` sequences. This allows us to translate to and from any other query languages,
> including support for native SQL features.
<div id="hierarchy-queries" />
Loading

0 comments on commit fd9a986

Please sign in to comment.